mirror of
https://github.com/abraunegg/onedrive
synced 2024-05-31 13:02:29 +02:00
Roll back to v2.3.1 (#446)
* Roll back to v2.3.1 after bad re-base merge with PR #389
This commit is contained in:
parent
f38b13dd00
commit
abb82868e4
12
README.md
12
README.md
|
@ -249,11 +249,6 @@ skip_file = "= .*|~*"
|
||||||
```text
|
```text
|
||||||
skip_file = "~*"
|
skip_file = "~*"
|
||||||
```
|
```
|
||||||
**Default valid configuration:**
|
|
||||||
```text
|
|
||||||
skip_file = "~*|.~*|*.tmp"
|
|
||||||
```
|
|
||||||
|
|
||||||
Do not use a skip_file entry of `.*` as this will prevent correct searching of local changes to process.
|
Do not use a skip_file entry of `.*` as this will prevent correct searching of local changes to process.
|
||||||
|
|
||||||
### Important - curl compatibility
|
### Important - curl compatibility
|
||||||
|
@ -318,7 +313,7 @@ Config path = /home/alex/.config/onedrive
|
||||||
Config file found in config path = false
|
Config file found in config path = false
|
||||||
Config option 'sync_dir' = /home/alex/OneDrive
|
Config option 'sync_dir' = /home/alex/OneDrive
|
||||||
Config option 'skip_dir' =
|
Config option 'skip_dir' =
|
||||||
Config option 'skip_file' = ~*|.~*|*.tmp
|
Config option 'skip_file' = ~*
|
||||||
Config option 'skip_dotfiles' = false
|
Config option 'skip_dotfiles' = false
|
||||||
Config option 'skip_symlinks' = false
|
Config option 'skip_symlinks' = false
|
||||||
Config option 'monitor_interval' = 45
|
Config option 'monitor_interval' = 45
|
||||||
|
@ -492,11 +487,6 @@ Files can be skipped in the following fashion:
|
||||||
* Explicitly specify the filename and it's full path relative to your sync_dir, eg: 'path/to/file/filename.ext'
|
* Explicitly specify the filename and it's full path relative to your sync_dir, eg: 'path/to/file/filename.ext'
|
||||||
* Explicitly specify the filename only and skip every instance of this filename, eg: 'filename.ext'
|
* Explicitly specify the filename only and skip every instance of this filename, eg: 'filename.ext'
|
||||||
|
|
||||||
By default, the following files will be skipped:
|
|
||||||
* Files that start with ~
|
|
||||||
* Files that start with .~ (like .~lock.* files generated by LibreOffice)
|
|
||||||
* Files that end in .tmp
|
|
||||||
|
|
||||||
**Note:** after changing `skip_file`, you must perform a full re-synchronization by adding `--resync` to your existing command line - for example: `onedrive --synchronize --resync`
|
**Note:** after changing `skip_file`, you must perform a full re-synchronization by adding `--resync` to your existing command line - for example: `onedrive --synchronize --resync`
|
||||||
|
|
||||||
**Note:** Do not use a skip_file entry of `.*` as this will prevent correct searching of local changes to process.
|
**Note:** Do not use a skip_file entry of `.*` as this will prevent correct searching of local changes to process.
|
||||||
|
|
2
config
2
config
|
@ -1,6 +1,6 @@
|
||||||
# Directory where the files will be synced
|
# Directory where the files will be synced
|
||||||
sync_dir = "~/OneDrive"
|
sync_dir = "~/OneDrive"
|
||||||
# Skip files and directories that match this pattern
|
# Skip files and directories that match this pattern
|
||||||
skip_file = "~*|.~*|*.tmp"
|
skip_file = "~*"
|
||||||
# Wait time (seconds) between sync operations in monitor mode
|
# Wait time (seconds) between sync operations in monitor mode
|
||||||
monitor_interval = "45"
|
monitor_interval = "45"
|
||||||
|
|
376
src/config.d
376
src/config.d
|
@ -1,6 +1,4 @@
|
||||||
import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE, exit;
|
import std.file, std.string, std.regex, std.stdio;
|
||||||
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;
|
import selective;
|
||||||
static import log;
|
static import log;
|
||||||
|
|
||||||
|
@ -12,110 +10,13 @@ final class Config
|
||||||
public string databaseFilePathDryRun;
|
public string databaseFilePathDryRun;
|
||||||
public string uploadStateFilePath;
|
public string uploadStateFilePath;
|
||||||
public string syncListFilePath;
|
public string syncListFilePath;
|
||||||
public string homePath;
|
|
||||||
public string configDirName;
|
|
||||||
|
|
||||||
private string userConfigFilePath;
|
private string userConfigFilePath;
|
||||||
// hashmap for the values found in the user config file
|
// hashmap for the values found in the user config file
|
||||||
// ARGGGG D is stupid and cannot make hashmap initializations!!!
|
private string[string] values;
|
||||||
// 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["single_directory"] = "";
|
|
||||||
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["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(configDirName,"~")) {
|
|
||||||
// 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";
|
refreshTokenFilePath = configDirName ~ "/refresh_token";
|
||||||
deltaLinkFilePath = configDirName ~ "/delta_link";
|
deltaLinkFilePath = configDirName ~ "/delta_link";
|
||||||
databaseFilePath = configDirName ~ "/items.sqlite3";
|
databaseFilePath = configDirName ~ "/items.sqlite3";
|
||||||
|
@ -125,8 +26,34 @@ final class Config
|
||||||
syncListFilePath = configDirName ~ "/sync_list";
|
syncListFilePath = configDirName ~ "/sync_list";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool initialize()
|
bool init()
|
||||||
{
|
{
|
||||||
|
// Default configuration directory
|
||||||
|
setValue("sync_dir", "~/OneDrive");
|
||||||
|
// Skip Directories - no directories are skipped by default
|
||||||
|
setValue("skip_dir", "");
|
||||||
|
// Configure to skip ONLY temp files (~*.doc etc) by default
|
||||||
|
// Prior configuration was: .*|~*
|
||||||
|
setValue("skip_file", "~*");
|
||||||
|
// 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");
|
||||||
|
// Check if we should ignore a directory if a special file (.nosync) is present
|
||||||
|
setValue("check_nosync", "false");
|
||||||
|
|
||||||
if (!load(userConfigFilePath)) {
|
if (!load(userConfigFilePath)) {
|
||||||
// What was the reason for failure?
|
// What was the reason for failure?
|
||||||
if (!exists(userConfigFilePath)) {
|
if (!exists(userConfigFilePath)) {
|
||||||
|
@ -140,154 +67,9 @@ final class Config
|
||||||
return true;
|
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"],
|
|
||||||
"logout",
|
|
||||||
"Logout the current user",
|
|
||||||
&boolValues["logout"],
|
|
||||||
"monitor|m",
|
|
||||||
"Keep monitoring for local and remote changes",
|
|
||||||
&boolValues["monitor"],
|
|
||||||
"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"],
|
|
||||||
"skip-dot-files",
|
|
||||||
"Skip dot files and folders from syncing",
|
|
||||||
&boolValues["skip_dotfiles"],
|
|
||||||
"single-directory",
|
|
||||||
"Specify a single local directory within the OneDrive root to sync.",
|
|
||||||
&stringValues["single_directory"],
|
|
||||||
"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) {
|
if (p) {
|
||||||
return *p;
|
return *p;
|
||||||
} else {
|
} else {
|
||||||
|
@ -295,39 +77,19 @@ final class Config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
long getValueLong(string key)
|
string getValue(string key, string value)
|
||||||
{
|
{
|
||||||
auto p = key in longValues;
|
auto p = key in values;
|
||||||
if (p) {
|
if (p) {
|
||||||
return *p;
|
return *p;
|
||||||
} else {
|
} else {
|
||||||
throw new Exception("Missing config value: " ~ key);
|
return value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool getValueBool(string key)
|
void setValue(string key, string value)
|
||||||
{
|
{
|
||||||
auto p = key in boolValues;
|
values[key] = value;
|
||||||
if (p) {
|
|
||||||
return *p;
|
|
||||||
} else {
|
|
||||||
throw new Exception("Missing config value: " ~ key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setValueBool(string key, bool value)
|
|
||||||
{
|
|
||||||
boolValues[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setValueString(string key, string value)
|
|
||||||
{
|
|
||||||
stringValues[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setValueLong(string key, long value)
|
|
||||||
{
|
|
||||||
longValues[key] = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool load(string filename)
|
private bool load(string filename)
|
||||||
|
@ -342,26 +104,13 @@ final class Config
|
||||||
if (!c.empty) {
|
if (!c.empty) {
|
||||||
c.popFront(); // skip the whole match
|
c.popFront(); // skip the whole match
|
||||||
string key = c.front.dup;
|
string key = c.front.dup;
|
||||||
auto p = key in boolValues;
|
auto p = key in values;
|
||||||
if (p) {
|
if (p) {
|
||||||
c.popFront();
|
c.popFront();
|
||||||
// only accept "true" as true value. TODO Should we support other formats?
|
setValue(key, c.front.dup);
|
||||||
setValueBool(key, c.front.dup == "true" ? true : false);
|
|
||||||
} else {
|
} else {
|
||||||
auto pp = key in stringValues;
|
log.log("Unknown key in config file: ", key);
|
||||||
if (pp) {
|
return false;
|
||||||
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 {
|
} else {
|
||||||
log.log("Malformed config line: ", line);
|
log.log("Malformed config line: ", line);
|
||||||
|
@ -372,49 +121,10 @@ final class Config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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.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
|
unittest
|
||||||
{
|
{
|
||||||
auto cfg = new Config("");
|
auto cfg = new Config("");
|
||||||
cfg.load("config");
|
cfg.load("config");
|
||||||
assert(cfg.getValueString("sync_dir") == "~/OneDrive");
|
assert(cfg.getValue("sync_dir") == "~/OneDrive");
|
||||||
|
assert(cfg.getValue("empty", "default") == "default");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct Item {
|
||||||
final class ItemDatabase
|
final class ItemDatabase
|
||||||
{
|
{
|
||||||
// increment this for every change in the db schema
|
// increment this for every change in the db schema
|
||||||
immutable int itemDatabaseVersion = 9;
|
immutable int itemDatabaseVersion = 8;
|
||||||
|
|
||||||
Database db;
|
Database db;
|
||||||
string insertItemStmt;
|
string insertItemStmt;
|
||||||
|
|
|
@ -10,7 +10,7 @@ version(Notifications) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// enable verbose logging
|
// enable verbose logging
|
||||||
long verbose;
|
int verbose;
|
||||||
bool writeLogFile = false;
|
bool writeLogFile = false;
|
||||||
|
|
||||||
private bool doNotifications;
|
private bool doNotifications;
|
||||||
|
|
457
src/main.d
457
src/main.d
|
@ -19,26 +19,114 @@ int main(string[] args)
|
||||||
// Disable buffering on stdout
|
// Disable buffering on stdout
|
||||||
stdout.setvbuf(0, _IONBF);
|
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
|
// configuration directory
|
||||||
string confdirOption;
|
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
|
||||||
try {
|
try {
|
||||||
// print the version and exit
|
|
||||||
bool printVersion = false;
|
|
||||||
auto opt = getopt(
|
auto opt = getopt(
|
||||||
args,
|
args,
|
||||||
std.getopt.config.passThrough,
|
|
||||||
std.getopt.config.bundling,
|
std.getopt.config.bundling,
|
||||||
std.getopt.config.caseSensitive,
|
std.getopt.config.caseSensitive,
|
||||||
"confdir", "Set the directory used to store the configuration files", &confdirOption,
|
"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,
|
||||||
"verbose|v+", "Print more details, useful for debugging (repeat for extra debugging)", &log.verbose,
|
"verbose|v+", "Print more details, useful for debugging (repeat for extra debugging)", &log.verbose,
|
||||||
"version", "Print the version and exit", &printVersion
|
"version", "Print the version and exit", &printVersion
|
||||||
);
|
);
|
||||||
if (opt.helpWanted) {
|
if (opt.helpWanted) {
|
||||||
args ~= "--help";
|
outputLongHelp(opt.options);
|
||||||
}
|
|
||||||
if (printVersion) {
|
|
||||||
std.stdio.write("onedrive ", import("version"));
|
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
} catch (GetOptException e) {
|
} catch (GetOptException e) {
|
||||||
|
@ -52,28 +140,92 @@ int main(string[] args)
|
||||||
return EXIT_FAILURE;
|
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);
|
||||||
|
|
||||||
// load configuration file if available
|
// Determine the base directory relative to which user specific configuration files should be stored.
|
||||||
auto cfg = new config.Config(confdirOption);
|
if (environment.get("XDG_CONFIG_HOME") != ""){
|
||||||
if (!cfg.initialize()) {
|
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()){
|
||||||
// There was an error loading the configuration
|
// There was an error loading the configuration
|
||||||
// Error message already printed
|
// Error message already printed
|
||||||
return EXIT_FAILURE;
|
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
|
// dry-run database setup
|
||||||
if (cfg.getValueBool("dry_run")) {
|
if (dryRun) {
|
||||||
// Make a copy of the original items.sqlite3 for use as the dry run copy if it exists
|
// Make a copy of the original items.sqlite3 for use as the dry run copy if it exists
|
||||||
if (exists(cfg.databaseFilePath)) {
|
if (exists(cfg.databaseFilePath)) {
|
||||||
// copy the file
|
// copy the file
|
||||||
|
@ -82,29 +234,58 @@ 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
|
// sync_dir environment handling to handle ~ expansion properly
|
||||||
string syncDir;
|
string syncDir;
|
||||||
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
|
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
|
||||||
log.vdebug("sync_dir: No SHELL or USER environment variable configuration detected");
|
log.vdebug("sync_dir: No SHELL or USER environment variable configuration detected");
|
||||||
// No shell or user set, so expandTilde() will fail - usually headless system running under init.d / systemd or potentially Docker
|
// No shell or user set, so expandTilde() will fail - usually headless system running under init.d / systemd or potentially Docker
|
||||||
// Does the 'currently configured' sync_dir include a ~
|
// Does the 'currently configured' sync_dir include a ~
|
||||||
if (canFind(cfg.getValueString("sync_dir"), "~")) {
|
if (canFind(cfg.getValue("sync_dir"),"~")) {
|
||||||
// A ~ was found
|
// A ~ was found
|
||||||
log.vdebug("sync_dir: A '~' was found in sync_dir, using the calculated 'homePath' to replace '~'");
|
log.vdebug("sync_dir: A '~' was found in sync_dir, using the calculated 'homePath' to replace '~'");
|
||||||
syncDir = cfg.homePath ~ strip(cfg.getValueString("sync_dir"), "~");
|
syncDir = homePath ~ strip(cfg.getValue("sync_dir"),"~","~");
|
||||||
} else {
|
} else {
|
||||||
// No ~ found in sync_dir, use as is
|
// No ~ found in sync_dir, use as is
|
||||||
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
||||||
syncDir = cfg.getValueString("sync_dir");
|
syncDir = cfg.getValue("sync_dir");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// A shell and user is set, expand any ~ as this will be expanded correctly if present
|
// A shell and user is set, expand any ~ as this will be expanded correctly if present
|
||||||
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
log.vdebug("sync_dir: Getting syncDir from config value sync_dir");
|
||||||
if (canFind(cfg.getValueString("sync_dir"), "~")) {
|
if (canFind(cfg.getValue("sync_dir"),"~")) {
|
||||||
log.vdebug("sync_dir: A '~' was found in configured sync_dir, automatically expanding as SHELL and USER environment variable is set");
|
log.vdebug("sync_dir: A '~' was found in configured sync_dir, automatically expanding as SHELL and USER environment variable is set");
|
||||||
syncDir = expandTilde(cfg.getValueString("sync_dir"));
|
syncDir = expandTilde(cfg.getValue("sync_dir"));
|
||||||
} else {
|
} else {
|
||||||
syncDir = cfg.getValueString("sync_dir");
|
syncDir = cfg.getValue("sync_dir");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,47 +293,50 @@ int main(string[] args)
|
||||||
log.vdebug("syncDir: ", syncDir);
|
log.vdebug("syncDir: ", syncDir);
|
||||||
|
|
||||||
// Configure logging if enabled
|
// Configure logging if enabled
|
||||||
if (cfg.getValueBool("enable_logging")){
|
if (enableLogFile){
|
||||||
// Read in a user defined log directory or use the default
|
// Read in a user defined log directory or use the default
|
||||||
string logDir = cfg.getValueString("log_dir");
|
string logDir = cfg.getValue("log_dir");
|
||||||
log.vlog("Using logfile dir: ", logDir);
|
log.vlog("Using logfile dir: ", logDir);
|
||||||
log.init(logDir);
|
log.init(logDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure whether notifications are used
|
// Configure whether notifications are used
|
||||||
log.setNotifications(cfg.getValueBool("monitor") && !cfg.getValueBool("disable_notifications"));
|
log.setNotifications(monitor && !disableNotifications);
|
||||||
|
|
||||||
// upgrades
|
// upgrades
|
||||||
if (exists(cfg.configDirName ~ "/items.db")) {
|
if (exists(configDirName ~ "/items.db")) {
|
||||||
if (!cfg.getValueBool("dry_run")) {
|
if (!dryRun) {
|
||||||
safeRemove(cfg.configDirName ~ "/items.db");
|
safeRemove(configDirName ~ "/items.db");
|
||||||
}
|
}
|
||||||
log.logAndNotify("Database schema changed, resync needed");
|
log.logAndNotify("Database schema changed, resync needed");
|
||||||
cfg.setValueBool("resync", true);
|
resync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.getValueBool("resync") || cfg.getValueBool("logout")) {
|
if (resync || logout) {
|
||||||
log.vlog("Deleting the saved status ...");
|
log.vlog("Deleting the saved status ...");
|
||||||
if (!cfg.getValueBool("dry_run")) {
|
if (!dryRun) {
|
||||||
safeRemove(cfg.databaseFilePath);
|
safeRemove(cfg.databaseFilePath);
|
||||||
safeRemove(cfg.deltaLinkFilePath);
|
safeRemove(cfg.deltaLinkFilePath);
|
||||||
safeRemove(cfg.uploadStateFilePath);
|
safeRemove(cfg.uploadStateFilePath);
|
||||||
}
|
}
|
||||||
if (cfg.getValueBool("logout")) {
|
if (logout) {
|
||||||
if (!cfg.getValueBool("dry_run")) {
|
if (!dryRun) {
|
||||||
safeRemove(cfg.refreshTokenFilePath);
|
safeRemove(cfg.refreshTokenFilePath);
|
||||||
|
} else {
|
||||||
|
// simulate file being removed / unavailable
|
||||||
|
simulateNoRefreshTokenFile = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display current application configuration, no application initialisation
|
// Display current application configuration, no application initialisation
|
||||||
if (cfg.getValueBool("display_config")){
|
if (displayConfiguration){
|
||||||
string userConfigFilePath = cfg.configDirName ~ "/config";
|
string userConfigFilePath = configDirName ~ "/config";
|
||||||
string userSyncList = cfg.configDirName ~ "/sync_list";
|
string userSyncList = configDirName ~ "/sync_list";
|
||||||
// Display application version
|
// Display application version
|
||||||
std.stdio.write("onedrive version = ", import("version"));
|
std.stdio.write("onedrive version = ", import("version"));
|
||||||
// Display all of the pertinent configuration options
|
// Display all of the pertinent configuration options
|
||||||
writeln("Config path = ", cfg.configDirName);
|
writeln("Config path = ", configDirName);
|
||||||
|
|
||||||
// Does a config file exist or are we using application defaults
|
// Does a config file exist or are we using application defaults
|
||||||
if (exists(userConfigFilePath)){
|
if (exists(userConfigFilePath)){
|
||||||
|
@ -162,19 +346,19 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config Options
|
// Config Options
|
||||||
writeln("Config option 'check_nosync' = ", cfg.getValueBool("check_nosync"));
|
writeln("Config option 'check_nosync' = ", cfg.getValue("check_nosync"));
|
||||||
writeln("Config option 'sync_dir' = ", syncDir);
|
writeln("Config option 'sync_dir' = ", syncDir);
|
||||||
writeln("Config option 'skip_dir' = ", cfg.getValueString("skip_dir"));
|
writeln("Config option 'skip_dir' = ", cfg.getValue("skip_dir"));
|
||||||
writeln("Config option 'skip_file' = ", cfg.getValueString("skip_file"));
|
writeln("Config option 'skip_file' = ", cfg.getValue("skip_file"));
|
||||||
writeln("Config option 'skip_dotfiles' = ", cfg.getValueBool("skip_dotfiles"));
|
writeln("Config option 'skip_dotfiles' = ", cfg.getValue("skip_dotfiles"));
|
||||||
writeln("Config option 'skip_symlinks' = ", cfg.getValueBool("skip_symlinks"));
|
writeln("Config option 'skip_symlinks' = ", cfg.getValue("skip_symlinks"));
|
||||||
writeln("Config option 'monitor_interval' = ", cfg.getValueLong("monitor_interval"));
|
writeln("Config option 'monitor_interval' = ", cfg.getValue("monitor_interval"));
|
||||||
writeln("Config option 'min_notif_changes' = ", cfg.getValueLong("min_notif_changes"));
|
writeln("Config option 'min_notif_changes' = ", cfg.getValue("min_notif_changes"));
|
||||||
writeln("Config option 'log_dir' = ", cfg.getValueString("log_dir"));
|
writeln("Config option 'log_dir' = ", cfg.getValue("log_dir"));
|
||||||
|
|
||||||
// Is config option drive_id configured?
|
// Is config option drive_id configured?
|
||||||
if (cfg.getValueString("drive_id") != ""){
|
if (cfg.getValue("drive_id", "") != ""){
|
||||||
writeln("Config option 'drive_id' = ", cfg.getValueString("drive_id"));
|
writeln("Config option 'drive_id' = ", cfg.getValue("drive_id"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is sync_list configured?
|
// Is sync_list configured?
|
||||||
|
@ -201,14 +385,14 @@ int main(string[] args)
|
||||||
} catch (CurlException e) {
|
} catch (CurlException e) {
|
||||||
// No network connection to OneDrive Service
|
// No network connection to OneDrive Service
|
||||||
log.error("No network connection to Microsoft OneDrive Service");
|
log.error("No network connection to Microsoft OneDrive Service");
|
||||||
if (!cfg.getValueBool("monitor")) {
|
if (!monitor) {
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize OneDrive, check for authorization
|
// Initialize OneDrive, check for authorization
|
||||||
oneDrive = new OneDriveApi(cfg);
|
oneDrive = new OneDriveApi(cfg, debugHttp, forceHTTP11, dryRun, simulateNoRefreshTokenFile);
|
||||||
oneDrive.printAccessToken = cfg.getValueBool("print_token");
|
oneDrive.printAccessToken = printAccessToken;
|
||||||
if (!oneDrive.init()) {
|
if (!oneDrive.init()) {
|
||||||
log.error("Could not initialize the OneDrive API");
|
log.error("Could not initialize the OneDrive API");
|
||||||
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
|
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
|
||||||
|
@ -218,14 +402,13 @@ int main(string[] args)
|
||||||
|
|
||||||
// if --synchronize or --monitor not passed in, exit & display help
|
// if --synchronize or --monitor not passed in, exit & display help
|
||||||
auto performSyncOK = false;
|
auto performSyncOK = false;
|
||||||
|
if (synchronize || monitor) {
|
||||||
if (cfg.getValueBool("synchronize") || cfg.getValueBool("monitor")) {
|
|
||||||
performSyncOK = true;
|
performSyncOK = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create-directory, remove-directory, source-directory, destination-directory
|
// create-directory, remove-directory, source-directory, destination-directory
|
||||||
// are activities that dont perform a sync no error message for these items either
|
// are activities that dont perform a sync no error message for these items either
|
||||||
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")) {
|
if (((createDirectory != "") || (removeDirectory != "")) || ((sourceDirectory != "") && (destinationDirectory != "")) || (o365SharedLibraryName != "") || (displaySyncStatus == true)) {
|
||||||
performSyncOK = true;
|
performSyncOK = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +420,7 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if --synchronize && --monitor passed in, exit & display help as these conflict with each other
|
// if --synchronize && --monitor passed in, exit & display help as these conflict with each other
|
||||||
if (cfg.getValueBool("synchronize") && cfg.getValueBool("monitor")) {
|
if (synchronize && monitor) {
|
||||||
writeln("\nERROR: --synchronize and --monitor cannot be used together\n");
|
writeln("\nERROR: --synchronize and --monitor cannot be used together\n");
|
||||||
writeln("Refer to --help to determine which command option you should use.\n");
|
writeln("Refer to --help to determine which command option you should use.\n");
|
||||||
oneDrive.http.shutdown();
|
oneDrive.http.shutdown();
|
||||||
|
@ -246,7 +429,7 @@ int main(string[] args)
|
||||||
|
|
||||||
// Initialize the item database
|
// Initialize the item database
|
||||||
log.vlog("Opening the item database ...");
|
log.vlog("Opening the item database ...");
|
||||||
if (!cfg.getValueBool("dry_run")) {
|
if (!dryRun) {
|
||||||
// Load the items.sqlite3 file as the database
|
// Load the items.sqlite3 file as the database
|
||||||
log.vdebug("Using database file: ", cfg.databaseFilePath);
|
log.vdebug("Using database file: ", cfg.databaseFilePath);
|
||||||
itemDb = new ItemDatabase(cfg.databaseFilePath);
|
itemDb = new ItemDatabase(cfg.databaseFilePath);
|
||||||
|
@ -279,15 +462,15 @@ int main(string[] args)
|
||||||
|
|
||||||
// Configure skip_dir & skip_file from config entries
|
// Configure skip_dir & skip_file from config entries
|
||||||
log.vdebug("Configuring skip_dir ...");
|
log.vdebug("Configuring skip_dir ...");
|
||||||
log.vdebug("skip_dir: ", cfg.getValueString("skip_dir"));
|
log.vdebug("skip_dir: ", cfg.getValue("skip_dir"));
|
||||||
selectiveSync.setDirMask(cfg.getValueString("skip_dir"));
|
selectiveSync.setDirMask(cfg.getValue("skip_dir"));
|
||||||
log.vdebug("Configuring skip_file ...");
|
log.vdebug("Configuring skip_file ...");
|
||||||
log.vdebug("skip_file: ", cfg.getValueString("skip_file"));
|
log.vdebug("skip_file: ", cfg.getValue("skip_file"));
|
||||||
selectiveSync.setFileMask(cfg.getValueString("skip_file"));
|
selectiveSync.setFileMask(cfg.getValue("skip_file"));
|
||||||
|
|
||||||
// Initialize the sync engine
|
// Initialize the sync engine
|
||||||
log.logAndNotify("Initializing the Synchronization Engine ...");
|
log.logAndNotify("Initializing the Synchronization Engine ...");
|
||||||
auto sync = new SyncEngine(cfg, oneDrive, itemDb, selectiveSync);
|
auto sync = new SyncEngine(cfg, oneDrive, itemDb, selectiveSync, dryRun);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!initSyncEngine(sync)) {
|
if (!initSyncEngine(sync)) {
|
||||||
|
@ -295,21 +478,21 @@ int main(string[] args)
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
} catch (CurlException e) {
|
} catch (CurlException e) {
|
||||||
if (!cfg.getValueBool("monitor")) {
|
if (!monitor) {
|
||||||
log.log("\nNo internet connection.");
|
log.log("\nNo Internet connection.");
|
||||||
oneDrive.http.shutdown();
|
oneDrive.http.shutdown();
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We should only set noRemoteDelete in an upload-only scenario
|
// We should only set noRemoteDelete in an upload-only scenario
|
||||||
if ((cfg.getValueBool("upload_only"))&&(cfg.getValueBool("no_remote_delete"))) sync.setNoRemoteDelete();
|
if ((uploadOnly)&&(noRemoteDelete)) sync.setNoRemoteDelete();
|
||||||
|
|
||||||
// Do we configure to disable the upload validation routine
|
// Do we configure to disable the upload validation routine
|
||||||
if (cfg.getValueBool("disable_upload_validation")) sync.setDisableUploadValidation();
|
if(disableUploadValidation) sync.setDisableUploadValidation();
|
||||||
|
|
||||||
// Do we need to validate the syncDir to check for the presence of a '.nosync' file
|
// Do we need to validate the syncDir to check for the presence of a '.nosync' file
|
||||||
if (cfg.getValueBool("check_nomount")) {
|
if (checkMount) {
|
||||||
// we were asked to check the mounts
|
// we were asked to check the mounts
|
||||||
if (exists(syncDir ~ "/.nosync")) {
|
if (exists(syncDir ~ "/.nosync")) {
|
||||||
log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data.");
|
log.logAndNotify("ERROR: .nosync file found. Aborting synchronization process to safeguard data.");
|
||||||
|
@ -319,53 +502,53 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to create or remove a directory?
|
// Do we need to create or remove a directory?
|
||||||
if ((cfg.getValueString("create_directory") != "") || (cfg.getValueString("remove_directory") != "")) {
|
if ((createDirectory != "") || (removeDirectory != "")) {
|
||||||
|
|
||||||
if (cfg.getValueString("create_directory") != "") {
|
if (createDirectory != "") {
|
||||||
// create a directory on OneDrive
|
// create a directory on OneDrive
|
||||||
sync.createDirectoryNoSync(cfg.getValueString("create_directory"));
|
sync.createDirectoryNoSync(createDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.getValueString("remove_directory") != "") {
|
if (removeDirectory != "") {
|
||||||
// remove a directory on OneDrive
|
// remove a directory on OneDrive
|
||||||
sync.deleteDirectoryNoSync(cfg.getValueString("remove_directory"));
|
sync.deleteDirectoryNoSync(removeDirectory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we renaming or moving a directory?
|
// Are we renaming or moving a directory?
|
||||||
if ((cfg.getValueString("source_directory") != "") && (cfg.getValueString("destination_directory") != "")) {
|
if ((sourceDirectory != "") && (destinationDirectory != "")) {
|
||||||
// We are renaming or moving a directory
|
// We are renaming or moving a directory
|
||||||
sync.renameDirectoryNoSync(cfg.getValueString("source_directory"), cfg.getValueString("destination_directory"));
|
sync.renameDirectoryNoSync(sourceDirectory, destinationDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
|
// Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
|
||||||
if (cfg.getValueString("get_o365_drive_id") != ""){
|
if (o365SharedLibraryName != ""){
|
||||||
sync.querySiteCollectionForDriveID(cfg.getValueString("get_o365_drive_id"));
|
sync.querySiteCollectionForDriveID(o365SharedLibraryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we displaying the sync status of the client?
|
// Are we displaying the sync status of the client?
|
||||||
if (cfg.getValueBool("display_sync_status")) {
|
if (displaySyncStatus) {
|
||||||
string remotePath = "/";
|
string remotePath = "/";
|
||||||
string localPath = ".";
|
string localPath = ".";
|
||||||
|
|
||||||
// Are we doing a single directory check?
|
// Are we doing a single directory check?
|
||||||
if (cfg.getValueString("single_directory") != ""){
|
if (singleDirectory != ""){
|
||||||
// Need two different path strings here
|
// Need two different path strings here
|
||||||
remotePath = cfg.getValueString("single_directory");
|
remotePath = singleDirectory;
|
||||||
localPath = cfg.getValueString("single_directory");
|
localPath = singleDirectory;
|
||||||
}
|
}
|
||||||
sync.queryDriveForChanges(remotePath);
|
sync.queryDriveForChanges(remotePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Are we performing a sync, resync or monitor operation?
|
// Are we performing a sync, resync or monitor operation?
|
||||||
if ((cfg.getValueBool("synchronize")) || (cfg.getValueBool("resync")) || (cfg.getValueBool("monitor"))) {
|
if ((synchronize) || (resync) || (monitor)) {
|
||||||
|
|
||||||
if ((cfg.getValueBool("synchronize")) || (cfg.getValueBool("resync"))) {
|
if ((synchronize) || (resync)) {
|
||||||
if (online) {
|
if (online) {
|
||||||
// Check user entry for local path - the above chdir means we are already in ~/OneDrive/ thus singleDirectory is local to this path
|
// Check user entry for local path - the above chdir means we are already in ~/OneDrive/ thus singleDirectory is local to this path
|
||||||
if (cfg.getValueString("single_directory") != ""){
|
if (singleDirectory != ""){
|
||||||
// Does the directory we want to sync actually exist?
|
// Does the directory we want to sync actually exist?
|
||||||
if (!exists(cfg.getValueString("single_directory"))){
|
if (!exists(singleDirectory)){
|
||||||
// the requested directory does not exist ..
|
// the requested directory does not exist ..
|
||||||
log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path");
|
log.logAndNotify("ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path");
|
||||||
oneDrive.http.shutdown();
|
oneDrive.http.shutdown();
|
||||||
|
@ -374,13 +557,13 @@ int main(string[] args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the sync
|
// Perform the sync
|
||||||
performSync(sync, cfg.getValueString("single_directory"), cfg.getValueBool("download_only"), cfg.getValueBool("local_first"), cfg.getValueBool("upload_only"), LOG_NORMAL, true);
|
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly, LOG_NORMAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cfg.getValueBool("monitor")) {
|
if (monitor) {
|
||||||
log.logAndNotify("Initializing monitor ...");
|
log.logAndNotify("Initializing monitor ...");
|
||||||
log.log("OneDrive monitor interval (seconds): ", cfg.getValueLong("monitor_interval"));
|
log.log("OneDrive monitor interval (seconds): ", to!long(cfg.getValue("monitor_interval")));
|
||||||
Monitor m = new Monitor(selectiveSync);
|
Monitor m = new Monitor(selectiveSync);
|
||||||
m.onDirCreated = delegate(string path) {
|
m.onDirCreated = delegate(string path) {
|
||||||
log.vlog("[M] Directory created: ", path);
|
log.vlog("[M] Directory created: ", path);
|
||||||
|
@ -432,44 +615,33 @@ int main(string[] args)
|
||||||
signal(SIGTERM, &exitHandler);
|
signal(SIGTERM, &exitHandler);
|
||||||
|
|
||||||
// initialise the monitor class
|
// initialise the monitor class
|
||||||
if (!cfg.getValueBool("download_only")) m.init(cfg, cfg.getValueLong("verbose") > 0, cfg.getValueBool("skip_symlinks"), cfg.getValueBool("check_nosync"));
|
if (cfg.getValue("skip_symlinks") == "true") skipSymlinks = true;
|
||||||
|
if (cfg.getValue("check_nosync") == "true") checkNoSync = true;
|
||||||
|
if (!downloadOnly) m.init(cfg, verbose, skipSymlinks, checkNoSync);
|
||||||
// monitor loop
|
// monitor loop
|
||||||
immutable auto checkInterval = dur!"seconds"(cfg.getValueLong("monitor_interval"));
|
immutable auto checkInterval = dur!"seconds"(to!long(cfg.getValue("monitor_interval")));
|
||||||
immutable auto logInterval = cfg.getValueLong("monitor_log_frequency");
|
immutable auto logInterval = to!long(cfg.getValue("monitor_log_frequency"));
|
||||||
immutable auto fullScanFrequency = cfg.getValueLong("monitor_fullscan_frequency");
|
|
||||||
auto lastCheckTime = MonoTime.currTime();
|
auto lastCheckTime = MonoTime.currTime();
|
||||||
auto logMonitorCounter = 0;
|
auto logMonitorCounter = 0;
|
||||||
auto fullScanCounter = 0;
|
|
||||||
bool fullScanRequired = true;
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (!cfg.getValueBool("download_only")) m.update(online);
|
if (!downloadOnly) m.update(online);
|
||||||
auto currTime = MonoTime.currTime();
|
auto currTime = MonoTime.currTime();
|
||||||
if (currTime - lastCheckTime > checkInterval) {
|
if (currTime - lastCheckTime > checkInterval) {
|
||||||
// log monitor output suppression
|
|
||||||
logMonitorCounter += 1;
|
logMonitorCounter += 1;
|
||||||
if (logMonitorCounter > logInterval)
|
if (logMonitorCounter > logInterval)
|
||||||
logMonitorCounter = 1;
|
logMonitorCounter = 1;
|
||||||
|
|
||||||
// full scan of sync_dir
|
|
||||||
fullScanCounter += 1;
|
|
||||||
if (fullScanCounter > fullScanFrequency){
|
|
||||||
fullScanCounter = 1;
|
|
||||||
fullScanRequired = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.logAndNotify("DEBUG trying to create checkpoint");
|
// log.logAndNotify("DEBUG trying to create checkpoint");
|
||||||
// auto res = itemdb.db_checkpoint();
|
// auto res = itemdb.db_checkpoint();
|
||||||
// log.logAndNotify("Checkpoint return: ", res);
|
// log.logAndNotify("Checkpoint return: ", res);
|
||||||
// itemdb.dump_open_statements();
|
// itemdb.dump_open_statements();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!initSyncEngine(sync)) {
|
if (!initSyncEngine(sync)) {
|
||||||
oneDrive.http.shutdown();
|
oneDrive.http.shutdown();
|
||||||
return EXIT_FAILURE;
|
return EXIT_FAILURE;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
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);
|
performSync(sync, singleDirectory, downloadOnly, localFirst, uploadOnly, (logMonitorCounter == logInterval ? MONITOR_LOG_QUIET : MONITOR_LOG_SILENT));
|
||||||
if (!cfg.getValueBool("download_only")) {
|
if (!downloadOnly) {
|
||||||
// discard all events that may have been generated by the sync
|
// discard all events that may have been generated by the sync
|
||||||
m.update(false);
|
m.update(false);
|
||||||
}
|
}
|
||||||
|
@ -484,7 +656,6 @@ int main(string[] args)
|
||||||
log.log("Cannot initialize connection to OneDrive");
|
log.log("Cannot initialize connection to OneDrive");
|
||||||
}
|
}
|
||||||
// performSync complete, set lastCheckTime to current time
|
// performSync complete, set lastCheckTime to current time
|
||||||
fullScanRequired = false;
|
|
||||||
lastCheckTime = MonoTime.currTime();
|
lastCheckTime = MonoTime.currTime();
|
||||||
GC.collect();
|
GC.collect();
|
||||||
}
|
}
|
||||||
|
@ -500,7 +671,7 @@ int main(string[] args)
|
||||||
destroy(itemDb);
|
destroy(itemDb);
|
||||||
|
|
||||||
// --dry-run temp database cleanup
|
// --dry-run temp database cleanup
|
||||||
if (cfg.getValueBool("dry_run")) {
|
if (dryRun) {
|
||||||
if (exists(cfg.databaseFilePathDryRun)) {
|
if (exists(cfg.databaseFilePathDryRun)) {
|
||||||
// remove the file
|
// remove the file
|
||||||
log.vdebug("Removing items-dryrun.sqlite3 as dry run operations complete");
|
log.vdebug("Removing items-dryrun.sqlite3 as dry run operations complete");
|
||||||
|
@ -530,7 +701,7 @@ bool initSyncEngine(SyncEngine sync)
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to synchronize the folder three times
|
// try to synchronize the folder three times
|
||||||
void performSync(SyncEngine sync, string singleDirectory, bool downloadOnly, bool localFirst, bool uploadOnly, long logLevel, bool fullScanRequired)
|
void performSync(SyncEngine sync, string singleDirectory, bool downloadOnly, bool localFirst, bool uploadOnly, long logLevel)
|
||||||
{
|
{
|
||||||
int count;
|
int count;
|
||||||
string remotePath = "/";
|
string remotePath = "/";
|
||||||
|
@ -589,17 +760,12 @@ void performSync(SyncEngine sync, string singleDirectory, bool downloadOnly, boo
|
||||||
// sync from OneDrive first before uploading files to OneDrive
|
// sync from OneDrive first before uploading files to OneDrive
|
||||||
if (logLevel < MONITOR_LOG_SILENT) log.log("Syncing changes from OneDrive ...");
|
if (logLevel < MONITOR_LOG_SILENT) log.log("Syncing changes from OneDrive ...");
|
||||||
sync.applyDifferences();
|
sync.applyDifferences();
|
||||||
// Is a full scan of the entire sync_dir required?
|
// is this a download only request?
|
||||||
if (fullScanRequired) {
|
if (!downloadOnly) {
|
||||||
// is this a download only request?
|
// process local changes
|
||||||
if (!downloadOnly) {
|
sync.scanForDifferences(localPath);
|
||||||
// process local changes walking the entire path checking for changes
|
// ensure that the current remote state is updated locally
|
||||||
// in monitor mode all local changes are captured via inotify
|
sync.applyDifferences();
|
||||||
// thus scanning every 'monitor_interval' (default 45 seconds) for local changes is excessive and not required
|
|
||||||
sync.scanForDifferences(localPath);
|
|
||||||
// ensure that the current remote state is updated locally
|
|
||||||
sync.applyDifferences();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -635,4 +801,41 @@ extern(C) nothrow @nogc @system void exitHandler(int value) {
|
||||||
} catch(Exception e) {}
|
} catch(Exception e) {}
|
||||||
exit(0);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,8 @@ import progress;
|
||||||
import config;
|
import config;
|
||||||
static import log;
|
static import log;
|
||||||
shared bool debugResponse = false;
|
shared bool debugResponse = false;
|
||||||
private bool dryRun = false;
|
shared bool dryRun = false;
|
||||||
private bool simulateNoRefreshTokenFile = false;
|
shared bool simulateNoRefreshTokenFile = false;
|
||||||
|
|
||||||
private immutable {
|
private immutable {
|
||||||
// Client Identifier
|
// Client Identifier
|
||||||
|
@ -66,7 +66,7 @@ final class OneDriveApi
|
||||||
// if true, every new access token is printed
|
// if true, every new access token is printed
|
||||||
bool printAccessToken;
|
bool printAccessToken;
|
||||||
|
|
||||||
this(Config cfg)
|
this(Config cfg, bool debugHttp, bool forceHTTP11, bool dryRun, bool simulateNoRefreshTokenFile)
|
||||||
{
|
{
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
http = HTTP();
|
http = HTTP();
|
||||||
|
@ -94,36 +94,36 @@ final class OneDriveApi
|
||||||
http.maxRedirects(5);
|
http.maxRedirects(5);
|
||||||
|
|
||||||
// Do we enable curl debugging?
|
// Do we enable curl debugging?
|
||||||
if (cfg.getValueBool("debug_https")) {
|
if (debugHttp) {
|
||||||
http.verbose = true;
|
http.verbose = true;
|
||||||
.debugResponse = true;
|
.debugResponse = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// What version of HTTP protocol do we use?
|
// What version of HTTP protocol do we use?
|
||||||
// Curl >= 7.62.0 defaults to http2 for a significant number of operations
|
// Curl >= 7.62.0 defaults to http2 for a significant number of operations
|
||||||
if (cfg.getValueBool("force_http_11")) {
|
if (forceHTTP11) {
|
||||||
log.vdebug("Downgrading all HTTP operations to HTTP 1.1");
|
log.vdebug("Downgrading all HTTP operations to HTTP 1.1");
|
||||||
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
|
// Downgrade to HTTP 1.1 - yes version = 2 is HTTP 1.1
|
||||||
http.handle.set(CurlOption.http_version,2);
|
http.handle.set(CurlOption.http_version,2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we set the dryRun handlers?
|
// Do we set the dryRun handlers?
|
||||||
if (cfg.getValueBool("dry_run")) {
|
if (dryRun) {
|
||||||
.dryRun = true;
|
.dryRun = true;
|
||||||
if (cfg.getValueBool("logout")) {
|
}
|
||||||
.simulateNoRefreshTokenFile = true;
|
if (simulateNoRefreshTokenFile) {
|
||||||
}
|
.simulateNoRefreshTokenFile = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool init()
|
bool init()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
driveId = cfg.getValueString("drive_id");
|
driveId = cfg.getValue("drive_id");
|
||||||
if (driveId.length) {
|
if (driveId.length) {
|
||||||
driveUrl = driveByIdUrl ~ driveId;
|
driveUrl = driveByIdUrl ~ driveId;
|
||||||
itemByIdUrl = driveUrl ~ "/items";
|
itemByIdUrl = driveUrl ~ "/items";
|
||||||
itemByPathUrl = driveUrl ~ "/root:/";
|
itemByPathUrl = driveUrl ~ "/root:/";
|
||||||
}
|
}
|
||||||
} catch (Exception e) {}
|
} catch (Exception e) {}
|
||||||
|
|
||||||
|
@ -265,7 +265,7 @@ final class OneDriveApi
|
||||||
// string itemByPathUrl = "https://graph.microsoft.com/v1.0/me/drive/root:/";
|
// string itemByPathUrl = "https://graph.microsoft.com/v1.0/me/drive/root:/";
|
||||||
if ((path == ".")||(path == "/")) url = driveUrl ~ "/root/";
|
if ((path == ".")||(path == "/")) url = driveUrl ~ "/root/";
|
||||||
else url = itemByPathUrl ~ encodeComponent(path) ~ ":/";
|
else url = itemByPathUrl ~ encodeComponent(path) ~ ":/";
|
||||||
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size";
|
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference";
|
||||||
return get(url);
|
return get(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -277,7 +277,7 @@ final class OneDriveApi
|
||||||
const(char)[] url;
|
const(char)[] url;
|
||||||
// string driveByIdUrl = "https://graph.microsoft.com/v1.0/drives/";
|
// string driveByIdUrl = "https://graph.microsoft.com/v1.0/drives/";
|
||||||
url = driveByIdUrl ~ driveId ~ "/items/" ~ id;
|
url = driveByIdUrl ~ driveId ~ "/items/" ~ id;
|
||||||
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference,size";
|
url ~= "?select=id,name,eTag,cTag,deleted,file,folder,root,fileSystemInfo,remoteItem,parentReference";
|
||||||
return get(url);
|
return get(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -730,8 +730,8 @@ final class OneDriveApi
|
||||||
case 400:
|
case 400:
|
||||||
// Bad Request .. how should we act?
|
// Bad Request .. how should we act?
|
||||||
log.vlog("OneDrive returned a 'HTTP 400 - Bad Request' - gracefully handling error");
|
log.vlog("OneDrive returned a 'HTTP 400 - Bad Request' - gracefully handling error");
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// 412 - Precondition Failed
|
// 412 - Precondition Failed
|
||||||
case 412:
|
case 412:
|
||||||
log.vlog("OneDrive returned a 'HTTP 412 - Precondition Failed' - gracefully handling error");
|
log.vlog("OneDrive returned a 'HTTP 412 - Precondition Failed' - gracefully handling error");
|
||||||
|
|
122
src/sync.d
122
src/sync.d
|
@ -205,7 +205,7 @@ final class SyncEngine
|
||||||
// sync engine dryRun flag
|
// sync engine dryRun flag
|
||||||
private bool dryRun = false;
|
private bool dryRun = false;
|
||||||
|
|
||||||
this(Config cfg, OneDriveApi onedrive, ItemDatabase itemdb, SelectiveSync selectiveSync)
|
this(Config cfg, OneDriveApi onedrive, ItemDatabase itemdb, SelectiveSync selectiveSync, bool dryRun)
|
||||||
{
|
{
|
||||||
assert(onedrive && itemdb && selectiveSync);
|
assert(onedrive && itemdb && selectiveSync);
|
||||||
this.cfg = cfg;
|
this.cfg = cfg;
|
||||||
|
@ -213,7 +213,7 @@ final class SyncEngine
|
||||||
this.itemdb = itemdb;
|
this.itemdb = itemdb;
|
||||||
this.selectiveSync = selectiveSync;
|
this.selectiveSync = selectiveSync;
|
||||||
// session = UploadSession(onedrive, cfg.uploadStateFilePath);
|
// session = UploadSession(onedrive, cfg.uploadStateFilePath);
|
||||||
this.dryRun = cfg.getValueBool("dry_run");
|
this.dryRun = dryRun;
|
||||||
}
|
}
|
||||||
|
|
||||||
void reset()
|
void reset()
|
||||||
|
@ -240,7 +240,7 @@ final class SyncEngine
|
||||||
// OneDrive responded with 400 error: Bad Request
|
// OneDrive responded with 400 error: Bad Request
|
||||||
log.error("\nERROR: OneDrive returned a 'HTTP 400 Bad Request' - Cannot Initialize Sync Engine");
|
log.error("\nERROR: OneDrive returned a 'HTTP 400 Bad Request' - Cannot Initialize Sync Engine");
|
||||||
// Check this
|
// Check this
|
||||||
if (cfg.getValueString("drive_id").length) {
|
if (cfg.getValue("drive_id").length) {
|
||||||
log.error("ERROR: Check your 'drive_id' entry in your configuration file as it may be incorrect\n");
|
log.error("ERROR: Check your 'drive_id' entry in your configuration file as it may be incorrect\n");
|
||||||
}
|
}
|
||||||
// Must exit here
|
// Must exit here
|
||||||
|
@ -308,7 +308,6 @@ final class SyncEngine
|
||||||
void setDisableUploadValidation()
|
void setDisableUploadValidation()
|
||||||
{
|
{
|
||||||
disableUploadValidation = true;
|
disableUploadValidation = true;
|
||||||
log.vdebug("documentLibrary account type - flagging to disable upload validation checks due to Microsoft SharePoint file modification enrichments");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -604,7 +603,7 @@ final class SyncEngine
|
||||||
if (("value" in changes) != null) {
|
if (("value" in changes) != null) {
|
||||||
auto nrChanges = count(changes["value"].array);
|
auto nrChanges = count(changes["value"].array);
|
||||||
|
|
||||||
if (nrChanges >= cfg.getValueLong("min_notif_changes")) {
|
if (nrChanges >= to!long(cfg.getValue("min_notif_changes"))) {
|
||||||
log.logAndNotify("Processing ", nrChanges, " changes");
|
log.logAndNotify("Processing ", nrChanges, " changes");
|
||||||
} else {
|
} else {
|
||||||
// There are valid changes
|
// There are valid changes
|
||||||
|
@ -757,11 +756,8 @@ final class SyncEngine
|
||||||
if (unwanted) log.vdebug("Flagging as unwanted: find(item.parentId).length != 0");
|
if (unwanted) log.vdebug("Flagging as unwanted: find(item.parentId).length != 0");
|
||||||
// Check if this is a directory to skip
|
// Check if this is a directory to skip
|
||||||
if (!unwanted) {
|
if (!unwanted) {
|
||||||
// Only check path if config is != ""
|
unwanted = selectiveSync.isDirNameExcluded(item.name);
|
||||||
if (cfg.getValueString("skip_dir") != "") {
|
if (unwanted) log.vlog("Skipping item - excluded by skip_dir config: ", item.name);
|
||||||
unwanted = selectiveSync.isDirNameExcluded(item.name);
|
|
||||||
if (unwanted) log.vlog("Skipping item - excluded by skip_dir config: ", item.name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Check if this is a file to skip
|
// Check if this is a file to skip
|
||||||
if (!unwanted) {
|
if (!unwanted) {
|
||||||
|
@ -802,7 +798,7 @@ final class SyncEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip downloading dot files if configured
|
// skip downloading dot files if configured
|
||||||
if (cfg.getValueBool("skip_dotfiles")) {
|
if (cfg.getValue("skip_dotfiles") == "true") {
|
||||||
if (isDotFile(path)) {
|
if (isDotFile(path)) {
|
||||||
log.vlog("Skipping item - .file or .folder: ", path);
|
log.vlog("Skipping item - .file or .folder: ", path);
|
||||||
unwanted = true;
|
unwanted = true;
|
||||||
|
@ -1364,45 +1360,28 @@ final class SyncEngine
|
||||||
writeln("done.");
|
writeln("done.");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// OneDrive Business Account
|
// OneDrive Business Account - always use a session to upload
|
||||||
// We need to always use a session to upload, but handle the changed file correctly
|
writeln("");
|
||||||
if (accountType == "business"){
|
|
||||||
// For logging consistency
|
try {
|
||||||
writeln("");
|
response = session.upload(path, item.driveId, item.parentId, baseName(path));
|
||||||
try {
|
} catch (OneDriveException e) {
|
||||||
response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag);
|
|
||||||
} catch (OneDriveException e) {
|
// Resolve https://github.com/abraunegg/onedrive/issues/36
|
||||||
// Resolve https://github.com/abraunegg/onedrive/issues/36
|
if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) {
|
||||||
if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) {
|
// The file is currently checked out or locked for editing by another user
|
||||||
// The file is currently checked out or locked for editing by another user
|
// We cant upload this file at this time
|
||||||
// We cant upload this file at this time
|
writeln(" skipped.");
|
||||||
writeln("skipped.");
|
log.fileOnly("Uploading modified file ", path, " ... skipped.");
|
||||||
log.fileOnly("Uploading modified file ", path, " ... skipped.");
|
writeln("", path, " is currently checked out or locked for editing by another user.");
|
||||||
writeln("", path, " is currently checked out or locked for editing by another user.");
|
log.fileOnly(path, " is currently checked out or locked for editing by another user.");
|
||||||
log.fileOnly(path, " is currently checked out or locked for editing by another user.");
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
// what is this error?????
|
|
||||||
else throw e;
|
|
||||||
}
|
}
|
||||||
// As the session.upload includes the last modified time, save the response
|
|
||||||
saveItem(response);
|
|
||||||
}
|
}
|
||||||
// OneDrive documentLibrary
|
|
||||||
if (accountType == "documentLibrary"){
|
|
||||||
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
|
|
||||||
// This means, as a session upload, on 'completion' the file is 'moved' and generates a 404 ......
|
|
||||||
// Delete record from the local database - file will be uploaded as a new file
|
|
||||||
writeln("skipped.");
|
|
||||||
log.fileOnly("Uploading modified file ", path, " ... skipped.");
|
|
||||||
log.vlog("Skip Reason: Microsoft Sharepoint 'enrichment' after upload issue");
|
|
||||||
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
|
|
||||||
itemdb.deleteById(item.driveId, item.id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// log line completion
|
|
||||||
writeln("done.");
|
writeln("done.");
|
||||||
|
// As the session.upload includes the last modified time, save the response
|
||||||
|
saveItem(response);
|
||||||
}
|
}
|
||||||
log.fileOnly("Uploading modified file ", path, " ... done.");
|
log.fileOnly("Uploading modified file ", path, " ... done.");
|
||||||
// use the cTag instead of the eTag because OneDrive may update the metadata of files AFTER they have been uploaded via simple upload
|
// use the cTag instead of the eTag because OneDrive may update the metadata of files AFTER they have been uploaded via simple upload
|
||||||
|
@ -1470,7 +1449,7 @@ final class SyncEngine
|
||||||
// path is less than maxPathLength
|
// path is less than maxPathLength
|
||||||
|
|
||||||
// skip dot files if configured
|
// skip dot files if configured
|
||||||
if (cfg.getValueBool("skip_dotfiles")) {
|
if (cfg.getValue("skip_dotfiles") == "true") {
|
||||||
if (isDotFile(path)) {
|
if (isDotFile(path)) {
|
||||||
log.vlog("Skipping item - .file or .folder: ", path);
|
log.vlog("Skipping item - .file or .folder: ", path);
|
||||||
return;
|
return;
|
||||||
|
@ -1478,7 +1457,7 @@ final class SyncEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do we need to check for .nosync? Only if --check-for-nosync was passed in
|
// Do we need to check for .nosync? Only if --check-for-nosync was passed in
|
||||||
if (cfg.getValueBool("check_nosync")) {
|
if (cfg.getValue("check_nosync") == "true") {
|
||||||
if (exists(path ~ "/.nosync")) {
|
if (exists(path ~ "/.nosync")) {
|
||||||
log.vlog("Skipping item - .nosync found & --check-for-nosync enabled: ", path);
|
log.vlog("Skipping item - .nosync found & --check-for-nosync enabled: ", path);
|
||||||
return;
|
return;
|
||||||
|
@ -1487,7 +1466,7 @@ final class SyncEngine
|
||||||
|
|
||||||
if (isSymlink(path)) {
|
if (isSymlink(path)) {
|
||||||
// if config says so we skip all symlinked items
|
// if config says so we skip all symlinked items
|
||||||
if (cfg.getValueBool("skip_symlinks")) {
|
if (cfg.getValue("skip_symlinks") == "true") {
|
||||||
log.vlog("Skipping item - skip symbolic links configured: ", path);
|
log.vlog("Skipping item - skip symbolic links configured: ", path);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
@ -1521,12 +1500,9 @@ final class SyncEngine
|
||||||
if (path != ".") {
|
if (path != ".") {
|
||||||
if (isDir(path)) {
|
if (isDir(path)) {
|
||||||
log.vdebug("Checking path: ", path);
|
log.vdebug("Checking path: ", path);
|
||||||
// Only check path if config is != ""
|
if (selectiveSync.isDirNameExcluded(strip(path,"./"))) {
|
||||||
if (cfg.getValueString("skip_dir") != "") {
|
log.vlog("Skipping item - excluded by skip_dir config: ", path);
|
||||||
if (selectiveSync.isDirNameExcluded(strip(path,"./"))) {
|
return;
|
||||||
log.vlog("Skipping item - excluded by skip_dir config: ", path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isFile(path)) {
|
if (isFile(path)) {
|
||||||
|
@ -1910,32 +1886,11 @@ final class SyncEngine
|
||||||
// use the cTag instead of the eTag because Onedrive may update the metadata of files AFTER they have been uploaded
|
// use the cTag instead of the eTag because Onedrive may update the metadata of files AFTER they have been uploaded
|
||||||
uploadLastModifiedTime(parent.driveId, id, cTag, mtime);
|
uploadLastModifiedTime(parent.driveId, id, cTag, mtime);
|
||||||
} else {
|
} else {
|
||||||
// OneDrive Business account modified file upload handling
|
// OneDrive Business account upload handling
|
||||||
if (accountType == "business"){
|
writeln("");
|
||||||
writeln("");
|
response = session.upload(path, parent.driveId, parent.id, baseName(path));
|
||||||
// session upload
|
writeln(" done.");
|
||||||
response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive["eTag"].str);
|
saveItem(response);
|
||||||
writeln(" done.");
|
|
||||||
saveItem(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
// OneDrive SharePoint account modified file upload handling
|
|
||||||
if (accountType == "documentLibrary"){
|
|
||||||
// If this is a Microsoft SharePoint site, we need to remove the existing file before upload
|
|
||||||
onedrive.deleteById(fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str);
|
|
||||||
// Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint.
|
|
||||||
// This means, as a session upload, on 'completion' the file is 'moved' and generates a 404 ......
|
|
||||||
// Upload modified file via simpleUpload to avoid the session 404 problem
|
|
||||||
response = onedrive.simpleUpload(path, parent.driveId, parent.id, baseName(path));
|
|
||||||
writeln(" done.");
|
|
||||||
saveItem(response);
|
|
||||||
// So - now the 'local' and 'remote' file is technically DIFFERENT ... thanks Microsoft .. NO way to disable this stupidity
|
|
||||||
// Download the Microsoft 'modified' file so 'local' is now in sync
|
|
||||||
log.vlog("Due to Microsoft Sharepoint 'enrichment' of files, downloading 'enriched' file to ensure local file is in-sync");
|
|
||||||
log.vlog("See: https://github.com/OneDrive/onedrive-api-docs/issues/935 for further details");
|
|
||||||
auto fileSize = response["size"].integer;
|
|
||||||
onedrive.downloadById(response["parentReference"]["driveId"].str, response["id"].str, path, fileSize);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// we are --dry-run - simulate the file upload
|
// we are --dry-run - simulate the file upload
|
||||||
|
@ -2066,11 +2021,10 @@ final class SyncEngine
|
||||||
// Takes a JSON input and formats to an item which can be used by the database
|
// Takes a JSON input and formats to an item which can be used by the database
|
||||||
Item item = makeItem(jsonItem);
|
Item item = makeItem(jsonItem);
|
||||||
// Add to the local database
|
// Add to the local database
|
||||||
log.vdebug("Adding to database: ", item);
|
|
||||||
itemdb.upsert(item);
|
itemdb.upsert(item);
|
||||||
} else {
|
} else {
|
||||||
// log error
|
// log error
|
||||||
log.error("ERROR: OneDrive response missing required 'id' element");
|
log.error("ERROR: OneDrive response missing required 'id' element:");
|
||||||
log.error("ERROR: ", jsonItem);
|
log.error("ERROR: ", jsonItem);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue