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,46 +715,60 @@ 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
Perform a trial sync with no changes made
--enable-logging
Enable client activity to a separate log file
--force-http-1.1
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

@ -16,13 +16,17 @@ onedrive \- folder synchronization with OneDrive
.SH DESCRIPTION
A complete tool to interact with OneDrive on Linux.
.SH OPTIONS
Without any option given, no sync is done and the program exits.
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,13 +355,26 @@ 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 {
log.log("Unknown key in config file: ", key);
return false;
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);
@ -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;
// print the version and exit
bool printVersion = false;
// Application Startup option validation
string confdirOption;
try {
// print the version and exit
bool printVersion = false;
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,36 +94,36 @@ 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) {
.simulateNoRefreshTokenFile = true;
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";
itemByPathUrl = driveUrl ~ "/root:/";
itemByIdUrl = driveUrl ~ "/items";
itemByPathUrl = driveUrl ~ "/root:/";
}
} catch (Exception e) {}

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;