2016-08-04 23:35:58 +02:00
|
|
|
import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE;
|
2015-09-20 21:21:51 +02:00
|
|
|
import core.memory, core.time, core.thread;
|
2018-09-13 00:46:27 +02:00
|
|
|
import std.getopt, std.file, std.path, std.process, std.stdio, std.conv, std.algorithm.searching;
|
2017-03-24 22:30:03 +01:00
|
|
|
import config, itemdb, monitor, onedrive, selective, sync, util;
|
2018-08-13 23:21:11 +02:00
|
|
|
import std.net.curl: CurlException;
|
2016-08-04 23:35:58 +02:00
|
|
|
static import log;
|
2015-09-01 20:45:34 +02:00
|
|
|
|
2016-08-04 23:35:58 +02:00
|
|
|
int main(string[] args)
|
2015-09-01 20:45:34 +02:00
|
|
|
{
|
2018-08-27 02:35:58 +02:00
|
|
|
// Determine the users home directory.
|
|
|
|
// Need to avoid using ~ here as expandTilde() below does not interpret correctly when running under init.d or systemd scripts
|
|
|
|
string homePath = "";
|
2018-09-20 21:45:06 +02:00
|
|
|
|
|
|
|
// Check for HOME environment variable
|
|
|
|
if (environment.get("HOME") != ""){
|
|
|
|
// Use HOME environment variable
|
|
|
|
homePath = environment.get("HOME");
|
2018-08-27 02:35:58 +02:00
|
|
|
} else {
|
2018-09-20 21:45:06 +02:00
|
|
|
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
|
|
|
|
// No shell is set or username - observed case when running as systemd service under CentOS 7.x
|
|
|
|
homePath = "/root";
|
2018-08-27 02:35:58 +02:00
|
|
|
} else {
|
2018-09-20 21:45:06 +02:00
|
|
|
// A shell & valid user is set, but no HOME is set, use ~ which can be expanded
|
|
|
|
homePath = "~";
|
2018-08-27 02:35:58 +02:00
|
|
|
}
|
2018-04-13 01:33:16 +02:00
|
|
|
}
|
2018-09-20 21:45:06 +02:00
|
|
|
|
|
|
|
// Determine the base directory relative to which user specific configuration files should be stored.
|
|
|
|
string configDirBase = "";
|
|
|
|
if (environment.get("XDG_CONFIG_HOME") != ""){
|
|
|
|
configDirBase = environment.get("XDG_CONFIG_HOME");
|
|
|
|
} else {
|
|
|
|
// XDG_CONFIG_HOME does not exist on systems where X11 is not present - ie - headless systems / servers
|
|
|
|
configDirBase = homePath ~ "/.config";
|
|
|
|
}
|
2018-04-13 01:33:16 +02:00
|
|
|
|
2016-08-04 23:35:58 +02:00
|
|
|
// configuration directory
|
2018-09-20 21:45:06 +02:00
|
|
|
string configDirName = configDirBase ~ "/onedrive";
|
2018-04-07 09:06:57 +02:00
|
|
|
// only download remote changes
|
|
|
|
bool downloadOnly;
|
2017-08-01 19:11:50 +02:00
|
|
|
// override the sync directory
|
|
|
|
string syncDirName;
|
2016-08-04 23:35:58 +02:00
|
|
|
// enable monitor mode
|
|
|
|
bool monitor;
|
|
|
|
// force a full resync
|
|
|
|
bool resync;
|
2016-08-05 00:12:58 +02:00
|
|
|
// remove the current user and sync state
|
|
|
|
bool logout;
|
2016-08-04 23:35:58 +02:00
|
|
|
// enable verbose logging
|
|
|
|
bool verbose;
|
2017-05-28 20:14:50 +02:00
|
|
|
// print the access token
|
|
|
|
bool printAccessToken;
|
2017-07-14 11:31:16 +02:00
|
|
|
// print the version and exit
|
|
|
|
bool printVersion;
|
2018-03-14 05:43:40 +01:00
|
|
|
|
|
|
|
// Additional options added to support MyNAS Storage Appliance
|
2018-05-16 11:19:43 +02:00
|
|
|
// Debug the HTTPS submit operations if required
|
2018-03-14 05:43:40 +01:00
|
|
|
bool debugHttp;
|
2018-05-16 11:19:43 +02:00
|
|
|
// Debug the HTTPS response operations if required
|
|
|
|
bool debugHttpSubmit;
|
2018-03-14 05:43:40 +01:00
|
|
|
// This allows for selective directory syncing instead of everything under ~/OneDrive/
|
|
|
|
string singleDirectory;
|
|
|
|
// Create a single root directory on OneDrive
|
|
|
|
string createDirectory;
|
|
|
|
// Remove a single directory on OneDrive
|
|
|
|
string removeDirectory;
|
|
|
|
// The source directory if we are using the OneDrive client to rename a directory
|
|
|
|
string sourceDirectory;
|
|
|
|
// The destination directory if we are using the OneDrive client to rename a directory
|
|
|
|
string destinationDirectory;
|
|
|
|
// Configure a flag to perform a sync
|
|
|
|
// This is beneficial so that if just running the client itself - without any options, or sync check, the client does not perform a sync
|
|
|
|
bool synchronize;
|
|
|
|
// Local sync - Upload local changes first before downloading changes from OneDrive
|
|
|
|
bool localFirst;
|
2018-04-07 09:06:57 +02:00
|
|
|
// Upload Only
|
|
|
|
bool uploadOnly;
|
2018-06-17 00:27:43 +02:00
|
|
|
// Add a check mounts option to resolve https://github.com/abraunegg/onedrive/issues/8
|
|
|
|
bool checkMount;
|
2018-08-02 22:02:10 +02:00
|
|
|
// Add option to skip symlinks
|
|
|
|
bool skipSymlinks;
|
2018-08-05 02:43:31 +02:00
|
|
|
// Add option for no remote delete
|
|
|
|
bool noRemoteDelete;
|
2018-08-13 23:21:11 +02:00
|
|
|
// Are we able to reach the OneDrive Service
|
|
|
|
bool online = false;
|
2018-11-23 21:13:16 +01:00
|
|
|
// Do we enable a log file
|
|
|
|
bool enableLogFile = false;
|
2018-11-23 20:26:30 +01:00
|
|
|
// Does the user want to disable upload validation - https://github.com/abraunegg/onedrive/issues/205
|
|
|
|
// SharePoint will associate some metadata from the library the file is uploaded to directly in the file - thus change file size & checksums
|
|
|
|
bool disableUploadValidation = false;
|
2018-12-04 00:59:23 +01:00
|
|
|
// SharePoint / Office 365 Shared Library name to query
|
|
|
|
string o365SharedLibraryName;
|
2018-12-05 20:19:00 +01:00
|
|
|
// Do not use notifications in monitor mode
|
|
|
|
bool disableNotifications = false;
|
2018-12-19 19:42:28 +01:00
|
|
|
// Display application configuration but do not sync
|
|
|
|
bool displayConfiguration = false;
|
2018-08-13 23:21:11 +02:00
|
|
|
|
2015-09-14 19:21:06 +02:00
|
|
|
try {
|
|
|
|
auto opt = getopt(
|
|
|
|
args,
|
2016-08-04 23:35:58 +02:00
|
|
|
std.getopt.config.bundling,
|
2017-07-14 11:31:16 +02:00
|
|
|
std.getopt.config.caseSensitive,
|
2018-06-26 22:23:17 +02:00
|
|
|
"check-for-nomount", "Check for the presence of .nosync in the syncdir root. If found, do not perform sync.", &checkMount,
|
2017-07-14 11:31:16 +02:00
|
|
|
"confdir", "Set the directory used to store the configuration files", &configDirName,
|
2018-03-14 05:43:40 +01:00
|
|
|
"create-directory", "Create a directory on OneDrive - no sync will be performed.", &createDirectory,
|
|
|
|
"destination-directory", "Destination directory for renamed or move on OneDrive - no sync will be performed.", &destinationDirectory,
|
2018-04-15 12:02:39 +02:00
|
|
|
"debug-https", "Debug OneDrive HTTPS communication.", &debugHttp,
|
2018-12-05 20:19:00 +01:00
|
|
|
"disable-notifications", "Do not use desktop notifications in monitor mode.", &disableNotifications,
|
2018-12-19 19:42:28 +01:00
|
|
|
"display-config", "Display what options the client will use as currently configured - no sync will be performed.", &displayConfiguration,
|
2018-11-23 21:13:16 +01:00
|
|
|
"download-only|d", "Only download remote changes", &downloadOnly,
|
2018-11-23 20:26:30 +01:00
|
|
|
"disable-upload-validation", "Disable upload validation when uploading to OneDrive", &disableUploadValidation,
|
2018-11-23 21:13:16 +01:00
|
|
|
"enable-logging", "Enable client activity to a separate log file", &enableLogFile,
|
2018-12-04 00:59:23 +01:00
|
|
|
"get-O365-drive-id", "Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library", &o365SharedLibraryName,
|
2018-03-14 05:43:40 +01:00
|
|
|
"local-first", "Synchronize from the local directory source first, before downloading changes from OneDrive.", &localFirst,
|
2017-07-14 11:31:16 +02:00
|
|
|
"logout", "Logout the current user", &logout,
|
|
|
|
"monitor|m", "Keep monitoring for local and remote changes", &monitor,
|
2018-08-05 02:43:31 +02:00
|
|
|
"no-remote-delete", "Do not delete local file 'deletes' from OneDrive when using --upload-only", &noRemoteDelete,
|
2017-07-14 11:31:16 +02:00
|
|
|
"print-token", "Print the access token, useful for debugging", &printAccessToken,
|
|
|
|
"resync", "Forget the last saved state, perform a full sync", &resync,
|
2018-03-14 05:43:40 +01:00
|
|
|
"remove-directory", "Remove a directory on OneDrive - no sync will be performed.", &removeDirectory,
|
|
|
|
"single-directory", "Specify a single local directory within the OneDrive root to sync.", &singleDirectory,
|
2018-08-02 22:02:10 +02:00
|
|
|
"skip-symlinks", "Skip syncing of symlinks", &skipSymlinks,
|
2018-03-14 05:43:40 +01:00
|
|
|
"source-directory", "Source directory to rename or move on OneDrive - no sync will be performed.", &sourceDirectory,
|
2018-01-19 17:56:36 +01:00
|
|
|
"syncdir", "Set the directory used to sync the files that are synced", &syncDirName,
|
2018-03-14 05:43:40 +01:00
|
|
|
"synchronize", "Perform a synchronization", &synchronize,
|
2018-04-07 09:06:57 +02:00
|
|
|
"upload-only", "Only upload to OneDrive, do not sync changes from OneDrive locally", &uploadOnly,
|
2018-12-07 19:01:22 +01:00
|
|
|
"verbose|v+", "Print more details, useful for debugging (repeat for extra debugging)", &log.verbose,
|
2017-07-14 11:31:16 +02:00
|
|
|
"version", "Print the version and exit", &printVersion
|
2015-09-14 19:21:06 +02:00
|
|
|
);
|
|
|
|
if (opt.helpWanted) {
|
2015-09-22 11:20:54 +02:00
|
|
|
defaultGetoptPrinter(
|
|
|
|
"Usage: onedrive [OPTION]...\n\n" ~
|
2018-04-12 02:18:18 +02:00
|
|
|
"no option No sync and exit",
|
2015-09-22 11:20:54 +02:00
|
|
|
opt.options
|
|
|
|
);
|
2016-08-04 23:35:58 +02:00
|
|
|
return EXIT_SUCCESS;
|
2015-09-14 19:21:06 +02:00
|
|
|
}
|
|
|
|
} catch (GetOptException e) {
|
2017-12-28 15:21:41 +01:00
|
|
|
log.error(e.msg);
|
|
|
|
log.error("Try 'onedrive -h' for more information");
|
2016-08-04 23:35:58 +02:00
|
|
|
return EXIT_FAILURE;
|
2015-09-14 19:21:06 +02:00
|
|
|
}
|
2018-12-05 20:19:00 +01:00
|
|
|
|
2018-02-18 18:24:46 +01:00
|
|
|
// disable buffering on stdout
|
|
|
|
stdout.setvbuf(0, _IONBF);
|
2015-09-14 19:21:06 +02:00
|
|
|
|
2017-07-14 11:31:16 +02:00
|
|
|
if (printVersion) {
|
2017-08-01 19:11:50 +02:00
|
|
|
std.stdio.write("onedrive ", import("version"));
|
2017-07-14 11:31:16 +02:00
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2018-04-13 01:33:16 +02:00
|
|
|
// load configuration
|
2016-08-04 23:35:58 +02:00
|
|
|
log.vlog("Loading config ...");
|
2017-06-12 16:46:10 +02:00
|
|
|
configDirName = configDirName.expandTilde().absolutePath();
|
2018-04-13 01:33:16 +02:00
|
|
|
log.vlog("Using Config Dir: ", configDirName);
|
2017-08-01 19:11:50 +02:00
|
|
|
if (!exists(configDirName)) mkdirRecurse(configDirName);
|
2016-08-04 23:35:58 +02:00
|
|
|
auto cfg = new config.Config(configDirName);
|
|
|
|
cfg.init();
|
2017-08-01 19:11:50 +02:00
|
|
|
|
2018-12-19 19:42:28 +01:00
|
|
|
// Set the local path OneDrive root
|
|
|
|
string syncDir;
|
|
|
|
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
|
|
|
|
// No shell or user set, so expandTilde() will fail - usually headless system running under init.d / systemd
|
|
|
|
// Did the user specify a 'different' sync dir by passing a value in?
|
|
|
|
if (syncDirName){
|
|
|
|
// was there a ~ in the passed in state? it will not work via init.d / systemd
|
|
|
|
if (canFind(cfg.getValue("sync_dir"),"~")) {
|
|
|
|
// A ~ was found
|
|
|
|
syncDir = homePath ~ "/OneDrive";
|
|
|
|
} else {
|
|
|
|
// No ~ found in passed in state, use as is
|
|
|
|
syncDir = cfg.getValue("sync_dir");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// need to create a default as expanding ~ will not work
|
|
|
|
syncDir = homePath ~ "/OneDrive";
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// A shell and user is set, expand any ~ as this will be expanded if present
|
|
|
|
syncDir = expandTilde(cfg.getValue("sync_dir"));
|
|
|
|
}
|
|
|
|
|
2018-11-23 21:13:16 +01:00
|
|
|
// Configure logging if enabled
|
|
|
|
if (enableLogFile){
|
|
|
|
// Read in a user defined log directory or use the default
|
|
|
|
string logDir = cfg.getValue("log_dir");
|
|
|
|
log.vlog("Using logfile dir: ", logDir);
|
|
|
|
log.init(logDir);
|
|
|
|
}
|
2018-12-05 20:19:00 +01:00
|
|
|
|
|
|
|
// Configure whether notifications are used
|
|
|
|
log.setNotifications(monitor && !disableNotifications);
|
2018-11-23 21:13:16 +01:00
|
|
|
|
2017-08-01 19:11:50 +02:00
|
|
|
// command line parameters override the config
|
2018-07-09 21:34:27 +02:00
|
|
|
if (syncDirName) cfg.setValue("sync_dir", syncDirName.expandTilde().absolutePath());
|
2018-08-02 22:02:10 +02:00
|
|
|
if (skipSymlinks) cfg.setValue("skip_symlinks", "true");
|
2018-08-05 02:43:31 +02:00
|
|
|
|
2016-12-25 19:23:33 +01:00
|
|
|
// upgrades
|
|
|
|
if (exists(configDirName ~ "/items.db")) {
|
|
|
|
remove(configDirName ~ "/items.db");
|
2018-12-05 20:19:00 +01:00
|
|
|
log.logAndNotify("Database schema changed, resync needed");
|
2016-12-25 19:23:33 +01:00
|
|
|
resync = true;
|
|
|
|
}
|
|
|
|
|
2016-08-05 00:12:58 +02:00
|
|
|
if (resync || logout) {
|
2017-05-28 23:14:37 +02:00
|
|
|
log.vlog("Deleting the saved status ...");
|
2016-08-05 00:12:58 +02:00
|
|
|
safeRemove(cfg.databaseFilePath);
|
2017-05-28 20:54:57 +02:00
|
|
|
safeRemove(cfg.deltaLinkFilePath);
|
2016-08-05 00:12:58 +02:00
|
|
|
safeRemove(cfg.uploadStateFilePath);
|
|
|
|
if (logout) {
|
|
|
|
safeRemove(cfg.refreshTokenFilePath);
|
|
|
|
}
|
2015-09-14 19:21:06 +02:00
|
|
|
}
|
|
|
|
|
2018-12-19 19:42:28 +01:00
|
|
|
// Display current application configuration, no application initialisation
|
|
|
|
if (displayConfiguration){
|
|
|
|
string userConfigFilePath = configDirName ~ "/config";
|
|
|
|
string userSyncList = configDirName ~ "/sync_list";
|
|
|
|
// Display all of the pertinent configuration options
|
|
|
|
writeln("Config path = ", configDirName);
|
|
|
|
|
|
|
|
// Does a config file exist or are we using application defaults
|
|
|
|
if (exists(userConfigFilePath)){
|
|
|
|
writeln("Config file found in config path = true");
|
|
|
|
} else {
|
|
|
|
writeln("Config file found in config path = false");
|
|
|
|
}
|
|
|
|
|
|
|
|
// Config Options
|
|
|
|
writeln("Config option 'sync_dir' = ", syncDir);
|
|
|
|
writeln("Config option 'skip_file' = ", cfg.getValue("skip_file"));
|
|
|
|
writeln("Config option 'skip_symlinks' = ", cfg.getValue("skip_symlinks"));
|
|
|
|
writeln("Config option 'monitor_interval' = ", cfg.getValue("monitor_interval"));
|
|
|
|
writeln("Config option 'log_dir' = ", cfg.getValue("log_dir"));
|
|
|
|
|
|
|
|
// Is config option drive_id configured?
|
|
|
|
if (cfg.getValue("drive_id", "") != ""){
|
|
|
|
writeln("Config option 'drive_id' = ", cfg.getValue("drive_id"));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is sync_list configured?
|
|
|
|
if (exists(userSyncList)){
|
|
|
|
writeln("Selective sync configured = true");
|
|
|
|
} else {
|
|
|
|
writeln("Selective sync configured = false");
|
|
|
|
}
|
|
|
|
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
}
|
|
|
|
|
2016-08-04 23:35:58 +02:00
|
|
|
log.vlog("Initializing the OneDrive API ...");
|
2018-08-13 23:21:11 +02:00
|
|
|
try {
|
|
|
|
online = testNetwork();
|
|
|
|
} catch (CurlException e) {
|
|
|
|
// No network connection to OneDrive Service
|
|
|
|
log.error("No network connection to Microsoft OneDrive Service");
|
2018-12-04 01:15:44 +01:00
|
|
|
if (!monitor) {
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-16 11:19:43 +02:00
|
|
|
// Initialize OneDrive, check for authorization
|
2018-03-14 05:43:40 +01:00
|
|
|
auto onedrive = new OneDriveApi(cfg, debugHttp);
|
2017-05-28 20:14:50 +02:00
|
|
|
onedrive.printAccessToken = printAccessToken;
|
2016-08-04 23:35:58 +02:00
|
|
|
if (!onedrive.init()) {
|
2017-12-28 15:21:41 +01:00
|
|
|
log.error("Could not initialize the OneDrive API");
|
2016-08-04 23:35:58 +02:00
|
|
|
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
|
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
2015-09-01 20:45:34 +02:00
|
|
|
}
|
2018-12-19 19:42:28 +01:00
|
|
|
|
2018-04-24 04:14:36 +02:00
|
|
|
// if --synchronize or --monitor not passed in, exit & display help
|
|
|
|
auto performSyncOK = false;
|
2018-05-16 11:19:43 +02:00
|
|
|
|
2018-04-24 04:14:36 +02:00
|
|
|
if (synchronize || monitor) {
|
|
|
|
performSyncOK = true;
|
|
|
|
}
|
|
|
|
|
2018-05-16 11:19:43 +02:00
|
|
|
// create-directory, remove-directory, source-directory, destination-directory
|
|
|
|
// are activities that dont perform a sync no error message for these items either
|
2018-12-19 19:42:28 +01:00
|
|
|
if (((createDirectory != "") || (removeDirectory != "")) || ((sourceDirectory != "") && (destinationDirectory != "")) || (o365SharedLibraryName != "")) {
|
2018-05-16 11:19:43 +02:00
|
|
|
performSyncOK = true;
|
|
|
|
}
|
|
|
|
|
2018-04-24 04:14:36 +02:00
|
|
|
if (!performSyncOK) {
|
|
|
|
writeln("\n--synchronize or --monitor missing from your command options or use --help for further assistance\n");
|
|
|
|
writeln("No OneDrive sync will be performed without either of these two arguments being present\n");
|
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
|
2018-03-14 05:43:40 +01:00
|
|
|
// initialize system
|
2016-08-04 23:35:58 +02:00
|
|
|
log.vlog("Opening the item database ...");
|
|
|
|
auto itemdb = new ItemDatabase(cfg.databaseFilePath);
|
2018-03-14 05:43:40 +01:00
|
|
|
|
2016-08-04 23:35:58 +02:00
|
|
|
log.vlog("All operations will be performed in: ", syncDir);
|
2017-08-01 19:11:50 +02:00
|
|
|
if (!exists(syncDir)) mkdirRecurse(syncDir);
|
2015-09-27 18:47:41 +02:00
|
|
|
chdir(syncDir);
|
2018-03-14 05:43:40 +01:00
|
|
|
|
2018-05-16 11:19:43 +02:00
|
|
|
// Configure selective sync by parsing and getting a regex for skip_file config component
|
2017-03-24 22:30:03 +01:00
|
|
|
auto selectiveSync = new SelectiveSync();
|
|
|
|
selectiveSync.load(cfg.syncListFilePath);
|
|
|
|
selectiveSync.setMask(cfg.getValue("skip_file"));
|
2018-05-16 11:19:43 +02:00
|
|
|
|
|
|
|
// Initialise the sync engine
|
2018-12-05 20:19:00 +01:00
|
|
|
log.logAndNotify("Initializing the Synchronization Engine ...");
|
2017-03-24 22:30:03 +01:00
|
|
|
auto sync = new SyncEngine(cfg, onedrive, itemdb, selectiveSync);
|
2018-07-16 01:58:36 +02:00
|
|
|
|
|
|
|
try {
|
2018-12-04 01:15:44 +01:00
|
|
|
if (!initSyncEngine(sync)) {
|
2018-07-16 01:58:36 +02:00
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
2018-12-04 01:15:44 +01:00
|
|
|
} catch (CurlException e) {
|
|
|
|
if (!monitor) {
|
|
|
|
log.log("\nNo internet connection.");
|
2018-09-24 21:25:40 +02:00
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
2018-07-16 01:58:36 +02:00
|
|
|
}
|
2018-12-04 01:15:44 +01:00
|
|
|
|
2018-08-09 23:40:17 +02:00
|
|
|
// We should only set noRemoteDelete in an upload-only scenario
|
|
|
|
if ((uploadOnly)&&(noRemoteDelete)) sync.setNoRemoteDelete();
|
|
|
|
|
2018-11-23 20:26:30 +01:00
|
|
|
// Do we configure to disable the upload validation routine
|
|
|
|
if(disableUploadValidation) sync.setDisableUploadValidation();
|
|
|
|
|
2018-06-17 00:27:43 +02:00
|
|
|
// Do we need to validate the syncDir to check for the presence of a '.nosync' file
|
|
|
|
if (checkMount) {
|
|
|
|
// we were asked to check the mounts
|
|
|
|
if (exists(syncDir ~ "/.nosync")) {
|
2018-12-05 20:19:00 +01:00
|
|
|
log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data.");
|
2018-06-17 00:27:43 +02:00
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-14 05:43:40 +01:00
|
|
|
// Do we need to create or remove a directory?
|
|
|
|
if ((createDirectory != "") || (removeDirectory != "")) {
|
|
|
|
|
|
|
|
if (createDirectory != "") {
|
|
|
|
// create a directory on OneDrive
|
|
|
|
sync.createDirectoryNoSync(createDirectory);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (removeDirectory != "") {
|
|
|
|
// remove a directory on OneDrive
|
|
|
|
sync.deleteDirectoryNoSync(removeDirectory);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Are we renaming or moving a directory?
|
|
|
|
if ((sourceDirectory != "") && (destinationDirectory != "")) {
|
|
|
|
// We are renaming or moving a directory
|
|
|
|
sync.renameDirectoryNoSync(sourceDirectory, destinationDirectory);
|
|
|
|
}
|
|
|
|
|
2018-12-04 00:59:23 +01:00
|
|
|
// Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
|
|
|
|
if (o365SharedLibraryName != ""){
|
|
|
|
sync.querySiteCollectionForDriveID(o365SharedLibraryName);
|
|
|
|
}
|
|
|
|
|
2018-03-14 05:43:40 +01:00
|
|
|
// Are we performing a sync, resync or monitor operation?
|
|
|
|
if ((synchronize) || (resync) || (monitor)) {
|
2015-09-01 20:45:34 +02:00
|
|
|
|
2018-03-14 05:43:40 +01:00
|
|
|
if ((synchronize) || (resync)) {
|
|
|
|
if (online) {
|
|
|
|
// Check user entry for local path - the above chdir means we are already in ~/OneDrive/ thus singleDirectory is local to this path
|
|
|
|
if (singleDirectory != ""){
|
|
|
|
// Does the directory we want to sync actually exist?
|
|
|
|
if (!exists(singleDirectory)){
|
|
|
|
// the requested directory does not exist ..
|
2018-12-05 20:19:00 +01:00
|
|
|
log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path");
|
2018-03-14 05:43:40 +01:00
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the sync
|
2018-04-07 09:06:57 +02:00
|
|
|
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly);
|
2015-09-20 21:21:51 +02:00
|
|
|
}
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (monitor) {
|
2018-12-05 20:19:00 +01:00
|
|
|
log.logAndNotify("Initializing monitor ...");
|
2018-08-07 21:35:18 +02:00
|
|
|
log.log("OneDrive monitor interval (seconds): ", to!long(cfg.getValue("monitor_interval")));
|
2018-03-14 05:43:40 +01:00
|
|
|
Monitor m = new Monitor(selectiveSync);
|
|
|
|
m.onDirCreated = delegate(string path) {
|
|
|
|
log.vlog("[M] Directory created: ", path);
|
|
|
|
try {
|
|
|
|
sync.scanForDifferences(path);
|
|
|
|
} catch(Exception e) {
|
2018-12-06 08:05:52 +01:00
|
|
|
log.logAndNotify("Cannot create remote directory: ", e.msg);
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
m.onFileChanged = delegate(string path) {
|
|
|
|
log.vlog("[M] File changed: ", path);
|
|
|
|
try {
|
|
|
|
sync.scanForDifferences(path);
|
|
|
|
} catch(Exception e) {
|
2018-12-06 08:05:52 +01:00
|
|
|
log.logAndNotify("Cannot upload file changes/creation: ", e.msg);
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
m.onDelete = delegate(string path) {
|
|
|
|
log.vlog("[M] Item deleted: ", path);
|
|
|
|
try {
|
|
|
|
sync.deleteByPath(path);
|
2018-12-06 00:50:46 +01:00
|
|
|
} catch(SyncException e) {
|
|
|
|
if (e.msg == "The item to delete is not in the local database") {
|
|
|
|
log.vlog("Item cannot be deleted because not found in database");
|
|
|
|
} else {
|
2018-12-06 08:05:52 +01:00
|
|
|
log.logAndNotify("Cannot delete remote item: ", e.msg);
|
2018-12-06 00:50:46 +01:00
|
|
|
}
|
2018-03-14 05:43:40 +01:00
|
|
|
} catch(Exception e) {
|
2018-12-06 08:05:52 +01:00
|
|
|
log.logAndNotify("Cannot delete remote item: ", e.msg);
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
m.onMove = delegate(string from, string to) {
|
|
|
|
log.vlog("[M] Item moved: ", from, " -> ", to);
|
|
|
|
try {
|
|
|
|
sync.uploadMoveItem(from, to);
|
|
|
|
} catch(Exception e) {
|
2018-12-06 08:05:52 +01:00
|
|
|
log.logAndNotify("Cannot move item:, ", e.msg);
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
|
|
|
};
|
2018-08-17 00:03:21 +02:00
|
|
|
// initialise the monitor class
|
|
|
|
if (cfg.getValue("skip_symlinks") == "true") skipSymlinks = true;
|
|
|
|
if (!downloadOnly) m.init(cfg, verbose, skipSymlinks);
|
2018-03-14 05:43:40 +01:00
|
|
|
// monitor loop
|
2018-08-07 21:35:18 +02:00
|
|
|
immutable auto checkInterval = dur!"seconds"(to!long(cfg.getValue("monitor_interval")));
|
2018-03-14 05:43:40 +01:00
|
|
|
auto lastCheckTime = MonoTime.currTime();
|
|
|
|
while (true) {
|
|
|
|
if (!downloadOnly) m.update(online);
|
|
|
|
auto currTime = MonoTime.currTime();
|
|
|
|
if (currTime - lastCheckTime > checkInterval) {
|
2018-11-29 10:48:24 +01:00
|
|
|
try {
|
2018-12-04 01:15:44 +01:00
|
|
|
if (!initSyncEngine(sync)) {
|
|
|
|
onedrive.http.shutdown();
|
|
|
|
return EXIT_FAILURE;
|
|
|
|
}
|
2018-12-12 21:08:18 +01:00
|
|
|
try {
|
|
|
|
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly);
|
|
|
|
if (!downloadOnly) {
|
|
|
|
// discard all events that may have been generated by the sync
|
|
|
|
m.update(false);
|
|
|
|
}
|
|
|
|
} catch (CurlException e) {
|
|
|
|
// we already tried three times in the performSync routine
|
|
|
|
// if we still have problems, then the sync handle might have
|
|
|
|
// gone stale and we need to re-initialize the sync engine
|
|
|
|
log.log("Pesistent connection errors, reinitializing connection");
|
|
|
|
sync.reset();
|
2018-12-04 01:15:44 +01:00
|
|
|
}
|
2018-11-29 10:48:24 +01:00
|
|
|
} catch (CurlException e) {
|
2018-12-12 21:08:18 +01:00
|
|
|
log.log("Cannot initialize connection to OneDrive");
|
2017-12-31 17:07:21 +01:00
|
|
|
}
|
2018-08-07 21:35:18 +02:00
|
|
|
// performSync complete, set lastCheckTime to current time
|
|
|
|
lastCheckTime = MonoTime.currTime();
|
2018-03-14 05:43:40 +01:00
|
|
|
GC.collect();
|
2018-09-13 00:43:29 +02:00
|
|
|
}
|
|
|
|
Thread.sleep(dur!"msecs"(500));
|
2015-09-17 00:16:23 +02:00
|
|
|
}
|
|
|
|
}
|
2015-09-14 19:21:06 +02:00
|
|
|
}
|
2016-02-24 17:07:59 +01:00
|
|
|
|
|
|
|
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
|
|
|
|
onedrive.http.shutdown();
|
2016-08-04 23:35:58 +02:00
|
|
|
return EXIT_SUCCESS;
|
2015-09-01 20:45:34 +02:00
|
|
|
}
|
2015-09-20 21:21:51 +02:00
|
|
|
|
2018-12-04 01:15:44 +01:00
|
|
|
bool initSyncEngine(SyncEngine sync)
|
|
|
|
{
|
|
|
|
try {
|
|
|
|
sync.init();
|
|
|
|
} catch (OneDriveException e) {
|
|
|
|
if (e.httpStatusCode == 400 || e.httpStatusCode == 401) {
|
|
|
|
// Authorization is invalid
|
|
|
|
log.log("\nAuthorization token invalid, use --logout to authorize the client again\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (e.httpStatusCode >= 500) {
|
|
|
|
// There was a HTTP 5xx Server Side Error, message already printed
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2015-09-20 21:21:51 +02:00
|
|
|
// try to synchronize the folder three times
|
2018-04-07 09:06:57 +02:00
|
|
|
void performSync(SyncEngine sync, string singleDirectory, bool downloadOnly, bool localFirst, bool uploadOnly)
|
2015-09-20 21:21:51 +02:00
|
|
|
{
|
|
|
|
int count;
|
2018-03-14 05:43:40 +01:00
|
|
|
string remotePath = "/";
|
|
|
|
string localPath = ".";
|
|
|
|
|
|
|
|
// Are we doing a single directory sync?
|
|
|
|
if (singleDirectory != ""){
|
|
|
|
// Need two different path strings here
|
|
|
|
remotePath = singleDirectory;
|
|
|
|
localPath = singleDirectory;
|
|
|
|
}
|
|
|
|
|
2015-09-20 21:21:51 +02:00
|
|
|
do {
|
|
|
|
try {
|
2018-03-14 05:43:40 +01:00
|
|
|
if (singleDirectory != ""){
|
|
|
|
// we were requested to sync a single directory
|
|
|
|
log.vlog("Syncing changes from this selected path: ", singleDirectory);
|
2018-07-15 07:22:08 +02:00
|
|
|
if (uploadOnly){
|
|
|
|
// Upload Only of selected single directory
|
|
|
|
log.log("Syncing changes from selected local path only - NOT syncing data changes from OneDrive ...");
|
|
|
|
sync.scanForDifferences(localPath);
|
|
|
|
} else {
|
|
|
|
// No upload only
|
|
|
|
if (localFirst) {
|
|
|
|
// Local First
|
2018-04-12 02:18:18 +02:00
|
|
|
log.log("Syncing changes from selected local path first before downloading changes from OneDrive ...");
|
2018-04-07 09:06:57 +02:00
|
|
|
sync.scanForDifferences(localPath);
|
|
|
|
sync.applyDifferencesSingleDirectory(remotePath);
|
2018-07-15 07:22:08 +02:00
|
|
|
} else {
|
|
|
|
// OneDrive First
|
|
|
|
log.log("Syncing changes from selected OneDrive path ...");
|
|
|
|
sync.applyDifferencesSingleDirectory(remotePath);
|
|
|
|
// is this a download only request?
|
|
|
|
if (!downloadOnly) {
|
|
|
|
// process local changes
|
|
|
|
sync.scanForDifferences(localPath);
|
|
|
|
// ensure that the current remote state is updated locally
|
|
|
|
sync.applyDifferencesSingleDirectory(remotePath);
|
|
|
|
}
|
2018-04-07 09:06:57 +02:00
|
|
|
}
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
|
|
|
} else {
|
2018-07-15 07:22:08 +02:00
|
|
|
// no single directory sync
|
|
|
|
if (uploadOnly){
|
|
|
|
// Upload Only of entire sync_dir
|
|
|
|
log.log("Syncing changes from local path only - NOT syncing data changes from OneDrive ...");
|
|
|
|
sync.scanForDifferences(localPath);
|
|
|
|
} else {
|
|
|
|
// No upload only
|
|
|
|
if (localFirst) {
|
|
|
|
// sync local files first before downloading from OneDrive
|
|
|
|
log.log("Syncing changes from local path first before downloading changes from OneDrive ...");
|
2018-04-07 09:06:57 +02:00
|
|
|
sync.scanForDifferences(localPath);
|
|
|
|
sync.applyDifferences();
|
2018-07-15 07:22:08 +02:00
|
|
|
} else {
|
|
|
|
// sync from OneDrive first before uploading files to OneDrive
|
|
|
|
log.log("Syncing changes from OneDrive ...");
|
|
|
|
sync.applyDifferences();
|
|
|
|
// is this a download only request?
|
|
|
|
if (!downloadOnly) {
|
|
|
|
// process local changes
|
|
|
|
sync.scanForDifferences(localPath);
|
|
|
|
// ensure that the current remote state is updated locally
|
|
|
|
sync.applyDifferences();
|
|
|
|
}
|
2018-04-07 09:06:57 +02:00
|
|
|
}
|
2018-03-14 05:43:40 +01:00
|
|
|
}
|
2017-12-31 17:07:21 +01:00
|
|
|
}
|
2015-09-20 21:21:51 +02:00
|
|
|
count = -1;
|
2017-12-28 15:03:15 +01:00
|
|
|
} catch (Exception e) {
|
2018-12-12 21:08:18 +01:00
|
|
|
if (++count == 3) {
|
|
|
|
log.log("Giving up on sync after three attempts: ", e.msg);
|
|
|
|
throw e;
|
|
|
|
} else
|
|
|
|
log.log("Retry sync count: ", count, ": ", e.msg);
|
2015-09-20 21:21:51 +02:00
|
|
|
}
|
|
|
|
} while (count != -1);
|
|
|
|
}
|
2018-12-05 20:19:00 +01:00
|
|
|
|