config options for command line switches (#449)

* config options for command line switches to allow for better config handling in docker containers
This commit is contained in:
Norbert Preining 2019-04-11 11:26:20 +09:00 committed by abraunegg
parent e5a9747d7b
commit ea22d8fef5
8 changed files with 600 additions and 445 deletions

View file

@ -454,8 +454,12 @@ To re-authorise the client, follow the steps below:
The application will now sync with OneDrive with the new credentials.
## Additional Configuration
Additional configuration is optional.
## Configuration
Configuration is determined by three layer: the default values, values set in the configuration file, and values passed in via the command line. The default values provide a reasonable default, and configuration is optionally.
Most command line options have a respective configuration file setting.
If you want to change the defaults, you can copy and edit the included config file into your `~/.config/onedrive` directory:
```text
mkdir -p ~/.config/onedrive
@ -464,13 +468,9 @@ nano ~/.config/onedrive/config
```
This file does not get created by default, and should only be created if you want to change the 'default' operational parameters.
Available options:
* `sync_dir`: directory where the files will be synced
* `skip_file`: any files or directories that match this pattern will be skipped during sync
* `skip_dotfiles`: skip any .files or .folders during sync
* `skip_symlinks`: any files or directories that are symlinked will be skipped during sync
* `monitor_interval`: time interval in seconds by which the monitor process will process local and remote changes
* `min_notif_changes`: minimum number of pending incoming changes to trigger a desktop notification
See the [config](config) file for the full list of options, and below under "All available commands" for all possible keys and there default values.
Comments regarding some of the options:
### sync_dir
Example: `sync_dir="~/MyDirToSync"`
@ -715,20 +715,20 @@ Options:
Set the directory used to store the configuration files
--create-directory ARG
Create a directory on OneDrive - no sync will be performed.
--destination-directory ARG
Destination directory for renamed or move on OneDrive - no sync will be performed.
--debug-https
Debug OneDrive HTTPS communication.
--destination-directory ARG
Destination directory for renamed or move on OneDrive - no sync will be performed.
--disable-notifications
Do not use desktop notifications in monitor mode.
--disable-upload-validation
Disable upload validation when uploading to OneDrive
--display-config
Display what options the client will use as currently configured - no sync will be performed.
--display-sync-status
Display the sync status of the client - no sync will be performed.
-d --download-only
--download-only -d
Only download remote changes
--disable-upload-validation
Disable upload validation when uploading to OneDrive
--dry-run
Perform a trial sync with no changes made
--enable-logging
@ -737,24 +737,38 @@ Options:
Force the use of HTTP 1.1 for all operations
--get-O365-drive-id ARG
Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library
--help -h
This help information.
--local-first
Synchronize from the local directory source first, before downloading changes from OneDrive.
--log-dir ARG
Directory where logging output is saved to, needs to end with a slash.
--logout
Logout the current user
-m --monitor
--min-notif-changes ARG
Minimum number of pending incoming changes necessary to trigger a desktop notification
--monitor -m
Keep monitoring for local and remote changes
--monitor-fullscan-frequency ARG
Number of sync runs before performing a full local scan of the synced directory
--monitor-interval ARG
Number of seconds by which each sync operation is undertaken when idle under monitor mode.
--monitor-log-frequency ARG
Frequency of logging in monitor mode
--no-remote-delete
Do not delete local file 'deletes' from OneDrive when using --upload-only
--print-token
Print the access token, useful for debugging
--resync
Forget the last saved state, perform a full sync
--remove-directory ARG
Remove a directory on OneDrive - no sync will be performed.
--resync
Forget the last saved state, perform a full sync
--single-directory ARG
Specify a single local directory within the OneDrive root to sync.
--skip-dot-files
Skip dot files and folders from syncing
--skip-file ARG
Skip any files that match this pattern from syncing
--skip-symlinks
Skip syncing of symlinks
--source-directory ARG
@ -765,12 +779,10 @@ Options:
Perform a synchronization
--upload-only
Only upload to OneDrive, do not sync changes from OneDrive locally
-v+ --verbose
--verbose -v+
Print more details, useful for debugging (repeat for extra debugging)
--version
Print the version and exit
-h --help
This help information.
```
### File naming

36
config
View file

@ -1,6 +1,30 @@
# Directory where the files will be synced
sync_dir = "~/OneDrive"
# Skip files and directories that match this pattern
skip_file = "~*|.~*|*.tmp"
# Wait time (seconds) between sync operations in monitor mode
monitor_interval = "45"
# Configuration for OneDrive Linux Client
# This file contains the list of supported configuration fields
# with their default values.
# All values need to be enclosed in quotes
# For explanations see the README.md or the man page.
#
# sync_dir = "~/OneDrive"
# skip_file = "~*|.~*|*.tmp"
# monitor_interval = "45"
# skip_dir = ""
# log_dir = "/var/log/onedrive/"
# drive_id = ""
# upload_only = "false"
# check_nomount = "false"
# check_nosync = "false"
# download_only = "false"
# disable_notifications = "false"
# disable_upload_validation = "false"
# enable_logging = "false"
# force_http_11 = "false"
# local_first = "false"
# no_remote_delete = "false"
# skip_symlinks = "false"
# debug_https = "false"
# skip_dotfiles = "false"
# dry_run = "false"
# monitor_interval = "45"
# min_notif_changes = "5"
# monitor_log_frequency = "5"
# monitor_fullscan_frequency = "10"

View file

@ -20,9 +20,13 @@ Without any option given, no sync is done and the program exits.
.TP
\fB\-\-check\-for\-nomount\fP
Check for the presence of .nosync in the syncdir root. If found, do not perform sync.
.br
Configuration file key: \fBcheck_nomount\fP (default: \fBfalse\fP)
.TP
\fB\-\-check\-for\-nosync\fP
Check for the presence of .nosync in each directory. If found, skip directory from sync.
.br
Configuration file key: \fBcheck_nosync\fP (default: \fBfalse\fP)
.TP
\fB\-\-confdir\fP ARG
Set the directory used to store the configuration files
@ -35,12 +39,18 @@ Destination directory for renamed or move on OneDrive \- no sync will be perform
.TP
\fB\-\-debug\-https\fP
Debug OneDrive HTTPS communication.
.br
Configuration file key: \fBdebug_https\fP (default: \fBfalse\fP)
.TP
\fB\-\-disable\-notifications\fP
Do not use desktop notifications in monitor mode
.br
Configuration file key: \fBdisable_notifications\fP (default: \fBfalse\fP)
.TP
\fB\-\-disable\-upload\-validation\fP
Disable upload validation when uploading to OneDrive
.br
Configuration file key: \fBdisable_upload_validation\fP (default: \fBfalse\fP)
.TP
\fB\-\-display\-config\fP
Display what options the client will use as currently configured \- no sync will be performed.
@ -50,30 +60,64 @@ Display the sync status of the client \- no sync will be performed.
.TP
\fB\-d \-\-download\-only\fP
Only download remote changes
.br
Configuration file key: \fBdownload_only\fP (default: \fBfalse\fP)
.TP
\fB\-\-dry\-run\fP
Perform a trial sync with no changes made. Can ONLY be used with --synchronize. Will be ignored for --monitor
.br
Configuration file key: \fBdry_run\fP (default: \fBfalse\fP)
.TP
\fB\-\-enable\-logging\fP
Enable client activity to a separate log file
.br
Configuration file key: \fBenable_logging\fP (default: \fBfalse\fP)
.TP
\fB\-\-force\-http\-1.1\fP
Force the use of HTTP 1.1 for all operations
.br
Configuration file key: \fBforce_http_11\fP (default: \fBfalse\fP)
.TP
\fB\-\-get\-O365\-drive\-id\fP ARG
Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library
.TP
\fB\-\-local\-first\fP
Synchronize from the local directory source first, before downloading changes from OneDrive.
.br
Configuration file key: \fBlocal_first\fP (default: \fBfalse\fP)
.TP
\fB\-\-logout\fP
Logout the current user
.TP
\fB\-\-log\-dir\fP ARG
defines the directory where logging output is saved to, needs to end with a slash
.br
Configuration file key: \fBlog_dir\fP (default: \fB/var/log/onedrive/\fP)
.TP
\fB\-\-min-notif-changes\fP
the minimum number of pending incoming changes necessary to trigger
a desktop notification
.br
Configuration file key: \fBmin_notif_changes\fP (default: \fB5\fP)
.TP
\fB\-m \-\-monitor\fP
Keep monitoring for local and remote changes
.TP
\fB\-\-monitor\-interval\fP ARG
The number of seconds by which each sync operation is undertaken when
idle under monitor mode
.br
Configuration file key: \fBmonitor_interval\fP (default: \fB45\fP)
.TP
\fB\-\-monitor\-fullscan-frequency\fP ARG
Number of sync runs before performing a full local scan of the synced directory
.br
Configuration file key: \fBmonitor_fullscan_frequency\fP (default: \fB10\fP)
.TP
\fB\-\-no\-remote\-delete\fP
Do not delete local file 'deletes' from OneDrive when using \fB\-\-upload\-only\fR
.br
Configuration file key: \fBno_remote_delete\fP (default: \fBfalse\fP)
.TP
\fB\-\-print\-token\fP
Print the access token, useful for debugging
@ -89,21 +133,34 @@ Specify a single local directory within the OneDrive root to sync.
.TP
\fB\-\-skip\-dot\-files\fP
Skip dot files and folders from syncing
.br
Configuration file key: \fBskip_dotfiles\fP (default: \fBfalse\fP)
.TP
\fB\-\-skip\-file\fP
Skip any files that match this pattern from syncing
.br
Configuration file key: \fBskip_file\fP (default: \fB~*|.~*|*.tmp\fP)
.TP
\fB\-\-skip\-symlinks\fP
Skip syncing of symlinks
.br
Configuration file key: \fBskip_symlinks\fP (default: \fBfalse\fP)
.TP
\fB\-\-source\-directory\fP ARG
Source directory to rename or move on OneDrive \- no sync will be performed.
.TP
\fB\-\-syncdir\fP ARG
Set the directory used to sync the files that are synced
.br
Configuration file key: \fBsync_dir\fP (default: \fB~/OneDrive\fP)
.TP
\fB\-\-synchronize\fP
Perform a synchronization
.TP
\fB\-\-upload\-only\fP
Only upload to OneDrive, do not sync changes from OneDrive locally
.br
Configuration file key: \fBupload_only\fP (default: \fBfalse\fP)
.TP
\fB\-v \-\-verbose\fP
Print more details, useful for debugging. Given two times (or more)
@ -141,27 +198,8 @@ cp\ @DOCDIR@/config\ ~/.config/onedrive/config
\fP
.fi
Available options:
.TP
\fBsync_dir\fP
directory where the files will be synced
.TP
\fBskip_file\fP
any files that match this pattern will be skipped during sync
.TP
\fBskip_symlinks\fP
skip symbolic links during sync, defaults to \fB"false"\fP
.TP
\fBmonitor_interval\fP
the number of seconds by which each sync operation is undertaken when
idle under monitor mode, defaults to \fB"45"\fP
.TP
\fBmin_notif_changes\fP
the minimum number of pending incoming changes necessary to trigger
a desktop notification, defaults to \fB"5"\fP
.TP
\fBlog_dir\fP
defines the directory where logging output is saved to, needs to end with a slash
For the supported options see the above list of command line options
for the availability of a configuration key.
.PP
Pattern are case insensitive.
\fB*\fP and \fB?\fP wildcards characters are supported.

View file

@ -1,4 +1,6 @@
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 std.algorithm.sorting: sort;
import selective;
static import log;
@ -10,13 +12,108 @@ final class Config
public string databaseFilePathDryRun;
public string uploadStateFilePath;
public string syncListFilePath;
public string homePath;
public string configDirName;
private string userConfigFilePath;
// hashmap for the values found in the user config file
private string[string] values;
// ARGGGG D is stupid and cannot make hashmap initializations!!!
// private string[string] foobar = [ "aa": "bb" ] does NOT work!!!
private string[string] stringValues;
private bool[string] boolValues;
private long[string] longValues;
this(string configDirName)
this(string confdirOption)
{
// default configuration
stringValues["sync_dir"] = "~/OneDrive";
stringValues["skip_file"] = "~*|.~*|*.tmp";
stringValues["skip_dir"] = "";
stringValues["log_dir"] = "/var/log/onedrive/";
stringValues["drive_id"] = "";
boolValues["upload_only"] = false;
boolValues["check_nomount"] = false;
boolValues["check_nosync"] = 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;
boolValues["skip_dotfiles"] = false;
boolValues["dry_run"] = false;
longValues["verbose"] = log.verbose; // might be initialized by the first getopt call!
longValues["monitor_interval"] = 45,
longValues["min_notif_changes"] = 5;
longValues["monitor_log_frequency"] = 5;
// Number of n sync runs before performing a full local scan of sync_dir
// By default 10 which means every ~7.5 minutes a full disk scan of sync_dir will occur
longValues["monitor_fullscan_frequency"] = 10;
// 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
// Check for HOME environment variable
if (environment.get("HOME") != ""){
// Use HOME environment variable
log.vdebug("homePath: HOME environment variable set");
homePath = environment.get("HOME");
} else {
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
// No shell is set or username - observed case when running as systemd service under CentOS 7.x
log.vdebug("homePath: WARNING - no HOME environment variable set");
log.vdebug("homePath: WARNING - no SHELL environment variable set");
log.vdebug("homePath: WARNING - no USER environment variable set");
homePath = "/root";
} else {
// A shell & valid user is set, but no HOME is set, use ~ which can be expanded
log.vdebug("homePath: WARNING - no HOME environment variable set");
homePath = "~";
}
}
// Output homePath calculation
log.vdebug("homePath: ", homePath);
// Determine the correct configuration directory to use
string configDirBase;
if (confdirOption != "") {
// A CLI 'confdir' was passed in
log.vdebug("configDirName: CLI override to set configDirName to: ", confdirOption);
if (canFind(confdirOption,"~")) {
// A ~ was found
log.vdebug("configDirName: A '~' was found in configDirName, using the calculated 'homePath' to replace '~'");
configDirName = homePath ~ strip(confdirOption,"~","~");
} else {
configDirName = confdirOption;
}
} else {
// Determine the base directory relative to which user specific configuration files should be stored.
if (environment.get("XDG_CONFIG_HOME") != ""){
log.vdebug("configDirBase: XDG_CONFIG_HOME environment variable set");
configDirBase = environment.get("XDG_CONFIG_HOME");
} else {
// XDG_CONFIG_HOME does not exist on systems where X11 is not present - ie - headless systems / servers
log.vdebug("configDirBase: WARNING - no XDG_CONFIG_HOME environment variable set");
configDirBase = homePath ~ "/.config";
}
// Output configDirBase calculation
log.vdebug("configDirBase: ", configDirBase);
// Set the default application configuration directory
log.vdebug("configDirName: Configuring application to use default config path");
// configDirBase contains the correct path so we do not need to check for presence of '~'
configDirName = configDirBase ~ "/onedrive";
}
log.vlog("Using Config Dir: ", configDirName);
if (!exists(configDirName)) mkdirRecurse(configDirName);
refreshTokenFilePath = configDirName ~ "/refresh_token";
deltaLinkFilePath = configDirName ~ "/delta_link";
databaseFilePath = configDirName ~ "/items.sqlite3";
@ -26,39 +123,8 @@ final class Config
syncListFilePath = configDirName ~ "/sync_list";
}
bool init()
bool initialize()
{
// Default configuration directory
setValue("sync_dir", "~/OneDrive");
// Skip Directories - no directories are skipped by default
setValue("skip_dir", "");
// 'skilion' configuration was: .*|~*
// Skip files that start with ~
// Skip files that start with .~ (like .~lock.* files generated by LibreOffice)
// Skip files that end in .tmp
setValue("skip_file", "~*|.~*|*.tmp");
// By default skip dot files & folders are not skipped
setValue("skip_dotfiles", "false");
// By default symlinks are not skipped (using string type
// instead of boolean because hashmap only stores string types)
setValue("skip_symlinks", "false");
// Configure the monitor mode loop - the number of seconds by which
// each sync operation is undertaken when idle under monitor mode
setValue("monitor_interval", "45");
// Configure the default logging directory to be /var/log/onedrive/
setValue("log_dir", "/var/log/onedrive/");
// Configure a default empty value for drive_id
setValue("drive_id", "");
// Minimal changes that trigger a log and notification on sync
setValue("min_notif_changes", "5");
// Frequency of log messages in monitor, ie after n sync runs ship out a log message
setValue("monitor_log_frequency", "5");
// Number of n sync runs before performing a full local scan of sync_dir
// By default 10 which means every ~7.5 minutes a full disk scan of sync_dir will occur
setValue("monitor_fullscan_frequency", "10");
// Check if we should ignore a directory if a special file (.nosync) is present
setValue("check_nosync", "false");
if (!load(userConfigFilePath)) {
// What was the reason for failure?
if (!exists(userConfigFilePath)) {
@ -72,9 +138,169 @@ final class Config
return true;
}
string getValue(string key)
void update_from_args(string[] args)
{
auto p = key in values;
// Add additional options that are NOT configurable via config file
stringValues["create_directory"] = "";
stringValues["destination_directory"] = "";
stringValues["get_o365_drive_id"] = "";
stringValues["remove_directory"] = "";
stringValues["single_directory"] = "";
stringValues["source_directory"] = "";
boolValues["display_config"] = false;
boolValues["display_sync_status"] = false;
boolValues["resync"] = false;
boolValues["print_token"] = false;
boolValues["logout"] = false;
boolValues["monitor"] = false;
boolValues["synchronize"] = false;
// Application Startup option validation
try {
string tmpStr;
bool tmpBol;
long tmpVerb;
auto opt = getopt(
args,
std.getopt.config.bundling,
std.getopt.config.caseSensitive,
"check-for-nomount",
"Check for the presence of .nosync in the syncdir root. If found, do not perform sync.",
&boolValues["check_nomount"],
"check-for-nosync",
"Check for the presence of .nosync in each directory. If found, skip directory from sync.",
&boolValues["check_nosync"],
"create-directory",
"Create a directory on OneDrive - no sync will be performed.",
&stringValues["create_directory"],
"debug-https",
"Debug OneDrive HTTPS communication.",
&boolValues["debug_https"],
"destination-directory",
"Destination directory for renamed or move on OneDrive - no sync will be performed.",
&stringValues["destination_directory"],
"disable-notifications",
"Do not use desktop notifications in monitor mode.",
&boolValues["disable_notifications"],
"disable-upload-validation",
"Disable upload validation when uploading to OneDrive",
&boolValues["disable_upload_validation"],
"display-config",
"Display what options the client will use as currently configured - no sync will be performed.",
&boolValues["display_config"],
"display-sync-status",
"Display the sync status of the client - no sync will be performed.",
&boolValues["display_sync_status"],
"download-only|d",
"Only download remote changes",
&boolValues["download_only"],
"dry-run",
"Perform a trial sync with no changes made",
&boolValues["dry_run"],
"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"],
"get-O365-drive-id",
"Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library",
&stringValues["get_o365_drive_id"],
"local-first",
"Synchronize from the local directory source first, before downloading changes from OneDrive.",
&boolValues["local_first"],
"log-dir",
"Directory where logging output is saved to, needs to end with a slash.",
&stringValues["log_dir"],
"logout",
"Logout the current user",
&boolValues["logout"],
"min-notif-changes",
"Minimum number of pending incoming changes necessary to trigger a desktop notification",
&longValues["min_notif_changes"],
"monitor|m",
"Keep monitoring for local and remote changes",
&boolValues["monitor"],
"monitor-interval",
"Number of seconds by which each sync operation is undertaken when idle under monitor mode.",
&longValues["monitor_interval"],
"monitor-fullscan-frequency",
"Number of sync runs before performing a full local scan of the synced directory",
&longValues["monitor_fullscan_frequency"],
"monitor-log-frequency",
"Frequency of logging in monitor mode",
&longValues["monitor_log_frequency"],
"no-remote-delete",
"Do not delete local file 'deletes' from OneDrive when using --upload-only",
&boolValues["no_remote_delete"],
"print-token",
"Print the access token, useful for debugging",
&boolValues["print_token"],
"resync",
"Forget the last saved state, perform a full sync",
&boolValues["resync"],
"remove-directory",
"Remove a directory on OneDrive - no sync will be performed.",
&stringValues["remove_directory"],
"single-directory",
"Specify a single local directory within the OneDrive root to sync.",
&stringValues["single_directory"],
"skip-dot-files",
"Skip dot files and folders from syncing",
&boolValues["skip_dotfiles"],
"skip-file",
"Skip any files that match this pattern from syncing",
&stringValues["skip_file"],
"skip-symlinks",
"Skip syncing of symlinks",
&boolValues["skip_symlinks"],
"source-directory",
"Source directory to rename or move on OneDrive - no sync will be performed.",
&stringValues["source_directory"],
"syncdir",
"Specify the local directory used for synchronization to OneDrive",
&stringValues["sync_dir"],
"synchronize",
"Perform a synchronization",
&boolValues["synchronize"],
"upload-only",
"Only upload to OneDrive, do not sync changes from OneDrive locally",
&boolValues["upload_only"],
// duplicated from main.d to get full help output!
"confdir",
"Set the directory used to store the configuration files",
&tmpStr,
"verbose|v+",
"Print more details, useful for debugging (repeat for extra debugging)",
&tmpVerb,
"version",
"Print the version and exit",
&tmpBol
);
if (opt.helpWanted) {
outputLongHelp(opt.options);
exit(EXIT_SUCCESS);
}
} catch (GetOptException e) {
log.error(e.msg);
log.error("Try 'onedrive -h' for more information");
exit(EXIT_FAILURE);
} catch (Exception e) {
// error
log.error(e.msg);
log.error("Try 'onedrive -h' for more information");
exit(EXIT_FAILURE);
}
}
string getValueString(string key)
{
auto p = key in stringValues;
if (p) {
return *p;
} else {
@ -82,19 +308,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) {
return *p;
} 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)
@ -109,14 +355,27 @@ final class Config
if (!c.empty) {
c.popFront(); // skip the whole match
string key = c.front.dup;
auto p = key in values;
auto p = key in boolValues;
if (p) {
c.popFront();
setValue(key, c.front.dup);
// only accept "true" as true value. TODO Should we support other formats?
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 {
log.log("Unknown key in config file: ", key);
return false;
}
}
}
} else {
log.log("Malformed config line: ", line);
return false;
@ -126,10 +385,55 @@ final class Config
}
}
void outputLongHelp(Option[] opt)
{
auto argsNeedingOptions = [
"--confdir",
"--create-directory",
"--destination-directory",
"--get-O365-drive-id",
"--log-dir",
"--min-notif-changes",
"--monitor-interval",
"--monitor-log-frequency",
"--monitor-fullscan-frequency",
"--remove-directory",
"--single-directory",
"--skip-file",
"--source-directory",
"--syncdir" ];
writeln(`OneDrive - a client for OneDrive Cloud Services
Usage:
onedrive [options] --synchronize
Do a one time synchronization
onedrive [options] --monitor
Monitor filesystem and sync regularly
onedrive [options] --display-config
Display the currently used configuration
onedrive [options] --display-sync-status
Query OneDrive service and report on pending changes
onedrive -h | --help
Show this help screen
onedrive --version
Show version
Options:
`);
foreach (it; opt.sort!("a.optLong < b.optLong")) {
writefln(" %s%s%s%s\n %s",
it.optLong,
it.optShort == "" ? "" : " " ~ it.optShort,
argsNeedingOptions.canFind(it.optLong) ? " ARG" : "",
it.required ? " (required)" : "", it.help);
}
}
unittest
{
auto cfg = new Config("");
cfg.load("config");
assert(cfg.getValue("sync_dir") == "~/OneDrive");
assert(cfg.getValue("empty", "default") == "default");
assert(cfg.getValueString("sync_dir") == "~/OneDrive");
}

View file

@ -10,7 +10,7 @@ version(Notifications) {
}
// enable verbose logging
int verbose;
long verbose;
bool writeLogFile = false;
private bool doNotifications;

View file

@ -19,114 +19,26 @@ int main(string[] args)
// Disable buffering on stdout
stdout.setvbuf(0, _IONBF);
// Application Option Variables
// Add a check mounts option to resolve https://github.com/abraunegg/onedrive/issues/8
bool checkMount = false;
// Check if we should ignore a directory if a special file (.nosync) is present - https://github.com/abraunegg/onedrive/issues/163
bool checkNoSync = false;
// configuration directory
string configDirName;
// Create a single root directory on OneDrive
string createDirectory;
// The destination directory if we are using the OneDrive client to rename a directory
string destinationDirectory;
// Debug the HTTPS submit operations if required
bool debugHttp = false;
// Do not use notifications in monitor mode
bool disableNotifications = false;
// Display application configuration but do not sync
bool displayConfiguration = false;
// Display sync status
bool displaySyncStatus = false;
// only download remote changes
bool downloadOnly = false;
// 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;
// Perform only a dry run - not applicable for --monitor mode
bool dryRun = false;
// Do we enable a log file
bool enableLogFile = false;
// 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 = false;
// SharePoint / Office 365 Shared Library name to query
string o365SharedLibraryName;
// Local sync - Upload local changes first before downloading changes from OneDrive
bool localFirst = false;
// remove the current user and sync state
bool logout = false;
// enable monitor mode
bool monitor = false;
// Add option for no remote delete
bool noRemoteDelete = false;
// print the access token
bool printAccessToken = false;
// force a full resync
bool resync = false;
// Remove a single directory on OneDrive
string removeDirectory;
// This allows for selective directory syncing instead of everything under ~/OneDrive/
string singleDirectory;
// Skip dot files & folders - eg .file or /.folder/
bool skipDotFiles = false;
// Add option to skip symlinks
bool skipSymlinks = false;
// The source directory if we are using the OneDrive client to rename a directory
string sourceDirectory;
// override the sync directory
string syncDirName;
// 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 = false;
// Upload Only
bool uploadOnly = false;
// enable verbose logging
bool verbose = false;
string confdirOption;
try {
// print the version and exit
bool printVersion = false;
// Application Startup option validation
try {
auto opt = getopt(
args,
std.getopt.config.passThrough,
std.getopt.config.bundling,
std.getopt.config.caseSensitive,
"check-for-nomount", "Check for the presence of .nosync in the syncdir root. If found, do not perform sync.", &checkMount,
"check-for-nosync", "Check for the presence of .nosync in each directory. If found, skip directory from sync.", &checkNoSync,
"confdir", "Set the directory used to store the configuration files", &configDirName,
"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,
"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-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,
"dry-run", "Perform a trial sync with no changes made", &dryRun,
"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,
"local-first", "Synchronize from the local directory source first, before downloading changes from OneDrive.", &localFirst,
"logout", "Logout the current user", &logout,
"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,
"resync", "Forget the last saved state, perform a full sync", &resync,
"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,
"skip-dot-files", "Skip dot files and folders from syncing", &skipDotFiles,
"skip-symlinks", "Skip syncing of symlinks", &skipSymlinks,
"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,
"upload-only", "Only upload to OneDrive, do not sync changes from OneDrive locally", &uploadOnly,
"confdir", "Set the directory used to store the configuration files", &confdirOption,
"verbose|v+", "Print more details, useful for debugging (repeat for extra debugging)", &log.verbose,
"version", "Print the version and exit", &printVersion
);
if (opt.helpWanted) {
outputLongHelp(opt.options);
args ~= "--help";
}
if (printVersion) {
std.stdio.write("onedrive ", import("version"));
return EXIT_SUCCESS;
}
} catch (GetOptException e) {
@ -140,92 +52,28 @@ int main(string[] args)
return EXIT_FAILURE;
}
// Main function variables
string homePath = "";
string configDirBase = "";
// Debug the HTTPS response operations if required
bool debugHttpSubmit;
// Are we able to reach the OneDrive Service
bool online = false;
// simulateNoRefreshTokenFile in case of --dry-run & --logout
bool simulateNoRefreshTokenFile = false;
// 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
// Check for HOME environment variable
if (environment.get("HOME") != ""){
// Use HOME environment variable
log.vdebug("homePath: HOME environment variable set");
homePath = environment.get("HOME");
} else {
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
// No shell is set or username - observed case when running as systemd service under CentOS 7.x
log.vdebug("homePath: WARNING - no HOME environment variable set");
log.vdebug("homePath: WARNING - no SHELL environment variable set");
log.vdebug("homePath: WARNING - no USER environment variable set");
homePath = "/root";
} else {
// A shell & valid user is set, but no HOME is set, use ~ which can be expanded
log.vdebug("homePath: WARNING - no HOME environment variable set");
homePath = "~";
}
}
// Output homePath calculation
log.vdebug("homePath: ", homePath);
// Determine the base directory relative to which user specific configuration files should be stored.
if (environment.get("XDG_CONFIG_HOME") != ""){
log.vdebug("configDirBase: XDG_CONFIG_HOME environment variable set");
configDirBase = environment.get("XDG_CONFIG_HOME");
} else {
// XDG_CONFIG_HOME does not exist on systems where X11 is not present - ie - headless systems / servers
log.vdebug("configDirBase: WARNING - no XDG_CONFIG_HOME environment variable set");
configDirBase = homePath ~ "/.config";
}
// Output configDirBase calculation
log.vdebug("configDirBase: ", configDirBase);
// Determine the correct configuration directory to use
if (configDirName != "") {
// A CLI 'confdir' was passed in
log.vdebug("configDirName: CLI override to set configDirName to: ", configDirName);
if (canFind(configDirName,"~")) {
// A ~ was found
log.vdebug("configDirName: A '~' was found in configDirName, using the calculated 'homePath' to replace '~'");
configDirName = homePath ~ strip(configDirName,"~","~");
}
} else {
// Set the default application configuration directory
log.vdebug("configDirName: Configuring application to use default config path");
// configDirBase contains the correct path so we do not need to check for presence of '~'
configDirName = configDirBase ~ "/onedrive";
}
if (printVersion) {
std.stdio.write("onedrive ", import("version"));
return EXIT_SUCCESS;
}
// dry-run notification
if (dryRun) {
log.log("DRY-RUN Configured. Output below shows what 'would' have occurred.");
}
// load application configuration
log.vlog("Loading config ...");
log.vlog("Using Config Dir: ", configDirName);
if (!exists(configDirName)) mkdirRecurse(configDirName);
auto cfg = new config.Config(configDirName);
if(!cfg.init()){
// load configuration file if available
auto cfg = new config.Config(confdirOption);
if (!cfg.initialize()) {
// There was an error loading the configuration
// Error message already printed
return EXIT_FAILURE;
}
// update configuration from command line args
cfg.update_from_args(args);
// dry-run notification
if (cfg.getValueBool("dry_run")) {
log.log("DRY-RUN Configured. Output below shows what 'would' have occurred.");
}
// Are we able to reach the OneDrive Service
bool online = false;
// dry-run database setup
if (dryRun) {
if (cfg.getValueBool("dry_run")) {
// Make a copy of the original items.sqlite3 for use as the dry run copy if it exists
if (exists(cfg.databaseFilePath)) {
// copy the file
@ -234,58 +82,29 @@ int main(string[] args)
}
}
// command line parameters to override default 'config' & take precedence
// Set the client to skip specific directories if .nosync is found AND ONLY if --check-for-nosync was passed in
if (checkNoSync) {
log.vdebug("CLI override to set check_nosync to: true");
cfg.setValue("check_nosync", "true");
}
// Set the client to skip dot files & folders if --skip-dot-files was passed in
if (skipDotFiles) {
// The user passed in an alternate skip_dotfiles as to what was either in 'config' file or application default
log.vdebug("CLI override to set skip_dotfiles to: true");
cfg.setValue("skip_dotfiles", "true");
}
// 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
string syncDir;
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
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
// Does the 'currently configured' sync_dir include a ~
if (canFind(cfg.getValue("sync_dir"),"~")) {
if (canFind(cfg.getValueString("sync_dir"), "~")) {
// A ~ was found
log.vdebug("sync_dir: A '~' was found in sync_dir, using the calculated 'homePath' to replace '~'");
syncDir = homePath ~ strip(cfg.getValue("sync_dir"),"~","~");
syncDir = cfg.homePath ~ strip(cfg.getValueString("sync_dir"), "~");
} else {
// No ~ found in sync_dir, use as is
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
syncDir = cfg.getValue("sync_dir");
syncDir = cfg.getValueString("sync_dir");
}
} else {
// 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");
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");
syncDir = expandTilde(cfg.getValue("sync_dir"));
syncDir = expandTilde(cfg.getValueString("sync_dir"));
} else {
syncDir = cfg.getValue("sync_dir");
syncDir = cfg.getValueString("sync_dir");
}
}
@ -293,50 +112,47 @@ int main(string[] args)
log.vdebug("syncDir: ", syncDir);
// Configure logging if enabled
if (enableLogFile){
if (cfg.getValueBool("enable_logging")){
// 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.init(logDir);
}
// Configure whether notifications are used
log.setNotifications(monitor && !disableNotifications);
log.setNotifications(cfg.getValueBool("monitor") && !cfg.getValueBool("disable_notifications"));
// upgrades
if (exists(configDirName ~ "/items.db")) {
if (!dryRun) {
safeRemove(configDirName ~ "/items.db");
if (exists(cfg.configDirName ~ "/items.db")) {
if (!cfg.getValueBool("dry_run")) {
safeRemove(cfg.configDirName ~ "/items.db");
}
log.logAndNotify("Database schema changed, resync needed");
resync = true;
cfg.setValueBool("resync", true);
}
if (resync || logout) {
if (cfg.getValueBool("resync") || cfg.getValueBool("logout")) {
log.vlog("Deleting the saved status ...");
if (!dryRun) {
if (!cfg.getValueBool("dry_run")) {
safeRemove(cfg.databaseFilePath);
safeRemove(cfg.deltaLinkFilePath);
safeRemove(cfg.uploadStateFilePath);
}
if (logout) {
if (!dryRun) {
if (cfg.getValueBool("logout")) {
if (!cfg.getValueBool("dry_run")) {
safeRemove(cfg.refreshTokenFilePath);
} else {
// simulate file being removed / unavailable
simulateNoRefreshTokenFile = true;
}
}
}
// Display current application configuration, no application initialisation
if (displayConfiguration){
string userConfigFilePath = configDirName ~ "/config";
string userSyncList = configDirName ~ "/sync_list";
if (cfg.getValueBool("display_config")){
string userConfigFilePath = cfg.configDirName ~ "/config";
string userSyncList = cfg.configDirName ~ "/sync_list";
// Display application version
std.stdio.write("onedrive version = ", import("version"));
// Display all of the pertinent configuration options
writeln("Config path = ", configDirName);
writeln("Config path = ", cfg.configDirName);
// Does a config file exist or are we using application defaults
if (exists(userConfigFilePath)){
@ -346,19 +162,19 @@ int main(string[] args)
}
// Config Options
writeln("Config option 'check_nosync' = ", cfg.getValue("check_nosync"));
writeln("Config option 'check_nosync' = ", cfg.getValueBool("check_nosync"));
writeln("Config option 'sync_dir' = ", syncDir);
writeln("Config option 'skip_dir' = ", cfg.getValue("skip_dir"));
writeln("Config option 'skip_file' = ", cfg.getValue("skip_file"));
writeln("Config option 'skip_dotfiles' = ", cfg.getValue("skip_dotfiles"));
writeln("Config option 'skip_symlinks' = ", cfg.getValue("skip_symlinks"));
writeln("Config option 'monitor_interval' = ", cfg.getValue("monitor_interval"));
writeln("Config option 'min_notif_changes' = ", cfg.getValue("min_notif_changes"));
writeln("Config option 'log_dir' = ", cfg.getValue("log_dir"));
writeln("Config option 'skip_dir' = ", cfg.getValueString("skip_dir"));
writeln("Config option 'skip_file' = ", cfg.getValueString("skip_file"));
writeln("Config option 'skip_dotfiles' = ", cfg.getValueBool("skip_dotfiles"));
writeln("Config option 'skip_symlinks' = ", cfg.getValueBool("skip_symlinks"));
writeln("Config option 'monitor_interval' = ", cfg.getValueLong("monitor_interval"));
writeln("Config option 'min_notif_changes' = ", cfg.getValueLong("min_notif_changes"));
writeln("Config option 'log_dir' = ", cfg.getValueString("log_dir"));
// Is config option drive_id configured?
if (cfg.getValue("drive_id", "") != ""){
writeln("Config option 'drive_id' = ", cfg.getValue("drive_id"));
if (cfg.getValueString("drive_id") != ""){
writeln("Config option 'drive_id' = ", cfg.getValueString("drive_id"));
}
// Is sync_list configured?
@ -385,14 +201,14 @@ int main(string[] args)
} catch (CurlException e) {
// No network connection to OneDrive Service
log.error("No network connection to Microsoft OneDrive Service");
if (!monitor) {
if (!cfg.getValueBool("monitor")) {
return EXIT_FAILURE;
}
}
// Initialize OneDrive, check for authorization
oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11, dryRun, simulateNoRefreshTokenFile);
oneDrive.printAccessToken = printAccessToken;
oneDrive = new OneDriveApi(cfg);
oneDrive.printAccessToken = cfg.getValueBool("print_token");
if (!oneDrive.init()) {
log.error("Could not initialize the OneDrive API");
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
@ -402,13 +218,14 @@ int main(string[] args)
// if --synchronize or --monitor not passed in, exit & display help
auto performSyncOK = false;
if (synchronize || monitor) {
if (cfg.getValueBool("synchronize") || cfg.getValueBool("monitor")) {
performSyncOK = true;
}
// create-directory, remove-directory, source-directory, destination-directory
// are activities that dont perform a sync no error message for these items either
if (((createDirectory != "") || (removeDirectory != "")) || ((sourceDirectory != "") && (destinationDirectory != "")) || (o365SharedLibraryName != "") || (displaySyncStatus == true)) {
if (((cfg.getValueString("create_directory") != "") || (cfg.getValueString("remove_directory") != "")) || ((cfg.getValueString("source_directory") != "") && (cfg.getValueString("destination_directory") != "")) || (cfg.getValueString("get_o365_drive_id") != "") || cfg.getValueBool("display_sync_status")) {
performSyncOK = true;
}
@ -420,7 +237,7 @@ int main(string[] args)
}
// if --synchronize && --monitor passed in, exit & display help as these conflict with each other
if (synchronize && monitor) {
if (cfg.getValueBool("synchronize") && cfg.getValueBool("monitor")) {
writeln("\nERROR: --synchronize and --monitor cannot be used together\n");
writeln("Refer to --help to determine which command option you should use.\n");
oneDrive.http.shutdown();
@ -429,7 +246,7 @@ int main(string[] args)
// Initialize the item database
log.vlog("Opening the item database ...");
if (!dryRun) {
if (!cfg.getValueBool("dry_run")) {
// Load the items.sqlite3 file as the database
log.vdebug("Using database file: ", cfg.databaseFilePath);
itemDb = new ItemDatabase(cfg.databaseFilePath);
@ -462,15 +279,15 @@ int main(string[] args)
// Configure skip_dir & skip_file from config entries
log.vdebug("Configuring skip_dir ...");
log.vdebug("skip_dir: ", cfg.getValue("skip_dir"));
selectiveSync.setDirMask(cfg.getValue("skip_dir"));
log.vdebug("skip_dir: ", cfg.getValueString("skip_dir"));
selectiveSync.setDirMask(cfg.getValueString("skip_dir"));
log.vdebug("Configuring skip_file ...");
log.vdebug("skip_file: ", cfg.getValue("skip_file"));
selectiveSync.setFileMask(cfg.getValue("skip_file"));
log.vdebug("skip_file: ", cfg.getValueString("skip_file"));
selectiveSync.setFileMask(cfg.getValueString("skip_file"));
// Initialize the sync engine
log.logAndNotify("Initializing the Synchronization Engine ...");
auto sync = new SyncEngine(cfg, oneDrive, itemDb, selectiveSync, dryRun);
auto sync = new SyncEngine(cfg, oneDrive, itemDb, selectiveSync);
try {
if (!initSyncEngine(sync)) {
@ -478,21 +295,21 @@ int main(string[] args)
return EXIT_FAILURE;
}
} catch (CurlException e) {
if (!monitor) {
log.log("\nNo Internet connection.");
if (!cfg.getValueBool("monitor")) {
log.log("\nNo internet connection.");
oneDrive.http.shutdown();
return EXIT_FAILURE;
}
}
// 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
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
if (checkMount) {
if (cfg.getValueBool("check_nomount")) {
// we were asked to check the mounts
if (exists(syncDir ~ "/.nosync")) {
log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data.");
@ -502,53 +319,53 @@ int main(string[] args)
}
// Do we need to create or remove a directory?
if ((createDirectory != "") || (removeDirectory != "")) {
if ((cfg.getValueString("create_directory") != "") || (cfg.getValueString("remove_directory") != "")) {
if (createDirectory != "") {
if (cfg.getValueString("create_directory") != "") {
// create a directory on OneDrive
sync.createDirectoryNoSync(createDirectory);
sync.createDirectoryNoSync(cfg.getValueString("create_directory"));
}
if (removeDirectory != "") {
if (cfg.getValueString("remove_directory") != "") {
// remove a directory on OneDrive
sync.deleteDirectoryNoSync(removeDirectory);
sync.deleteDirectoryNoSync(cfg.getValueString("remove_directory"));
}
}
// Are we renaming or moving a directory?
if ((sourceDirectory != "") && (destinationDirectory != "")) {
if ((cfg.getValueString("source_directory") != "") && (cfg.getValueString("destination_directory") != "")) {
// We are renaming or moving a directory
sync.renameDirectoryNoSync(sourceDirectory, destinationDirectory);
sync.renameDirectoryNoSync(cfg.getValueString("source_directory"), cfg.getValueString("destination_directory"));
}
// Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
if (o365SharedLibraryName != ""){
sync.querySiteCollectionForDriveID(o365SharedLibraryName);
if (cfg.getValueString("get_o365_drive_id") != ""){
sync.querySiteCollectionForDriveID(cfg.getValueString("get_o365_drive_id"));
}
// Are we displaying the sync status of the client?
if (displaySyncStatus) {
if (cfg.getValueBool("display_sync_status")) {
string remotePath = "/";
string localPath = ".";
// Are we doing a single directory check?
if (singleDirectory != ""){
if (cfg.getValueString("single_directory") != ""){
// Need two different path strings here
remotePath = singleDirectory;
localPath = singleDirectory;
remotePath = cfg.getValueString("single_directory");
localPath = cfg.getValueString("single_directory");
}
sync.queryDriveForChanges(remotePath);
}
// Are we performing a sync, resync or monitor operation?
if ((synchronize) || (resync) || (monitor)) {
if ((cfg.getValueBool("synchronize")) || (cfg.getValueBool("resync")) || (cfg.getValueBool("monitor"))) {
if ((synchronize) || (resync)) {
if ((cfg.getValueBool("synchronize")) || (cfg.getValueBool("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 != ""){
if (cfg.getValueString("single_directory") != ""){
// Does the directory we want to sync actually exist?
if (!exists(singleDirectory)){
if (!exists(cfg.getValueString("single_directory"))){
// the requested directory does not exist ..
log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path");
oneDrive.http.shutdown();
@ -556,14 +373,13 @@ int main(string[] args)
}
}
// Perform the sync
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly, LOG_NORMAL, true);
performSync(sync, cfg.getValueString("single_directory"), cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), LOG_NORMAL, true);
}
}
if (monitor) {
if (cfg.getValueBool("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);
m.onDirCreated = delegate(string path) {
log.vlog("[M] Directory created: ", path);
@ -615,19 +431,17 @@ int main(string[] args)
signal(SIGTERM, &exitHandler);
// initialise the monitor class
if (cfg.getValue("skip_symlinks") == "true") skipSymlinks = true;
if (cfg.getValue("check_nosync") == "true") checkNoSync = true;
if (!downloadOnly) m.init(cfg, verbose, skipSymlinks, checkNoSync);
if (!cfg.getValueBool("download_only")) m.init(cfg, cfg.getValueLong("verbose") > 0, cfg.getValueBool("skip_symlinks"), cfg.getValueBool("check_nosync"));
// monitor loop
immutable auto checkInterval = dur!"seconds"(to!long(cfg.getValue("monitor_interval")));
immutable auto logInterval = to!long(cfg.getValue("monitor_log_frequency"));
immutable auto fullScanFrequency = to!long(cfg.getValue("monitor_fullscan_frequency"));
immutable auto checkInterval = dur!"seconds"(cfg.getValueLong("monitor_interval"));
immutable auto logInterval = cfg.getValueLong("monitor_log_frequency");
immutable auto fullScanFrequency = cfg.getValueLong("monitor_fullscan_frequency");
auto lastCheckTime = MonoTime.currTime();
auto logMonitorCounter = 0;
auto fullScanCounter = 0;
bool fullScanRequired = true;
while (true) {
if (!downloadOnly) m.update(online);
if (!cfg.getValueBool("download_only")) m.update(online);
auto currTime = MonoTime.currTime();
if (currTime - lastCheckTime > checkInterval) {
// log monitor output suppression
@ -653,8 +467,8 @@ int main(string[] args)
return EXIT_FAILURE;
}
try {
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly, (logMonitorCounter == logInterval ? MONITOR_LOG_QUIET : MONITOR_LOG_SILENT), fullScanRequired);
if (!downloadOnly) {
performSync(sync, cfg.getValueString("single_directory"), cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), (logMonitorCounter == logInterval ? MONITOR_LOG_QUIET : MONITOR_LOG_SILENT), fullScanRequired);
if (!cfg.getValueBool("download_only")) {
// discard all events that may have been generated by the sync
m.update(false);
}
@ -685,7 +499,7 @@ int main(string[] args)
destroy(itemDb);
// --dry-run temp database cleanup
if (dryRun) {
if (cfg.getValueBool("dry_run")) {
if (exists(cfg.databaseFilePathDryRun)) {
// remove the file
log.vdebug("Removing items-dryrun.sqlite3 as dry run operations complete");
@ -825,41 +639,4 @@ extern(C) nothrow @nogc @system void exitHandler(int value) {
} catch(Exception e) {}
exit(0);
}
void outputLongHelp(Option[] opt)
{
auto argsNeedingOptions = [
"--confdir",
"--create-directory",
"--destination-directory",
"--get-O365-drive-id",
"--remove-directory",
"--single-directory",
"--source-directory",
"--syncdir" ];
writeln(`OneDrive - a client for OneDrive Cloud Services
Usage:
onedrive [options] --synchronize
Do a one time synchronization
onedrive [options] --monitor
Monitor filesystem and sync regularly
onedrive [options] --display-config
Display the currently used configuration
onedrive [options] --display-sync-status
Query OneDrive service and report on pending changes
onedrive -h | --help
Show this help screen
onedrive --version
Show version
Options:
`);
foreach (it; opt) {
writefln(" %s%s%s%s\n %s",
it.optShort == "" ? "" : it.optShort ~ " ",
it.optLong,
argsNeedingOptions.canFind(it.optLong) ? " ARG" : "",
it.required ? " (required)" : "", it.help);
}
}

View file

@ -8,8 +8,8 @@ import progress;
import config;
static import log;
shared bool debugResponse = false;
shared bool dryRun = false;
shared bool simulateNoRefreshTokenFile = false;
private bool dryRun = false;
private bool simulateNoRefreshTokenFile = false;
private immutable {
// Client Identifier
@ -66,7 +66,7 @@ final class OneDriveApi
// if true, every new access token is printed
bool printAccessToken;
this(Config cfg, bool debugHttp, bool forceHTTP11, bool dryRun, bool simulateNoRefreshTokenFile)
this(Config cfg)
{
this.cfg = cfg;
http = HTTP();
@ -94,32 +94,32 @@ final class OneDriveApi
http.maxRedirects(5);
// Do we enable curl debugging?
if (debugHttp) {
if (cfg.getValueBool("debug_https")) {
http.verbose = true;
.debugResponse = true;
}
// What version of HTTP protocol do we use?
// 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");
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
http.handle.set(CurlOption.http_version,2);
}
// Do we set the dryRun handlers?
if (dryRun) {
if (cfg.getValueBool("dry_run")) {
.dryRun = true;
}
if (simulateNoRefreshTokenFile) {
if (cfg.getValueBool("logout")) {
.simulateNoRefreshTokenFile = true;
}
}
}
bool init()
{
try {
driveId = cfg.getValue("drive_id");
driveId = cfg.getValueString("drive_id");
if (driveId.length) {
driveUrl = driveByIdUrl ~ driveId;
itemByIdUrl = driveUrl ~ "/items";

View file

@ -208,7 +208,7 @@ final class SyncEngine
// sync engine dryRun flag
private bool dryRun = false;
this(Config cfg, OneDriveApi onedrive, ItemDatabase itemdb, SelectiveSync selectiveSync, bool dryRun)
this(Config cfg, OneDriveApi onedrive, ItemDatabase itemdb, SelectiveSync selectiveSync)
{
assert(onedrive && itemdb && selectiveSync);
this.cfg = cfg;
@ -216,7 +216,7 @@ final class SyncEngine
this.itemdb = itemdb;
this.selectiveSync = selectiveSync;
// session = UploadSession(onedrive, cfg.uploadStateFilePath);
this.dryRun = dryRun;
this.dryRun = cfg.getValueBool("dry_run");
}
void reset()
@ -243,7 +243,7 @@ final class SyncEngine
// OneDrive responded with 400 error: Bad Request
log.error("\nERROR: OneDrive returned a 'HTTP 400 Bad Request' - Cannot Initialize Sync Engine");
// 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");
}
// Must exit here
@ -614,7 +614,7 @@ final class SyncEngine
if (("value" in changes) != null) {
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");
} else {
// There are valid changes
@ -768,7 +768,7 @@ final class SyncEngine
// Check if this is a directory to skip
if (!unwanted) {
// Only check path if config is != ""
if (cfg.getValue("skip_dir") != "") {
if (cfg.getValueString("skip_dir") != "") {
unwanted = selectiveSync.isDirNameExcluded(item.name);
if (unwanted) log.vlog("Skipping item - excluded by skip_dir config: ", item.name);
}
@ -812,7 +812,7 @@ final class SyncEngine
}
// skip downloading dot files if configured
if (cfg.getValue("skip_dotfiles") == "true") {
if (cfg.getValueBool("skip_dotfiles")) {
if (isDotFile(path)) {
log.vlog("Skipping item - .file or .folder: ", path);
unwanted = true;
@ -1480,7 +1480,7 @@ final class SyncEngine
// path is less than maxPathLength
// skip dot files if configured
if (cfg.getValue("skip_dotfiles") == "true") {
if (cfg.getValueBool("skip_dotfiles")) {
if (isDotFile(path)) {
log.vlog("Skipping item - .file or .folder: ", path);
return;
@ -1488,7 +1488,7 @@ final class SyncEngine
}
// Do we need to check for .nosync? Only if --check-for-nosync was passed in
if (cfg.getValue("check_nosync") == "true") {
if (cfg.getValueBool("check_nosync")) {
if (exists(path ~ "/.nosync")) {
log.vlog("Skipping item - .nosync found & --check-for-nosync enabled: ", path);
return;
@ -1497,7 +1497,7 @@ final class SyncEngine
if (isSymlink(path)) {
// 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);
return;
@ -1532,7 +1532,7 @@ final class SyncEngine
if (isDir(path)) {
log.vdebug("Checking path: ", path);
// Only check path if config is != ""
if (cfg.getValue("skip_dir") != "") {
if (cfg.getValueString("skip_dir") != "") {
if (selectiveSync.isDirNameExcluded(strip(path,"./"))) {
log.vlog("Skipping item - excluded by skip_dir config: ", path);
return;