mirror of
https://github.com/abraunegg/onedrive
synced 2024-06-29 19:02:59 +02:00
config options: working version
This commit is contained in:
parent
25b09775ab
commit
a635cab0ad
197
src/config.d
197
src/config.d
|
@ -1,4 +1,5 @@
|
||||||
import std.file, std.string, std.regex, std.stdio;
|
import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE, exit;
|
||||||
|
import std.file, std.string, std.regex, std.stdio, std.process, std.algorithm.searching, std.getopt, std.conv;
|
||||||
import selective;
|
import selective;
|
||||||
static import log;
|
static import log;
|
||||||
|
|
||||||
|
@ -13,31 +14,36 @@ final class Config
|
||||||
|
|
||||||
private string userConfigFilePath;
|
private string userConfigFilePath;
|
||||||
// hashmap for the values found in the user config file
|
// hashmap for the values found in the user config file
|
||||||
private string[string] values = [
|
// ARGGGG D is stupid and cannot make hashmap initializations!!!
|
||||||
"upload_only": "false",
|
// private string[string] foobar = [ "aa": "bb" ] does NOT work!!!
|
||||||
"check_for_nomount": "false",
|
private string[string] stringValues;
|
||||||
"download_only": "false",
|
private bool[string] boolValues;
|
||||||
"disable_notifications": "false",
|
private long[string] longValues;
|
||||||
"disable_upload_validation": "false",
|
|
||||||
"enable_logging": "false",
|
|
||||||
"force_http_11": "false",
|
|
||||||
"local_first": "false",
|
|
||||||
"no_remote_delete": "false",
|
|
||||||
"skip_symlinks": "false",
|
|
||||||
"debug_https": "false",
|
|
||||||
"verbose": "0",
|
|
||||||
"monitor_interval" : "45",
|
|
||||||
"min_notif_changes": "5",
|
|
||||||
"single_directory": "",
|
|
||||||
"sync_dir": "~/OneDrive",
|
|
||||||
"skip_file": "~*",
|
|
||||||
"log_dir": "/var/log/onedrive/",
|
|
||||||
"drive_id": ""
|
|
||||||
]:
|
|
||||||
|
|
||||||
|
|
||||||
this(string configDirName)
|
this(string configDirName)
|
||||||
{
|
{
|
||||||
|
// default configuration
|
||||||
|
stringValues["single_directory"] = "";
|
||||||
|
stringValues["sync_dir"] = "~/OneDrive";
|
||||||
|
stringValues["skip_file"] = "~*";
|
||||||
|
stringValues["log_dir"] = "/var/log/onedrive/";
|
||||||
|
stringValues["drive_id"] = "";
|
||||||
|
boolValues["upload_only"] = false;
|
||||||
|
boolValues["check_for_nomount"] = false;
|
||||||
|
boolValues["download_only"] = false;
|
||||||
|
boolValues["disable_notifications"] = false;
|
||||||
|
boolValues["disable_upload_validation"] = false;
|
||||||
|
boolValues["enable_logging"] = false;
|
||||||
|
boolValues["force_http_11"] = false;
|
||||||
|
boolValues["local_first"] = false;
|
||||||
|
boolValues["no_remote_delete"] = false;
|
||||||
|
boolValues["skip_symlinks"] = false;
|
||||||
|
boolValues["debug_https"] = false;
|
||||||
|
longValues["verbose"] = 0;
|
||||||
|
longValues["monitor_interval"] = 45,
|
||||||
|
longValues["min_notif_changes"] = 5;
|
||||||
|
|
||||||
// Determine the users home directory.
|
// 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
|
// Need to avoid using ~ here as expandTilde() below does not interpret correctly when running under init.d or systemd scripts
|
||||||
// Check for HOME environment variable
|
// Check for HOME environment variable
|
||||||
|
@ -64,6 +70,7 @@ final class Config
|
||||||
|
|
||||||
|
|
||||||
// Determine the correct configuration directory to use
|
// Determine the correct configuration directory to use
|
||||||
|
string configDirBase;
|
||||||
if (configDirName != "") {
|
if (configDirName != "") {
|
||||||
// A CLI 'confdir' was passed in
|
// A CLI 'confdir' was passed in
|
||||||
log.vdebug("configDirName: CLI override to set configDirName to: ", configDirName);
|
log.vdebug("configDirName: CLI override to set configDirName to: ", configDirName);
|
||||||
|
@ -119,74 +126,73 @@ final class Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool update_from_args(string[] args)
|
Option[] update_from_args(string[] args)
|
||||||
{
|
{
|
||||||
// Debug the HTTPS submit operations if required
|
|
||||||
bool debugHttp = cfg.getValue("debug_https") == "false" ? false : true;
|
|
||||||
// Do not use notifications in monitor mode
|
|
||||||
bool disableNotifications = cfg.getValue("disable_notifications") == "false" ? false : true;
|
|
||||||
// only download remote changes
|
|
||||||
bool downloadOnly = cfg.getValue("download_only") == "false" ? false : true;
|
|
||||||
// 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 = cfg.getValue("disable_upload_validation") == "false" ? false : true;
|
|
||||||
// Do we enable a log file
|
|
||||||
bool enableLogFile = cfg.getValue("enable_logging") == "false" ? false : true;
|
|
||||||
// Force the use of HTTP 1.1 to overcome curl => 7.62.0 where some operations are now sent via HTTP/2
|
|
||||||
// Whilst HTTP/2 operations are handled, in some cases the handling of this outside of the client is not being done correctly (router, other) thus the client breaks
|
|
||||||
// This flag then allows the user to downgrade all HTTP operations to HTTP 1.1 for maximum network path compatibility
|
|
||||||
bool forceHTTP11 = cfg.getValue("force_http_11") == "false" ? false : true;
|
|
||||||
// Local sync - Upload local changes first before downloading changes from OneDrive
|
|
||||||
bool localFirst = cfg.getValue("local_first") == "false" ? false : true;
|
|
||||||
// Add option for no remote delete
|
|
||||||
bool noRemoteDelete = cfg.getValue("no_remote_delete") == "false" ? false : true;
|
|
||||||
// Add option to skip symlinks
|
|
||||||
bool skipSymlinks = cfg.getValue("skip_symlinks") == "false" ? false : true;
|
|
||||||
// override the sync directory
|
|
||||||
string syncDirName = cfg.getValue("sync_dir");
|
|
||||||
// Upload Only
|
|
||||||
bool uploadOnly = cfg.getValue("upload_only") == "false" ? false : true;
|
|
||||||
|
|
||||||
|
|
||||||
// Application Startup option validation
|
// Application Startup option validation
|
||||||
try {
|
try {
|
||||||
auto opt = getopt(
|
auto opt = getopt(
|
||||||
args,
|
args,
|
||||||
std.getopt.config.bundling,
|
std.getopt.config.bundling,
|
||||||
std.getopt.config.caseSensitive,
|
std.getopt.config.caseSensitive,
|
||||||
"check-for-nomount", "Check for the presence of .nosync in the syncdir root. If found, do not perform sync.", &checkMount,
|
std.getopt.config.passThrough,
|
||||||
"debug-https", "Debug OneDrive HTTPS communication.", &debugHttp,
|
"check-for-nomount",
|
||||||
"disable-notifications", "Do not use desktop notifications in monitor mode.", &disableNotifications,
|
"Check for the presence of .nosync in the syncdir root. If found, do not perform sync.",
|
||||||
"download-only|d", "Only download remote changes", &downloadOnly,
|
&boolValues["check_for_nomount"],
|
||||||
"disable-upload-validation", "Disable upload validation when uploading to OneDrive", &disableUploadValidation,
|
"debug-https",
|
||||||
"enable-logging", "Enable client activity to a separate log file", &enableLogFile,
|
"Debug OneDrive HTTPS communication.",
|
||||||
"force-http-1.1", "Force the use of HTTP 1.1 for all operations", &forceHTTP11,
|
&boolValues["debug_https"],
|
||||||
"local-first", "Synchronize from the local directory source first, before downloading changes from OneDrive.", &localFirst,
|
"disable-notifications",
|
||||||
"no-remote-delete", "Do not delete local file 'deletes' from OneDrive when using --upload-only", &noRemoteDelete,
|
"Do not use desktop notifications in monitor mode.",
|
||||||
"skip-symlinks", "Skip syncing of symlinks", &skipSymlinks,
|
&boolValues["disable_notifications"],
|
||||||
"syncdir", "Specify the local directory used for synchronization to OneDrive", &syncDirName,
|
"download-only|d",
|
||||||
"upload-only", "Only upload to OneDrive, do not sync changes from OneDrive locally", &uploadOnly,
|
"Only download remote changes",
|
||||||
|
&boolValues["download_only"],
|
||||||
|
"disable-upload-validation",
|
||||||
|
"Disable upload validation when uploading to OneDrive",
|
||||||
|
&boolValues["disable_upload_validation"],
|
||||||
|
"enable-logging",
|
||||||
|
"Enable client activity to a separate log file",
|
||||||
|
&boolValues["enable_logging"],
|
||||||
|
"force-http-1.1",
|
||||||
|
"Force the use of HTTP 1.1 for all operations",
|
||||||
|
&boolValues["force_http_11"],
|
||||||
|
"local-first",
|
||||||
|
"Synchronize from the local directory source first, before downloading changes from OneDrive.",
|
||||||
|
&boolValues["local_first"],
|
||||||
|
"no-remote-delete",
|
||||||
|
"Do not delete local file 'deletes' from OneDrive when using --upload-only",
|
||||||
|
&boolValues["no_remote_delete"],
|
||||||
|
"skip-symlinks",
|
||||||
|
"Skip syncing of symlinks",
|
||||||
|
&boolValues["skip_symlinks"],
|
||||||
|
"syncdir",
|
||||||
|
"Specify the local directory used for synchronization to OneDrive",
|
||||||
|
&stringValues["sync_dir"],
|
||||||
|
"upload-only",
|
||||||
|
"Only upload to OneDrive, do not sync changes from OneDrive locally",
|
||||||
|
&boolValues["upload_only"],
|
||||||
|
"verbose|v+",
|
||||||
|
"Print more details, useful for debugging (repeat for extra debugging)",
|
||||||
|
&longValues["verbose"]
|
||||||
);
|
);
|
||||||
if (opt.helpWanted) {
|
return opt.options;
|
||||||
outputLongHelp(opt.options);
|
|
||||||
return EXIT_SUCCESS;
|
|
||||||
}
|
|
||||||
} catch (GetOptException e) {
|
} catch (GetOptException e) {
|
||||||
log.error(e.msg);
|
log.error(e.msg);
|
||||||
log.error("Try 'onedrive -h' for more information");
|
log.error("Try 'onedrive -h' for more information");
|
||||||
return EXIT_FAILURE;
|
exit(EXIT_FAILURE);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
// error
|
// error
|
||||||
log.error(e.msg);
|
log.error(e.msg);
|
||||||
log.error("Try 'onedrive -h' for more information");
|
log.error("Try 'onedrive -h' for more information");
|
||||||
return EXIT_FAILURE;
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string getValue(string key)
|
string getValueString(string key)
|
||||||
{
|
{
|
||||||
auto p = key in values;
|
auto p = key in stringValues;
|
||||||
if (p) {
|
if (p) {
|
||||||
return *p;
|
return *p;
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,19 +200,39 @@ final class Config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string getValue(string key, string value)
|
long getValueLong(string key)
|
||||||
{
|
{
|
||||||
auto p = key in values;
|
auto p = key in longValues;
|
||||||
if (p) {
|
if (p) {
|
||||||
return *p;
|
return *p;
|
||||||
} else {
|
} else {
|
||||||
return value;
|
throw new Exception("Missing config value: " ~ key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void setValue(string key, string value)
|
bool getValueBool(string key)
|
||||||
{
|
{
|
||||||
values[key] = value;
|
auto p = key in boolValues;
|
||||||
|
if (p) {
|
||||||
|
return *p;
|
||||||
|
} else {
|
||||||
|
throw new Exception("Missing config value: " ~ key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValueBool(string key, bool value)
|
||||||
|
{
|
||||||
|
boolValues[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValueString(string key, string value)
|
||||||
|
{
|
||||||
|
stringValues[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setValueLong(string key, long value)
|
||||||
|
{
|
||||||
|
longValues[key] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool load(string filename)
|
private bool load(string filename)
|
||||||
|
@ -221,15 +247,27 @@ final class Config
|
||||||
if (!c.empty) {
|
if (!c.empty) {
|
||||||
c.popFront(); // skip the whole match
|
c.popFront(); // skip the whole match
|
||||||
string key = c.front.dup;
|
string key = c.front.dup;
|
||||||
auto p = key in values;
|
auto p = key in boolValues;
|
||||||
if (p) {
|
if (p) {
|
||||||
c.popFront();
|
c.popFront();
|
||||||
// TODO add check for correct format (numbers, booleans)
|
// only accept "true" as true value. TODO Should we support other formats?
|
||||||
setValue(key, c.front.dup);
|
setValueBool(key, c.front.dup == "true" ? true : false);
|
||||||
|
} else {
|
||||||
|
auto pp = key in stringValues;
|
||||||
|
if (pp) {
|
||||||
|
c.popFront();
|
||||||
|
setValueString(key, c.front.dup);
|
||||||
|
} else {
|
||||||
|
auto ppp = key in longValues;
|
||||||
|
if (ppp) {
|
||||||
|
c.popFront();
|
||||||
|
setValueLong(key, to!long(c.front.dup));
|
||||||
} else {
|
} else {
|
||||||
log.log("Unknown key in config file: ", key);
|
log.log("Unknown key in config file: ", key);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
log.log("Malformed config line: ", line);
|
log.log("Malformed config line: ", line);
|
||||||
return false;
|
return false;
|
||||||
|
@ -243,6 +281,5 @@ unittest
|
||||||
{
|
{
|
||||||
auto cfg = new Config("");
|
auto cfg = new Config("");
|
||||||
cfg.load("config");
|
cfg.load("config");
|
||||||
assert(cfg.getValue("sync_dir") == "~/OneDrive");
|
assert(cfg.getValueString("sync_dir") == "~/OneDrive");
|
||||||
assert(cfg.getValue("empty", "default") == "default");
|
|
||||||
}
|
}
|
||||||
|
|
140
src/main.d
140
src/main.d
|
@ -1,6 +1,7 @@
|
||||||
import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE, exit;
|
import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE, exit;
|
||||||
import core.memory, core.time, core.thread;
|
import core.memory, core.time, core.thread;
|
||||||
import std.getopt, std.file, std.path, std.process, std.stdio, std.conv, std.algorithm.searching, std.string;
|
import std.getopt, std.file, std.path, std.process, std.stdio, std.conv, std.algorithm.searching, std.string;
|
||||||
|
import std.algorithm.sorting: sort;
|
||||||
import config, itemdb, monitor, onedrive, selective, sync, util;
|
import config, itemdb, monitor, onedrive, selective, sync, util;
|
||||||
import std.net.curl: CurlException;
|
import std.net.curl: CurlException;
|
||||||
import core.stdc.signal;
|
import core.stdc.signal;
|
||||||
|
@ -18,6 +19,10 @@ int main(string[] args)
|
||||||
// configuration directory
|
// configuration directory
|
||||||
string configDirName;
|
string configDirName;
|
||||||
|
|
||||||
|
Option[] savedOpts;
|
||||||
|
|
||||||
|
bool helpWanted = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// print the version and exit
|
// print the version and exit
|
||||||
bool printVersion = false;
|
bool printVersion = false;
|
||||||
|
@ -29,10 +34,10 @@ int main(string[] args)
|
||||||
"confdir", "Set the directory used to store the configuration files", &configDirName,
|
"confdir", "Set the directory used to store the configuration files", &configDirName,
|
||||||
"version", "Print the version and exit", &printVersion
|
"version", "Print the version and exit", &printVersion
|
||||||
);
|
);
|
||||||
// TODO deal with delayed help output!!!
|
|
||||||
if (opt.helpWanted) {
|
if (opt.helpWanted) {
|
||||||
outputLongHelp(opt.options);
|
args ~= "--help";
|
||||||
return EXIT_SUCCESS;
|
helpWanted = true;
|
||||||
|
savedOpts ~= opt.options;
|
||||||
}
|
}
|
||||||
if (printVersion) {
|
if (printVersion) {
|
||||||
std.stdio.write("onedrive ", import("version"));
|
std.stdio.write("onedrive ", import("version"));
|
||||||
|
@ -56,43 +61,27 @@ int main(string[] args)
|
||||||
// Error message already printed
|
// Error message already printed
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
// update configuration from command line args
|
||||||
|
savedOpts ~= cfg.update_from_args(args);
|
||||||
|
|
||||||
|
//
|
||||||
|
// parse the remaining arguments that do not have a corresponding config file entry
|
||||||
|
|
||||||
// Application Option Variables
|
// Application Option Variables
|
||||||
// Create a single root directory on OneDrive
|
// Create a single root directory on OneDrive
|
||||||
string createDirectory;
|
string createDirectory;
|
||||||
// The destination directory if we are using the OneDrive client to rename a directory
|
// The destination directory if we are using the OneDrive client to rename a directory
|
||||||
string destinationDirectory;
|
string destinationDirectory;
|
||||||
// Debug the HTTPS submit operations if required
|
|
||||||
bool debugHttp = cfg.getValue("debug_https") == "false" ? false : true;
|
|
||||||
// Do not use notifications in monitor mode
|
|
||||||
bool disableNotifications = cfg.getValue("disable_notifications") == "false" ? false : true;
|
|
||||||
// Display application configuration but do not sync
|
// Display application configuration but do not sync
|
||||||
bool displayConfiguration = false;
|
bool displayConfiguration = false;
|
||||||
// Display sync status
|
// Display sync status
|
||||||
bool displaySyncStatus = false;
|
bool displaySyncStatus = false;
|
||||||
// only download remote changes
|
|
||||||
bool downloadOnly = cfg.getValue("download_only") == "false" ? false : true;
|
|
||||||
// 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 = cfg.getValue("disable_upload_validation") == "false" ? false : true;
|
|
||||||
// Do we enable a log file
|
|
||||||
bool enableLogFile = cfg.getValue("enable_logging") == "false" ? false : true;
|
|
||||||
// Force the use of HTTP 1.1 to overcome curl => 7.62.0 where some operations are now sent via HTTP/2
|
|
||||||
// Whilst HTTP/2 operations are handled, in some cases the handling of this outside of the client is not being done correctly (router, other) thus the client breaks
|
|
||||||
// This flag then allows the user to downgrade all HTTP operations to HTTP 1.1 for maximum network path compatibility
|
|
||||||
bool forceHTTP11 = cfg.getValue("force_http_11") == "false" ? false : true;
|
|
||||||
// SharePoint / Office 365 Shared Library name to query
|
// SharePoint / Office 365 Shared Library name to query
|
||||||
string o365SharedLibraryName;
|
string o365SharedLibraryName;
|
||||||
// Local sync - Upload local changes first before downloading changes from OneDrive
|
|
||||||
bool localFirst = cfg.getValue("local_first") == "false" ? false : true;
|
|
||||||
// remove the current user and sync state
|
// remove the current user and sync state
|
||||||
bool logout = false;
|
bool logout = false;
|
||||||
// enable monitor mode
|
// enable monitor mode
|
||||||
bool monitor = false;
|
bool monitor = false;
|
||||||
// Add option for no remote delete
|
|
||||||
bool noRemoteDelete = cfg.getValue("no_remote_delete") == "false" ? false : true;
|
|
||||||
// print the access token
|
// print the access token
|
||||||
bool printAccessToken = false;
|
bool printAccessToken = false;
|
||||||
// force a full resync
|
// force a full resync
|
||||||
|
@ -101,55 +90,36 @@ int main(string[] args)
|
||||||
string removeDirectory;
|
string removeDirectory;
|
||||||
// This allows for selective directory syncing instead of everything under ~/OneDrive/
|
// This allows for selective directory syncing instead of everything under ~/OneDrive/
|
||||||
string singleDirectory;
|
string singleDirectory;
|
||||||
// Add option to skip symlinks
|
|
||||||
bool skipSymlinks = cfg.getValue("skip_symlinks") == "false" ? false : true;
|
|
||||||
// The source directory if we are using the OneDrive client to rename a directory
|
// The source directory if we are using the OneDrive client to rename a directory
|
||||||
string sourceDirectory;
|
string sourceDirectory;
|
||||||
// override the sync directory
|
|
||||||
string syncDirName = cfg.getValue("sync_dir");
|
|
||||||
// Configure a flag to perform a sync
|
// 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
|
// 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 = false;
|
bool synchronize = false;
|
||||||
// Upload Only
|
|
||||||
bool uploadOnly = cfg.getValue("upload_only") == "false" ? false : true;
|
|
||||||
|
|
||||||
|
|
||||||
// Application Startup option validation
|
|
||||||
|
// Options without respective config file setting
|
||||||
try {
|
try {
|
||||||
auto opt = getopt(
|
auto opt = getopt(
|
||||||
args,
|
args,
|
||||||
std.getopt.config.bundling,
|
std.getopt.config.bundling,
|
||||||
std.getopt.config.caseSensitive,
|
std.getopt.config.caseSensitive,
|
||||||
"check-for-nomount", "Check for the presence of .nosync in the syncdir root. If found, do not perform sync.", &checkMount,
|
|
||||||
"confdir", "Set the directory used to store the configuration files", &configDirName,
|
|
||||||
"create-directory", "Create a directory on OneDrive - no sync will be performed.", &createDirectory,
|
"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,
|
"destination-directory", "Destination directory for renamed or move on OneDrive - no sync will be performed.", &destinationDirectory,
|
||||||
"debug-https", "Debug OneDrive HTTPS communication.", &debugHttp,
|
|
||||||
"disable-notifications", "Do not use desktop notifications in monitor mode.", &disableNotifications,
|
|
||||||
"display-config", "Display what options the client will use as currently configured - no sync will be performed.", &displayConfiguration,
|
"display-config", "Display what options the client will use as currently configured - no sync will be performed.", &displayConfiguration,
|
||||||
"display-sync-status", "Display the sync status of the client - no sync will be performed.", &displaySyncStatus,
|
"display-sync-status", "Display the sync status of the client - no sync will be performed.", &displaySyncStatus,
|
||||||
"download-only|d", "Only download remote changes", &downloadOnly,
|
|
||||||
"disable-upload-validation", "Disable upload validation when uploading to OneDrive", &disableUploadValidation,
|
|
||||||
"enable-logging", "Enable client activity to a separate log file", &enableLogFile,
|
|
||||||
"force-http-1.1", "Force the use of HTTP 1.1 for all operations", &forceHTTP11,
|
|
||||||
"get-O365-drive-id", "Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library", &o365SharedLibraryName,
|
"get-O365-drive-id", "Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library", &o365SharedLibraryName,
|
||||||
"local-first", "Synchronize from the local directory source first, before downloading changes from OneDrive.", &localFirst,
|
|
||||||
"logout", "Logout the current user", &logout,
|
"logout", "Logout the current user", &logout,
|
||||||
"monitor|m", "Keep monitoring for local and remote changes", &monitor,
|
"monitor|m", "Keep monitoring for local and remote changes", &monitor,
|
||||||
"no-remote-delete", "Do not delete local file 'deletes' from OneDrive when using --upload-only", &noRemoteDelete,
|
|
||||||
"print-token", "Print the access token, useful for debugging", &printAccessToken,
|
"print-token", "Print the access token, useful for debugging", &printAccessToken,
|
||||||
"resync", "Forget the last saved state, perform a full sync", &resync,
|
"resync", "Forget the last saved state, perform a full sync", &resync,
|
||||||
"remove-directory", "Remove a directory on OneDrive - no sync will be performed.", &removeDirectory,
|
"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,
|
"single-directory", "Specify a single local directory within the OneDrive root to sync.", &singleDirectory,
|
||||||
"skip-symlinks", "Skip syncing of symlinks", &skipSymlinks,
|
|
||||||
"source-directory", "Source directory to rename or move on OneDrive - no sync will be performed.", &sourceDirectory,
|
"source-directory", "Source directory to rename or move on OneDrive - no sync will be performed.", &sourceDirectory,
|
||||||
"syncdir", "Specify the local directory used for synchronization to OneDrive", &syncDirName,
|
|
||||||
"synchronize", "Perform a synchronization", &synchronize,
|
"synchronize", "Perform a synchronization", &synchronize,
|
||||||
"upload-only", "Only upload to OneDrive, do not sync changes from OneDrive locally", &uploadOnly,
|
|
||||||
"verbose|v+", "Print more details, useful for debugging (repeat for extra debugging)", &log.verbose,
|
|
||||||
);
|
);
|
||||||
if (opt.helpWanted) {
|
if (opt.helpWanted) {
|
||||||
outputLongHelp(opt.options);
|
outputLongHelp(opt.options ~ savedOpts);
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
} catch (GetOptException e) {
|
} catch (GetOptException e) {
|
||||||
|
@ -163,24 +133,10 @@ int main(string[] args)
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Are we able to reach the OneDrive Service
|
// Are we able to reach the OneDrive Service
|
||||||
bool online = false;
|
bool online = false;
|
||||||
|
|
||||||
// command line parameters to override default 'config' & take precedence
|
|
||||||
// Set the client to skip symbolic links if --skip-symlinks was passed in
|
|
||||||
if (skipSymlinks) {
|
|
||||||
// The user passed in an alternate skip_symlinks as to what was either in 'config' file or application default
|
|
||||||
log.vdebug("CLI override to set skip_symlinks to: true");
|
|
||||||
cfg.setValue("skip_symlinks", "true");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the OneDrive Local Sync Directory if was passed in via --syncdir
|
|
||||||
if (syncDirName) {
|
|
||||||
// The user passed in an alternate sync_dir as to what was either in 'config' file or application default
|
|
||||||
// Do not expandTilde here as we do not know if we reliably can
|
|
||||||
log.vdebug("CLI override to set sync_dir to: ", syncDirName);
|
|
||||||
cfg.setValue("sync_dir", syncDirName);
|
|
||||||
}
|
|
||||||
|
|
||||||
// sync_dir environment handling to handle ~ expansion properly
|
// sync_dir environment handling to handle ~ expansion properly
|
||||||
string syncDir;
|
string syncDir;
|
||||||
|
@ -188,23 +144,23 @@ int main(string[] args)
|
||||||
log.vdebug("sync_dir: No SHELL or USER environment variable configuration detected");
|
log.vdebug("sync_dir: No SHELL or USER environment variable configuration detected");
|
||||||
// No shell or user set, so expandTilde() will fail - usually headless system running under init.d / systemd or potentially Docker
|
// No shell or user set, so expandTilde() will fail - usually headless system running under init.d / systemd or potentially Docker
|
||||||
// Does the 'currently configured' sync_dir include a ~
|
// Does the 'currently configured' sync_dir include a ~
|
||||||
if (canFind(cfg.getValue("sync_dir"),"~")) {
|
if (canFind(cfg.getValueString("sync_dir"), "~")) {
|
||||||
// A ~ was found
|
// A ~ was found
|
||||||
log.vdebug("sync_dir: A '~' was found in sync_dir, using the calculated 'homePath' to replace '~'");
|
log.vdebug("sync_dir: A '~' was found in sync_dir, using the calculated 'homePath' to replace '~'");
|
||||||
syncDir = cfg.homePath ~ strip(cfg.getValue("sync_dir"),"~","~");
|
syncDir = cfg.homePath ~ strip(cfg.getValueString("sync_dir"), "~");
|
||||||
} else {
|
} else {
|
||||||
// No ~ found in sync_dir, use as is
|
// No ~ found in sync_dir, use as is
|
||||||
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
||||||
syncDir = cfg.getValue("sync_dir");
|
syncDir = cfg.getValueString("sync_dir");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// A shell and user is set, expand any ~ as this will be expanded correctly if present
|
// A shell and user is set, expand any ~ as this will be expanded correctly if present
|
||||||
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
||||||
if (canFind(cfg.getValue("sync_dir"),"~")) {
|
if (canFind(cfg.getValueString("sync_dir"), "~")) {
|
||||||
log.vdebug("sync_dir: A '~' was found in configured sync_dir, automatically expanding as SHELL and USER environment variable is set");
|
log.vdebug("sync_dir: A '~' was found in configured sync_dir, automatically expanding as SHELL and USER environment variable is set");
|
||||||
syncDir = expandTilde(cfg.getValue("sync_dir"));
|
syncDir = expandTilde(cfg.getValueString("sync_dir"));
|
||||||
} else {
|
} else {
|
||||||
syncDir = cfg.getValue("sync_dir");
|
syncDir = cfg.getValueString("sync_dir");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -212,15 +168,15 @@ int main(string[] args)
|
||||||
log.vdebug("syncDir: ", syncDir);
|
log.vdebug("syncDir: ", syncDir);
|
||||||
|
|
||||||
// Configure logging if enabled
|
// Configure logging if enabled
|
||||||
if (enableLogFile){
|
if (cfg.getValueBool("enable_logging")){
|
||||||
// Read in a user defined log directory or use the default
|
// Read in a user defined log directory or use the default
|
||||||
string logDir = cfg.getValue("log_dir");
|
string logDir = cfg.getValueString("log_dir");
|
||||||
log.vlog("Using logfile dir: ", logDir);
|
log.vlog("Using logfile dir: ", logDir);
|
||||||
log.init(logDir);
|
log.init(logDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure whether notifications are used
|
// Configure whether notifications are used
|
||||||
log.setNotifications(monitor && !disableNotifications);
|
log.setNotifications(monitor && !cfg.getValueBool("disable_notifications"));
|
||||||
|
|
||||||
// upgrades
|
// upgrades
|
||||||
if (exists(configDirName ~ "/items.db")) {
|
if (exists(configDirName ~ "/items.db")) {
|
||||||
|
@ -257,15 +213,15 @@ int main(string[] args)
|
||||||
|
|
||||||
// Config Options
|
// Config Options
|
||||||
writeln("Config option 'sync_dir' = ", syncDir);
|
writeln("Config option 'sync_dir' = ", syncDir);
|
||||||
writeln("Config option 'skip_file' = ", cfg.getValue("skip_file"));
|
writeln("Config option 'skip_file' = ", cfg.getValueString("skip_file"));
|
||||||
writeln("Config option 'skip_symlinks' = ", cfg.getValue("skip_symlinks"));
|
writeln("Config option 'skip_symlinks' = ", cfg.getValueBool("skip_symlinks"));
|
||||||
writeln("Config option 'monitor_interval' = ", cfg.getValue("monitor_interval"));
|
writeln("Config option 'monitor_interval' = ", cfg.getValueLong("monitor_interval"));
|
||||||
writeln("Config option 'min_notif_changes' = ", cfg.getValue("min_notif_changes"));
|
writeln("Config option 'min_notif_changes' = ", cfg.getValueLong("min_notif_changes"));
|
||||||
writeln("Config option 'log_dir' = ", cfg.getValue("log_dir"));
|
writeln("Config option 'log_dir' = ", cfg.getValueString("log_dir"));
|
||||||
|
|
||||||
// Is config option drive_id configured?
|
// Is config option drive_id configured?
|
||||||
if (cfg.getValue("drive_id", "") != ""){
|
if (cfg.getValueString("drive_id") != ""){
|
||||||
writeln("Config option 'drive_id' = ", cfg.getValue("drive_id"));
|
writeln("Config option 'drive_id' = ", cfg.getValueString("drive_id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is sync_list configured?
|
// Is sync_list configured?
|
||||||
|
@ -298,7 +254,7 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize OneDrive, check for authorization
|
// Initialize OneDrive, check for authorization
|
||||||
oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11);
|
oneDrive = new OneDriveApi(cfg);
|
||||||
oneDrive.printAccessToken = printAccessToken;
|
oneDrive.printAccessToken = printAccessToken;
|
||||||
if (!oneDrive.init()) {
|
if (!oneDrive.init()) {
|
||||||
log.error("Could not initialize the OneDrive API");
|
log.error("Could not initialize the OneDrive API");
|
||||||
|
@ -351,7 +307,7 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectiveSync.load(cfg.syncListFilePath);
|
selectiveSync.load(cfg.syncListFilePath);
|
||||||
selectiveSync.setMask(cfg.getValue("skip_file"));
|
selectiveSync.setMask(cfg.getValueString("skip_file"));
|
||||||
|
|
||||||
// Initialise the sync engine
|
// Initialise the sync engine
|
||||||
log.logAndNotify("Initializing the Synchronization Engine ...");
|
log.logAndNotify("Initializing the Synchronization Engine ...");
|
||||||
|
@ -371,13 +327,13 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should only set noRemoteDelete in an upload-only scenario
|
// We should only set noRemoteDelete in an upload-only scenario
|
||||||
if ((uploadOnly)&&(noRemoteDelete)) sync.setNoRemoteDelete();
|
if ((cfg.getValueBool("upload_only"))&&(cfg.getValueBool("no_remote_delete"))) sync.setNoRemoteDelete();
|
||||||
|
|
||||||
// Do we configure to disable the upload validation routine
|
// Do we configure to disable the upload validation routine
|
||||||
if(disableUploadValidation) sync.setDisableUploadValidation();
|
if (cfg.getValueBool("disable_upload_validation")) sync.setDisableUploadValidation();
|
||||||
|
|
||||||
// Do we need to validate the syncDir to check for the presence of a '.nosync' file
|
// Do we need to validate the syncDir to check for the presence of a '.nosync' file
|
||||||
if (cfg.getValue("check_for_nomount") == "true") {
|
if (cfg.getValueBool("check_for_nomount")) {
|
||||||
// we were asked to check the mounts
|
// we were asked to check the mounts
|
||||||
if (exists(syncDir ~ "/.nosync")) {
|
if (exists(syncDir ~ "/.nosync")) {
|
||||||
log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data.");
|
log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data.");
|
||||||
|
@ -442,13 +398,13 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the sync
|
// Perform the sync
|
||||||
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly, monitor);
|
performSync(sync, singleDirectory, cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), monitor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor) {
|
if (monitor) {
|
||||||
log.logAndNotify("Initializing monitor ...");
|
log.logAndNotify("Initializing monitor ...");
|
||||||
log.log("OneDrive monitor interval (seconds): ", to!long(cfg.getValue("monitor_interval")));
|
log.log("OneDrive monitor interval (seconds): ", cfg.getValueLong("monitor_interval"));
|
||||||
Monitor m = new Monitor(selectiveSync);
|
Monitor m = new Monitor(selectiveSync);
|
||||||
m.onDirCreated = delegate(string path) {
|
m.onDirCreated = delegate(string path) {
|
||||||
log.vlog("[M] Directory created: ", path);
|
log.vlog("[M] Directory created: ", path);
|
||||||
|
@ -500,13 +456,12 @@ int main(string[] args)
|
||||||
signal(SIGTERM, &exitHandler);
|
signal(SIGTERM, &exitHandler);
|
||||||
|
|
||||||
// initialise the monitor class
|
// initialise the monitor class
|
||||||
if (cfg.getValue("skip_symlinks") == "true") skipSymlinks = true;
|
if (!cfg.getValueBool("download_only")) m.init(cfg, cfg.getValueLong("verbose") > 0, cfg.getValueBool("skip_symlinks"));
|
||||||
if (!downloadOnly) m.init(cfg, verbose > 0, skipSymlinks);
|
|
||||||
// monitor loop
|
// monitor loop
|
||||||
immutable auto checkInterval = dur!"seconds"(to!long(cfg.getValue("monitor_interval")));
|
immutable auto checkInterval = dur!"seconds"(cfg.getValueLong("monitor_interval"));
|
||||||
auto lastCheckTime = MonoTime.currTime();
|
auto lastCheckTime = MonoTime.currTime();
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!downloadOnly) m.update(online);
|
if (!cfg.getValueBool("download_only")) m.update(online);
|
||||||
auto currTime = MonoTime.currTime();
|
auto currTime = MonoTime.currTime();
|
||||||
if (currTime - lastCheckTime > checkInterval) {
|
if (currTime - lastCheckTime > checkInterval) {
|
||||||
// log.logAndNotify("DEBUG trying to create checkpoint");
|
// log.logAndNotify("DEBUG trying to create checkpoint");
|
||||||
|
@ -519,8 +474,8 @@ int main(string[] args)
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly, monitor);
|
performSync(sync, singleDirectory, cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), monitor);
|
||||||
if (!downloadOnly) {
|
if (!cfg.getValueBool("download_only")) {
|
||||||
// discard all events that may have been generated by the sync
|
// discard all events that may have been generated by the sync
|
||||||
m.update(false);
|
m.update(false);
|
||||||
}
|
}
|
||||||
|
@ -696,12 +651,15 @@ Usage:
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
`);
|
`);
|
||||||
foreach (it; opt) {
|
foreach (it; opt.sort!("a.optLong < b.optLong")) {
|
||||||
|
if (it.optLong == "--help") continue;
|
||||||
writefln(" %s%s%s%s\n %s",
|
writefln(" %s%s%s%s\n %s",
|
||||||
it.optShort == "" ? "" : it.optShort ~ " ",
|
|
||||||
it.optLong,
|
it.optLong,
|
||||||
|
it.optShort == "" ? "" : " " ~ it.optShort,
|
||||||
argsNeedingOptions.canFind(it.optLong) ? " ARG" : "",
|
argsNeedingOptions.canFind(it.optLong) ? " ARG" : "",
|
||||||
it.required ? " (required)" : "", it.help);
|
it.required ? " (required)" : "", it.help);
|
||||||
}
|
}
|
||||||
|
// write help last
|
||||||
|
writefln(" --help -h\n This help information.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ final class OneDriveApi
|
||||||
// if true, every new access token is printed
|
// if true, every new access token is printed
|
||||||
bool printAccessToken;
|
bool printAccessToken;
|
||||||
|
|
||||||
this(Config cfg, bool debugHttp, bool forceHTTP11)
|
this(Config cfg)
|
||||||
{
|
{
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
http = HTTP();
|
http = HTTP();
|
||||||
|
@ -92,14 +92,14 @@ final class OneDriveApi
|
||||||
http.maxRedirects(5);
|
http.maxRedirects(5);
|
||||||
|
|
||||||
// Do we enable curl debugging?
|
// Do we enable curl debugging?
|
||||||
if (debugHttp) {
|
if (cfg.getValueBool("debug_https")) {
|
||||||
http.verbose = true;
|
http.verbose = true;
|
||||||
.debugResponse = true;
|
.debugResponse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// What version of HTTP protocol do we use?
|
// What version of HTTP protocol do we use?
|
||||||
// Curl >= 7.62.0 defaults to http2 for a significant number of operations
|
// Curl >= 7.62.0 defaults to http2 for a significant number of operations
|
||||||
if (forceHTTP11) {
|
if (cfg.getValueBool("force_http_11")) {
|
||||||
log.vdebug("Downgrading all HTTP operations to HTTP 1.1");
|
log.vdebug("Downgrading all HTTP operations to HTTP 1.1");
|
||||||
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
|
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
|
||||||
http.handle.set(CurlOption.http_version,2);
|
http.handle.set(CurlOption.http_version,2);
|
||||||
|
@ -109,7 +109,7 @@ final class OneDriveApi
|
||||||
bool init()
|
bool init()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
driveId = cfg.getValue("drive_id");
|
driveId = cfg.getValueString("drive_id");
|
||||||
if (driveId.length) {
|
if (driveId.length) {
|
||||||
driveUrl = driveByIdUrl ~ driveId;
|
driveUrl = driveByIdUrl ~ driveId;
|
||||||
itemByIdUrl = driveUrl ~ "/items";
|
itemByIdUrl = driveUrl ~ "/items";
|
||||||
|
|
|
@ -219,7 +219,7 @@ final class SyncEngine
|
||||||
// OneDrive responded with 400 error: Bad Request
|
// OneDrive responded with 400 error: Bad Request
|
||||||
log.error("\nERROR: OneDrive returned a 'HTTP 400 Bad Request' - Cannot Initialize Sync Engine");
|
log.error("\nERROR: OneDrive returned a 'HTTP 400 Bad Request' - Cannot Initialize Sync Engine");
|
||||||
// Check this
|
// Check this
|
||||||
if (cfg.getValue("drive_id").length) {
|
if (cfg.getValueString("drive_id").length) {
|
||||||
log.error("ERROR: Check your 'drive_id' entry in your configuration file as it may be incorrect\n");
|
log.error("ERROR: Check your 'drive_id' entry in your configuration file as it may be incorrect\n");
|
||||||
}
|
}
|
||||||
// Must exit here
|
// Must exit here
|
||||||
|
@ -582,7 +582,7 @@ final class SyncEngine
|
||||||
if (("value" in changes) != null) {
|
if (("value" in changes) != null) {
|
||||||
auto nrChanges = count(changes["value"].array);
|
auto nrChanges = count(changes["value"].array);
|
||||||
|
|
||||||
if (nrChanges >= to!long(cfg.getValue("min_notif_changes"))) {
|
if (nrChanges >= cfg.getValueLong("min_notif_changes")) {
|
||||||
log.logAndNotify("Processing ", nrChanges, " changes");
|
log.logAndNotify("Processing ", nrChanges, " changes");
|
||||||
} else {
|
} else {
|
||||||
// There are valid changes
|
// There are valid changes
|
||||||
|
@ -1314,7 +1314,7 @@ final class SyncEngine
|
||||||
|
|
||||||
if (isSymlink(path)) {
|
if (isSymlink(path)) {
|
||||||
// if config says so we skip all symlinked items
|
// if config says so we skip all symlinked items
|
||||||
if (cfg.getValue("skip_symlinks") == "true") {
|
if (cfg.getValueBool("skip_symlinks")) {
|
||||||
log.vlog("Skipping item - skip symbolic links configured: ", path);
|
log.vlog("Skipping item - skip symbolic links configured: ", path);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue