2018-12-28 03:19:20 +01:00
import core.stdc.stdlib : EXIT_SUCCESS , EXIT_FAILURE , exit ;
2015-09-20 21:21:51 +02:00
import core.memory , core . time , core . thread ;
2018-12-23 01:15:10 +01:00
import std.getopt , std . file , std . path , std . process , std . stdio , std . conv , std . algorithm . searching , std . string ;
2017-03-24 22:30:03 +01:00
import config , itemdb , monitor , onedrive , selective , sync , util ;
2018-08-13 23:21:11 +02:00
import std.net.curl : CurlException ;
2018-12-28 03:19:20 +01:00
import core.stdc.signal ;
import std.traits ;
2016-08-04 23:35:58 +02:00
static import log ;
2015-09-01 20:45:34 +02:00
2018-12-28 03:19:20 +01:00
OneDriveApi oneDrive ;
ItemDatabase itemDb ;
2019-02-26 21:21:23 +01:00
enum MONITOR_LOG_SILENT = 2 ;
enum MONITOR_LOG_QUIET = 1 ;
enum LOG_NORMAL = 0 ;
2016-08-04 23:35:58 +02:00
int main ( string [ ] args )
2015-09-01 20:45:34 +02:00
{
2018-12-23 01:15:10 +01:00
// Disable buffering on stdout
stdout . setvbuf ( 0 , _IONBF ) ;
2018-04-13 01:33:16 +02:00
2018-12-23 01:15:10 +01:00
// Application Option Variables
// Add a check mounts option to resolve https://github.com/abraunegg/onedrive/issues/8
2019-01-05 19:43:44 +01:00
bool checkMount = false ;
2016-08-04 23:35:58 +02:00
// configuration directory
2018-12-23 01:15:10 +01:00
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
2019-01-05 19:43:44 +01:00
bool debugHttp = false ;
2018-12-23 01:15:10 +01:00
// Do not use notifications in monitor mode
bool disableNotifications = false ;
// Display application configuration but do not sync
bool displayConfiguration = false ;
2018-12-28 02:26:03 +01:00
// Display sync status
bool displaySyncStatus = false ;
2018-04-07 09:06:57 +02:00
// only download remote changes
2019-01-05 19:43:44 +01:00
bool downloadOnly = false ;
2018-12-23 01:15:10 +01:00
// Does the user want to disable upload validation - https://github.com/abraunegg/onedrive/issues/205
// SharePoint will associate some metadata from the library the file is uploaded to directly in the file - thus change file size & checksums
bool disableUploadValidation = false ;
// Do we enable a log file
bool enableLogFile = false ;
2019-01-05 19:43:44 +01:00
// 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 ;
2018-12-23 01:15:10 +01:00
// SharePoint / Office 365 Shared Library name to query
string o365SharedLibraryName ;
// Local sync - Upload local changes first before downloading changes from OneDrive
2019-01-05 19:43:44 +01:00
bool localFirst = false ;
2016-08-05 00:12:58 +02:00
// remove the current user and sync state
2019-01-05 19:43:44 +01:00
bool logout = false ;
2018-12-23 01:15:10 +01:00
// enable monitor mode
2019-01-05 19:43:44 +01:00
bool monitor = false ;
2018-12-23 01:15:10 +01:00
// Add option for no remote delete
2019-01-05 19:43:44 +01:00
bool noRemoteDelete = false ;
2017-05-28 20:14:50 +02:00
// print the access token
2019-01-05 19:43:44 +01:00
bool printAccessToken = false ;
2018-12-23 01:15:10 +01:00
// force a full resync
2019-01-05 19:43:44 +01:00
bool resync = false ;
2018-03-14 05:43:40 +01:00
// Remove a single directory on OneDrive
string removeDirectory ;
2018-12-23 01:15:10 +01:00
// This allows for selective directory syncing instead of everything under ~/OneDrive/
string singleDirectory ;
2019-02-24 07:19:45 +01:00
// Skip dot files & folders - eg .file or /.folder/
bool skipDotFiles = false ;
2018-12-23 01:15:10 +01:00
// Add option to skip symlinks
2019-01-05 19:43:44 +01:00
bool skipSymlinks = false ;
2018-03-14 05:43:40 +01:00
// The source directory if we are using the OneDrive client to rename a directory
string sourceDirectory ;
2018-12-23 01:15:10 +01:00
// override the sync directory
string syncDirName ;
2018-03-14 05:43:40 +01:00
// 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
2019-01-05 19:43:44 +01:00
bool synchronize = false ;
2018-04-07 09:06:57 +02:00
// Upload Only
2019-01-05 19:43:44 +01:00
bool uploadOnly = false ;
2018-12-23 01:15:10 +01:00
// enable verbose logging
2019-01-05 19:43:44 +01:00
bool verbose = false ;
2018-12-23 01:15:10 +01:00
// print the version and exit
2019-01-05 19:43:44 +01:00
bool printVersion = false ;
2018-08-13 23:21:11 +02:00
2018-12-23 01:15:10 +01:00
// Application Startup option validation
2015-09-14 19:21:06 +02:00
try {
auto opt = getopt (
args ,
2016-08-04 23:35:58 +02:00
std . getopt . config . bundling ,
2017-07-14 11:31:16 +02:00
std . getopt . config . caseSensitive ,
2018-06-26 22:23:17 +02:00
"check-for-nomount" , "Check for the presence of .nosync in the syncdir root. If found, do not perform sync." , & checkMount ,
2017-07-14 11:31:16 +02:00
"confdir" , "Set the directory used to store the configuration files" , & configDirName ,
2018-03-14 05:43:40 +01:00
"create-directory" , "Create a directory on OneDrive - no sync will be performed." , & createDirectory ,
"destination-directory" , "Destination directory for renamed or move on OneDrive - no sync will be performed." , & destinationDirectory ,
2018-04-15 12:02:39 +02:00
"debug-https" , "Debug OneDrive HTTPS communication." , & debugHttp ,
2018-12-05 20:19:00 +01:00
"disable-notifications" , "Do not use desktop notifications in monitor mode." , & disableNotifications ,
2018-12-19 19:42:28 +01:00
"display-config" , "Display what options the client will use as currently configured - no sync will be performed." , & displayConfiguration ,
2018-12-28 02:26:03 +01:00
"display-sync-status" , "Display the sync status of the client - no sync will be performed." , & displaySyncStatus ,
2018-11-23 21:13:16 +01:00
"download-only|d" , "Only download remote changes" , & downloadOnly ,
2018-11-23 20:26:30 +01:00
"disable-upload-validation" , "Disable upload validation when uploading to OneDrive" , & disableUploadValidation ,
2018-11-23 21:13:16 +01:00
"enable-logging" , "Enable client activity to a separate log file" , & enableLogFile ,
2019-01-05 19:43:44 +01:00
"force-http-1.1" , "Force the use of HTTP 1.1 for all operations" , & forceHTTP11 ,
2018-12-04 00:59:23 +01:00
"get-O365-drive-id" , "Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library" , & o365SharedLibraryName ,
2018-03-14 05:43:40 +01:00
"local-first" , "Synchronize from the local directory source first, before downloading changes from OneDrive." , & localFirst ,
2017-07-14 11:31:16 +02:00
"logout" , "Logout the current user" , & logout ,
"monitor|m" , "Keep monitoring for local and remote changes" , & monitor ,
2018-08-05 02:43:31 +02:00
"no-remote-delete" , "Do not delete local file 'deletes' from OneDrive when using --upload-only" , & noRemoteDelete ,
2017-07-14 11:31:16 +02:00
"print-token" , "Print the access token, useful for debugging" , & printAccessToken ,
"resync" , "Forget the last saved state, perform a full sync" , & resync ,
2018-03-14 05:43:40 +01:00
"remove-directory" , "Remove a directory on OneDrive - no sync will be performed." , & removeDirectory ,
"single-directory" , "Specify a single local directory within the OneDrive root to sync." , & singleDirectory ,
2019-02-24 07:19:45 +01:00
"skip-dot-files" , "Skip dot files and folders from syncing" , & skipDotFiles ,
2018-08-02 22:02:10 +02:00
"skip-symlinks" , "Skip syncing of symlinks" , & skipSymlinks ,
2018-03-14 05:43:40 +01:00
"source-directory" , "Source directory to rename or move on OneDrive - no sync will be performed." , & sourceDirectory ,
2018-12-28 10:01:50 +01:00
"syncdir" , "Specify the local directory used for synchronization to OneDrive" , & syncDirName ,
2018-03-14 05:43:40 +01:00
"synchronize" , "Perform a synchronization" , & synchronize ,
2018-04-07 09:06:57 +02:00
"upload-only" , "Only upload to OneDrive, do not sync changes from OneDrive locally" , & uploadOnly ,
2018-12-07 19:01:22 +01:00
"verbose|v+" , "Print more details, useful for debugging (repeat for extra debugging)" , & log . verbose ,
2017-07-14 11:31:16 +02:00
"version" , "Print the version and exit" , & printVersion
2015-09-14 19:21:06 +02:00
) ;
if ( opt . helpWanted ) {
2018-12-28 10:01:50 +01:00
outputLongHelp ( opt . options ) ;
2016-08-04 23:35:58 +02:00
return EXIT_SUCCESS ;
2015-09-14 19:21:06 +02:00
}
} catch ( GetOptException e ) {
2017-12-28 15:21:41 +01:00
log . error ( e . msg ) ;
log . error ( "Try 'onedrive -h' for more information" ) ;
2016-08-04 23:35:58 +02:00
return EXIT_FAILURE ;
2018-12-20 01:05:32 +01:00
} catch ( Exception e ) {
// error
log . error ( e . msg ) ;
log . error ( "Try 'onedrive -h' for more information" ) ;
return EXIT_FAILURE ;
2015-09-14 19:21:06 +02:00
}
2018-12-05 20:19:00 +01:00
2018-12-23 01:15:10 +01:00
// 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 ;
// 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 ) ;
2015-09-14 19:21:06 +02:00
2018-12-23 01:15:10 +01:00
// 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" ;
}
2017-07-14 11:31:16 +02:00
if ( printVersion ) {
2017-08-01 19:11:50 +02:00
std . stdio . write ( "onedrive " , import ( "version" ) ) ;
2017-07-14 11:31:16 +02:00
return EXIT_SUCCESS ;
}
2018-12-23 01:15:10 +01:00
// load application configuration
2016-08-04 23:35:58 +02:00
log . vlog ( "Loading config ..." ) ;
2018-04-13 01:33:16 +02:00
log . vlog ( "Using Config Dir: " , configDirName ) ;
2017-08-01 19:11:50 +02:00
if ( ! exists ( configDirName ) ) mkdirRecurse ( configDirName ) ;
2016-08-04 23:35:58 +02:00
auto cfg = new config . Config ( configDirName ) ;
2018-12-20 00:51:21 +01:00
if ( ! cfg . init ( ) ) {
// There was an error loading the configuration
// Error message already printed
return EXIT_FAILURE ;
}
2017-08-01 19:11:50 +02:00
2018-12-23 01:15:10 +01:00
// command line parameters to override default 'config' & take precedence
2019-02-24 07:19:45 +01:00
// 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" ) ;
}
2018-12-23 01:15:10 +01:00
// 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 ) ;
2018-12-20 07:27:34 +01:00
cfg . setValue ( "sync_dir" , syncDirName ) ;
}
2018-12-23 01:15:10 +01:00
// sync_dir environment handling to handle ~ expansion properly
2018-12-19 19:42:28 +01:00
string syncDir ;
if ( ( environment . get ( "SHELL" ) = = "" ) & & ( environment . get ( "USER" ) = = "" ) ) {
2018-12-23 01:15:10 +01:00
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" ) , "~" ) ) {
// 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" ) , "~" , "~" ) ;
2018-12-19 19:42:28 +01:00
} else {
2018-12-23 01:15:10 +01:00
// No ~ found in sync_dir, use as is
log . vdebug ( "sync_dir: Getting syncDir from config value sync_dir" ) ;
syncDir = cfg . getValue ( "sync_dir" ) ;
2018-12-19 19:42:28 +01:00
}
} else {
2018-12-23 01:15:10 +01:00
// 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" ) , "~" ) ) {
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" ) ) ;
} else {
syncDir = cfg . getValue ( "sync_dir" ) ;
}
2018-12-19 19:42:28 +01:00
}
2018-12-23 01:15:10 +01:00
// vdebug syncDir as set and calculated
log . vdebug ( "syncDir: " , syncDir ) ;
2018-11-23 21:13:16 +01:00
// Configure logging if enabled
if ( enableLogFile ) {
// Read in a user defined log directory or use the default
string logDir = cfg . getValue ( "log_dir" ) ;
log . vlog ( "Using logfile dir: " , logDir ) ;
log . init ( logDir ) ;
}
2018-12-05 20:19:00 +01:00
// Configure whether notifications are used
log . setNotifications ( monitor & & ! disableNotifications ) ;
2018-11-23 21:13:16 +01:00
2016-12-25 19:23:33 +01:00
// upgrades
if ( exists ( configDirName ~ "/items.db" ) ) {
remove ( configDirName ~ "/items.db" ) ;
2018-12-05 20:19:00 +01:00
log . logAndNotify ( "Database schema changed, resync needed" ) ;
2016-12-25 19:23:33 +01:00
resync = true ;
}
2016-08-05 00:12:58 +02:00
if ( resync | | logout ) {
2017-05-28 23:14:37 +02:00
log . vlog ( "Deleting the saved status ..." ) ;
2016-08-05 00:12:58 +02:00
safeRemove ( cfg . databaseFilePath ) ;
2017-05-28 20:54:57 +02:00
safeRemove ( cfg . deltaLinkFilePath ) ;
2016-08-05 00:12:58 +02:00
safeRemove ( cfg . uploadStateFilePath ) ;
if ( logout ) {
safeRemove ( cfg . refreshTokenFilePath ) ;
}
2015-09-14 19:21:06 +02:00
}
2018-12-19 19:42:28 +01:00
// Display current application configuration, no application initialisation
if ( displayConfiguration ) {
string userConfigFilePath = configDirName ~ "/config" ;
string userSyncList = configDirName ~ "/sync_list" ;
2019-01-05 19:43:44 +01:00
// Display application version
std . stdio . write ( "onedrive version = " , import ( "version" ) ) ;
2018-12-19 19:42:28 +01:00
// Display all of the pertinent configuration options
writeln ( "Config path = " , configDirName ) ;
// Does a config file exist or are we using application defaults
if ( exists ( userConfigFilePath ) ) {
writeln ( "Config file found in config path = true" ) ;
} else {
writeln ( "Config file found in config path = false" ) ;
}
// Config Options
writeln ( "Config option 'sync_dir' = " , syncDir ) ;
writeln ( "Config option 'skip_file' = " , cfg . getValue ( "skip_file" ) ) ;
2019-02-24 07:19:45 +01:00
writeln ( "Config option 'skip_dotfiles' = " , cfg . getValue ( "skip_dotfiles" ) ) ;
2018-12-19 19:42:28 +01:00
writeln ( "Config option 'skip_symlinks' = " , cfg . getValue ( "skip_symlinks" ) ) ;
writeln ( "Config option 'monitor_interval' = " , cfg . getValue ( "monitor_interval" ) ) ;
2019-01-28 18:54:03 +01:00
writeln ( "Config option 'min_notif_changes' = " , cfg . getValue ( "min_notif_changes" ) ) ;
2018-12-19 19:42:28 +01:00
writeln ( "Config option 'log_dir' = " , cfg . getValue ( "log_dir" ) ) ;
// Is config option drive_id configured?
if ( cfg . getValue ( "drive_id" , "" ) ! = "" ) {
writeln ( "Config option 'drive_id' = " , cfg . getValue ( "drive_id" ) ) ;
}
// Is sync_list configured?
if ( exists ( userSyncList ) ) {
writeln ( "Selective sync configured = true" ) ;
2019-01-05 19:43:44 +01:00
writeln ( "sync_list contents:" ) ;
// Output the sync_list contents
auto syncListFile = File ( userSyncList ) ;
auto range = syncListFile . byLine ( ) ;
foreach ( line ; range )
{
writeln ( line ) ;
}
2018-12-19 19:42:28 +01:00
} else {
writeln ( "Selective sync configured = false" ) ;
}
return EXIT_SUCCESS ;
}
2016-08-04 23:35:58 +02:00
log . vlog ( "Initializing the OneDrive API ..." ) ;
2018-08-13 23:21:11 +02:00
try {
online = testNetwork ( ) ;
} catch ( CurlException e ) {
// No network connection to OneDrive Service
log . error ( "No network connection to Microsoft OneDrive Service" ) ;
2018-12-04 01:15:44 +01:00
if ( ! monitor ) {
return EXIT_FAILURE ;
}
}
2018-05-16 11:19:43 +02:00
// Initialize OneDrive, check for authorization
2019-01-05 19:43:44 +01:00
oneDrive = new OneDriveApi ( cfg , debugHttp , forceHTTP11 ) ;
2018-12-28 03:19:20 +01:00
oneDrive . printAccessToken = printAccessToken ;
if ( ! oneDrive . init ( ) ) {
2017-12-28 15:21:41 +01:00
log . error ( "Could not initialize the OneDrive API" ) ;
2016-08-04 23:35:58 +02:00
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2016-08-04 23:35:58 +02:00
return EXIT_FAILURE ;
2015-09-01 20:45:34 +02:00
}
2018-12-19 19:42:28 +01:00
2018-04-24 04:14:36 +02:00
// if --synchronize or --monitor not passed in, exit & display help
auto performSyncOK = false ;
2018-05-16 11:19:43 +02:00
2018-04-24 04:14:36 +02:00
if ( synchronize | | monitor ) {
performSyncOK = true ;
}
2018-05-16 11:19:43 +02:00
// create-directory, remove-directory, source-directory, destination-directory
// are activities that dont perform a sync no error message for these items either
2018-12-28 02:26:03 +01:00
if ( ( ( createDirectory ! = "" ) | | ( removeDirectory ! = "" ) ) | | ( ( sourceDirectory ! = "" ) & & ( destinationDirectory ! = "" ) ) | | ( o365SharedLibraryName ! = "" ) | | ( displaySyncStatus = = true ) ) {
2018-05-16 11:19:43 +02:00
performSyncOK = true ;
}
2018-04-24 04:14:36 +02:00
if ( ! performSyncOK ) {
writeln ( "\n--synchronize or --monitor missing from your command options or use --help for further assistance\n" ) ;
writeln ( "No OneDrive sync will be performed without either of these two arguments being present\n" ) ;
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2018-04-24 04:14:36 +02:00
return EXIT_FAILURE ;
}
2018-03-14 05:43:40 +01:00
// initialize system
2016-08-04 23:35:58 +02:00
log . vlog ( "Opening the item database ..." ) ;
2018-12-28 03:19:20 +01:00
itemDb = new ItemDatabase ( cfg . databaseFilePath ) ;
2018-03-14 05:43:40 +01:00
2016-08-04 23:35:58 +02:00
log . vlog ( "All operations will be performed in: " , syncDir ) ;
2018-12-23 01:15:10 +01:00
if ( ! exists ( syncDir ) ) {
log . vdebug ( "syncDir: Configured syncDir is missing. Creating: " , syncDir ) ;
mkdirRecurse ( syncDir ) ;
}
2015-09-27 18:47:41 +02:00
chdir ( syncDir ) ;
2018-03-14 05:43:40 +01:00
2018-05-16 11:19:43 +02:00
// Configure selective sync by parsing and getting a regex for skip_file config component
2017-03-24 22:30:03 +01:00
auto selectiveSync = new SelectiveSync ( ) ;
2019-01-05 19:43:44 +01:00
if ( exists ( cfg . syncListFilePath ) ) {
log . vdebug ( "Loading user configured sync_list file ..." ) ;
// list what will be synced
auto syncListFile = File ( cfg . syncListFilePath ) ;
auto range = syncListFile . byLine ( ) ;
foreach ( line ; range )
{
log . vdebug ( "sync_list: " , line ) ;
}
}
2017-03-24 22:30:03 +01:00
selectiveSync . load ( cfg . syncListFilePath ) ;
selectiveSync . setMask ( cfg . getValue ( "skip_file" ) ) ;
2018-05-16 11:19:43 +02:00
// Initialise the sync engine
2018-12-05 20:19:00 +01:00
log . logAndNotify ( "Initializing the Synchronization Engine ..." ) ;
2018-12-28 03:19:20 +01:00
auto sync = new SyncEngine ( cfg , oneDrive , itemDb , selectiveSync ) ;
2018-07-16 01:58:36 +02:00
try {
2018-12-04 01:15:44 +01:00
if ( ! initSyncEngine ( sync ) ) {
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2018-07-16 01:58:36 +02:00
return EXIT_FAILURE ;
}
2018-12-04 01:15:44 +01:00
} catch ( CurlException e ) {
if ( ! monitor ) {
log . log ( "\nNo internet connection." ) ;
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2018-09-24 21:25:40 +02:00
return EXIT_FAILURE ;
}
2018-07-16 01:58:36 +02:00
}
2018-12-04 01:15:44 +01:00
2018-08-09 23:40:17 +02:00
// We should only set noRemoteDelete in an upload-only scenario
if ( ( uploadOnly ) & & ( noRemoteDelete ) ) sync . setNoRemoteDelete ( ) ;
2018-11-23 20:26:30 +01:00
// Do we configure to disable the upload validation routine
if ( disableUploadValidation ) sync . setDisableUploadValidation ( ) ;
2018-06-17 00:27:43 +02:00
// Do we need to validate the syncDir to check for the presence of a '.nosync' file
if ( checkMount ) {
// we were asked to check the mounts
if ( exists ( syncDir ~ "/.nosync" ) ) {
2018-12-05 20:19:00 +01:00
log . logAndNotify ( "ERROR: .nosync file found. Aborting synchronization process to safeguard data." ) ;
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2018-06-17 00:27:43 +02:00
return EXIT_FAILURE ;
}
}
2018-03-14 05:43:40 +01:00
// Do we need to create or remove a directory?
if ( ( createDirectory ! = "" ) | | ( removeDirectory ! = "" ) ) {
if ( createDirectory ! = "" ) {
// create a directory on OneDrive
sync . createDirectoryNoSync ( createDirectory ) ;
}
if ( removeDirectory ! = "" ) {
// remove a directory on OneDrive
sync . deleteDirectoryNoSync ( removeDirectory ) ;
}
}
// Are we renaming or moving a directory?
if ( ( sourceDirectory ! = "" ) & & ( destinationDirectory ! = "" ) ) {
// We are renaming or moving a directory
sync . renameDirectoryNoSync ( sourceDirectory , destinationDirectory ) ;
}
2018-12-04 00:59:23 +01:00
// Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
if ( o365SharedLibraryName ! = "" ) {
sync . querySiteCollectionForDriveID ( o365SharedLibraryName ) ;
}
2018-12-28 02:26:03 +01:00
// Are we displaying the sync status of the client?
if ( displaySyncStatus ) {
string remotePath = "/" ;
string localPath = "." ;
// Are we doing a single directory check?
if ( singleDirectory ! = "" ) {
// Need two different path strings here
remotePath = singleDirectory ;
localPath = singleDirectory ;
}
sync . queryDriveForChanges ( remotePath ) ;
}
2018-03-14 05:43:40 +01:00
// Are we performing a sync, resync or monitor operation?
if ( ( synchronize ) | | ( resync ) | | ( monitor ) ) {
2015-09-01 20:45:34 +02:00
2018-03-14 05:43:40 +01:00
if ( ( synchronize ) | | ( resync ) ) {
if ( online ) {
// Check user entry for local path - the above chdir means we are already in ~/OneDrive/ thus singleDirectory is local to this path
if ( singleDirectory ! = "" ) {
// Does the directory we want to sync actually exist?
if ( ! exists ( singleDirectory ) ) {
// the requested directory does not exist ..
2018-12-05 20:19:00 +01:00
log . logAndNotify ( "ERROR: The requested local directory does not exist. Please check ~/OneDrive/ for requested path" ) ;
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2018-03-14 05:43:40 +01:00
return EXIT_FAILURE ;
}
}
// Perform the sync
2019-02-26 21:21:23 +01:00
performSync ( sync , singleDirectory , downloadOnly , localFirst , uploadOnly , LOG_NORMAL ) ;
2015-09-20 21:21:51 +02:00
}
2018-03-14 05:43:40 +01:00
}
if ( monitor ) {
2018-12-05 20:19:00 +01:00
log . logAndNotify ( "Initializing monitor ..." ) ;
2018-08-07 21:35:18 +02:00
log . log ( "OneDrive monitor interval (seconds): " , to ! long ( cfg . getValue ( "monitor_interval" ) ) ) ;
2018-03-14 05:43:40 +01:00
Monitor m = new Monitor ( selectiveSync ) ;
m . onDirCreated = delegate ( string path ) {
log . vlog ( "[M] Directory created: " , path ) ;
try {
sync . scanForDifferences ( path ) ;
2019-02-21 09:51:15 +01:00
} catch ( CurlException e ) {
log . vlog ( "Offline, cannot create remote dir!" ) ;
2018-03-14 05:43:40 +01:00
} catch ( Exception e ) {
2018-12-06 08:05:52 +01:00
log . logAndNotify ( "Cannot create remote directory: " , e . msg ) ;
2018-03-14 05:43:40 +01:00
}
} ;
m . onFileChanged = delegate ( string path ) {
log . vlog ( "[M] File changed: " , path ) ;
try {
sync . scanForDifferences ( path ) ;
2019-02-21 09:51:15 +01:00
} catch ( CurlException e ) {
log . vlog ( "Offline, cannot upload changed item!" ) ;
2018-03-14 05:43:40 +01:00
} catch ( Exception e ) {
2018-12-06 08:05:52 +01:00
log . logAndNotify ( "Cannot upload file changes/creation: " , e . msg ) ;
2018-03-14 05:43:40 +01:00
}
} ;
m . onDelete = delegate ( string path ) {
log . vlog ( "[M] Item deleted: " , path ) ;
try {
sync . deleteByPath ( path ) ;
2019-02-21 09:51:15 +01:00
} catch ( CurlException e ) {
log . vlog ( "Offline, cannot delete item!" ) ;
2018-12-06 00:50:46 +01:00
} catch ( SyncException e ) {
if ( e . msg = = "The item to delete is not in the local database" ) {
log . vlog ( "Item cannot be deleted because not found in database" ) ;
} else {
2018-12-06 08:05:52 +01:00
log . logAndNotify ( "Cannot delete remote item: " , e . msg ) ;
2018-12-06 00:50:46 +01:00
}
2018-03-14 05:43:40 +01:00
} catch ( Exception e ) {
2018-12-06 08:05:52 +01:00
log . logAndNotify ( "Cannot delete remote item: " , e . msg ) ;
2018-03-14 05:43:40 +01:00
}
} ;
m . onMove = delegate ( string from , string to ) {
log . vlog ( "[M] Item moved: " , from , " -> " , to ) ;
try {
sync . uploadMoveItem ( from , to ) ;
2019-02-21 09:51:15 +01:00
} catch ( CurlException e ) {
log . vlog ( "Offline, cannot move item!" ) ;
2018-03-14 05:43:40 +01:00
} catch ( Exception e ) {
2018-12-06 08:05:52 +01:00
log . logAndNotify ( "Cannot move item:, " , e . msg ) ;
2018-03-14 05:43:40 +01:00
}
} ;
2018-12-28 03:19:20 +01:00
signal ( SIGINT , & exitHandler ) ;
signal ( SIGTERM , & exitHandler ) ;
2018-08-17 00:03:21 +02:00
// initialise the monitor class
if ( cfg . getValue ( "skip_symlinks" ) = = "true" ) skipSymlinks = true ;
if ( ! downloadOnly ) m . init ( cfg , verbose , skipSymlinks ) ;
2018-03-14 05:43:40 +01:00
// monitor loop
2018-08-07 21:35:18 +02:00
immutable auto checkInterval = dur ! "seconds" ( to ! long ( cfg . getValue ( "monitor_interval" ) ) ) ;
2019-02-26 21:21:23 +01:00
immutable auto logInterval = to ! long ( cfg . getValue ( "monitor_log_frequency" ) ) ;
2018-03-14 05:43:40 +01:00
auto lastCheckTime = MonoTime . currTime ( ) ;
2019-02-26 21:21:23 +01:00
auto logMonitorCounter = 0 ;
2018-03-14 05:43:40 +01:00
while ( true ) {
if ( ! downloadOnly ) m . update ( online ) ;
auto currTime = MonoTime . currTime ( ) ;
if ( currTime - lastCheckTime > checkInterval ) {
2019-02-26 21:21:23 +01:00
logMonitorCounter + = 1 ;
if ( logMonitorCounter > logInterval )
logMonitorCounter = 1 ;
2018-12-26 08:11:06 +01:00
// log.logAndNotify("DEBUG trying to create checkpoint");
// auto res = itemdb.db_checkpoint();
// log.logAndNotify("Checkpoint return: ", res);
// itemdb.dump_open_statements();
2018-11-29 10:48:24 +01:00
try {
2018-12-04 01:15:44 +01:00
if ( ! initSyncEngine ( sync ) ) {
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2018-12-04 01:15:44 +01:00
return EXIT_FAILURE ;
}
2018-12-12 21:08:18 +01:00
try {
2019-02-26 21:21:23 +01:00
performSync ( sync , singleDirectory , downloadOnly , localFirst , uploadOnly , ( logMonitorCounter = = logInterval ? MONITOR_LOG_QUIET : MONITOR_LOG_SILENT ) ) ;
2018-12-12 21:08:18 +01:00
if ( ! downloadOnly ) {
// discard all events that may have been generated by the sync
m . update ( false ) ;
}
} catch ( CurlException e ) {
// we already tried three times in the performSync routine
// if we still have problems, then the sync handle might have
// gone stale and we need to re-initialize the sync engine
2019-02-26 20:49:23 +01:00
log . log ( "Persistent connection errors, reinitializing connection" ) ;
2018-12-12 21:08:18 +01:00
sync . reset ( ) ;
2018-12-04 01:15:44 +01:00
}
2018-11-29 10:48:24 +01:00
} catch ( CurlException e ) {
2018-12-12 21:08:18 +01:00
log . log ( "Cannot initialize connection to OneDrive" ) ;
2017-12-31 17:07:21 +01:00
}
2018-08-07 21:35:18 +02:00
// performSync complete, set lastCheckTime to current time
lastCheckTime = MonoTime . currTime ( ) ;
2018-03-14 05:43:40 +01:00
GC . collect ( ) ;
2018-09-13 00:43:29 +02:00
}
Thread . sleep ( dur ! "msecs" ( 500 ) ) ;
2015-09-17 00:16:23 +02:00
}
}
2015-09-14 19:21:06 +02:00
}
2016-02-24 17:07:59 +01:00
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
2018-12-28 03:19:20 +01:00
oneDrive . http . shutdown ( ) ;
2016-08-04 23:35:58 +02:00
return EXIT_SUCCESS ;
2015-09-01 20:45:34 +02:00
}
2015-09-20 21:21:51 +02:00
2018-12-04 01:15:44 +01:00
bool initSyncEngine ( SyncEngine sync )
{
try {
sync . init ( ) ;
} catch ( OneDriveException e ) {
if ( e . httpStatusCode = = 400 | | e . httpStatusCode = = 401 ) {
// Authorization is invalid
log . log ( "\nAuthorization token invalid, use --logout to authorize the client again\n" ) ;
return false ;
}
if ( e . httpStatusCode > = 500 ) {
// There was a HTTP 5xx Server Side Error, message already printed
return false ;
}
}
return true ;
}
2015-09-20 21:21:51 +02:00
// try to synchronize the folder three times
2019-02-26 21:21:23 +01:00
void performSync ( SyncEngine sync , string singleDirectory , bool downloadOnly , bool localFirst , bool uploadOnly , long logLevel )
2015-09-20 21:21:51 +02:00
{
int count ;
2018-03-14 05:43:40 +01:00
string remotePath = "/" ;
string localPath = "." ;
// Are we doing a single directory sync?
if ( singleDirectory ! = "" ) {
// Need two different path strings here
remotePath = singleDirectory ;
localPath = singleDirectory ;
}
2015-09-20 21:21:51 +02:00
do {
try {
2018-03-14 05:43:40 +01:00
if ( singleDirectory ! = "" ) {
// we were requested to sync a single directory
log . vlog ( "Syncing changes from this selected path: " , singleDirectory ) ;
2018-07-15 07:22:08 +02:00
if ( uploadOnly ) {
// Upload Only of selected single directory
2019-02-26 21:21:23 +01:00
if ( logLevel < MONITOR_LOG_QUIET ) log . log ( "Syncing changes from selected local path only - NOT syncing data changes from OneDrive ..." ) ;
2018-07-15 07:22:08 +02:00
sync . scanForDifferences ( localPath ) ;
} else {
// No upload only
if ( localFirst ) {
// Local First
2019-02-26 21:21:23 +01:00
if ( logLevel < MONITOR_LOG_QUIET ) log . log ( "Syncing changes from selected local path first before downloading changes from OneDrive ..." ) ;
2018-04-07 09:06:57 +02:00
sync . scanForDifferences ( localPath ) ;
sync . applyDifferencesSingleDirectory ( remotePath ) ;
2018-07-15 07:22:08 +02:00
} else {
// OneDrive First
2019-02-26 21:21:23 +01:00
if ( logLevel < MONITOR_LOG_QUIET ) log . log ( "Syncing changes from selected OneDrive path ..." ) ;
2018-07-15 07:22:08 +02:00
sync . applyDifferencesSingleDirectory ( remotePath ) ;
// is this a download only request?
if ( ! downloadOnly ) {
// process local changes
sync . scanForDifferences ( localPath ) ;
// ensure that the current remote state is updated locally
sync . applyDifferencesSingleDirectory ( remotePath ) ;
}
2018-04-07 09:06:57 +02:00
}
2018-03-14 05:43:40 +01:00
}
} else {
2018-07-15 07:22:08 +02:00
// no single directory sync
if ( uploadOnly ) {
// Upload Only of entire sync_dir
2019-02-26 21:21:23 +01:00
if ( logLevel < MONITOR_LOG_QUIET ) log . log ( "Syncing changes from local path only - NOT syncing data changes from OneDrive ..." ) ;
2018-07-15 07:22:08 +02:00
sync . scanForDifferences ( localPath ) ;
} else {
// No upload only
if ( localFirst ) {
// sync local files first before downloading from OneDrive
2019-02-26 21:21:23 +01:00
if ( logLevel < MONITOR_LOG_QUIET ) log . log ( "Syncing changes from local path first before downloading changes from OneDrive ..." ) ;
2018-04-07 09:06:57 +02:00
sync . scanForDifferences ( localPath ) ;
sync . applyDifferences ( ) ;
2018-07-15 07:22:08 +02:00
} else {
// sync from OneDrive first before uploading files to OneDrive
2019-02-26 21:21:23 +01:00
if ( logLevel < MONITOR_LOG_SILENT ) log . log ( "Syncing changes from OneDrive ..." ) ;
2018-07-15 07:22:08 +02:00
sync . applyDifferences ( ) ;
// is this a download only request?
if ( ! downloadOnly ) {
// process local changes
sync . scanForDifferences ( localPath ) ;
// ensure that the current remote state is updated locally
sync . applyDifferences ( ) ;
}
2018-04-07 09:06:57 +02:00
}
2018-03-14 05:43:40 +01:00
}
2017-12-31 17:07:21 +01:00
}
2015-09-20 21:21:51 +02:00
count = - 1 ;
2017-12-28 15:03:15 +01:00
} catch ( Exception e ) {
2018-12-12 21:08:18 +01:00
if ( + + count = = 3 ) {
log . log ( "Giving up on sync after three attempts: " , e . msg ) ;
throw e ;
} else
log . log ( "Retry sync count: " , count , ": " , e . msg ) ;
2015-09-20 21:21:51 +02:00
}
} while ( count ! = - 1 ) ;
}
2018-12-05 20:19:00 +01:00
2018-12-28 03:19:20 +01:00
// getting around the @nogc problem
// https://p0nce.github.io/d-idioms/#Bypassing-@nogc
auto assumeNoGC ( T ) ( T t ) if ( isFunctionPointer ! T | | isDelegate ! T )
{
enum attrs = functionAttributes ! T | FunctionAttribute . nogc ;
return cast ( SetFunctionAttributes ! ( T , functionLinkage ! T , attrs ) ) t ;
}
extern ( C ) nothrow @nogc @system void exitHandler ( int value ) {
try {
assumeNoGC ( ( ) {
log . log ( "Got termination signal, shutting down db connection" ) ;
// make sure the .wal file is incorporated into the main db
destroy ( itemDb ) ;
// workaround for segfault in std.net.curl.Curl.shutdown() on exit
oneDrive . http . shutdown ( ) ;
} ) ( ) ;
} catch ( Exception e ) { }
exit ( 0 ) ;
}
2018-12-28 10:01:50 +01:00
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 ) ;
}
}