2019-04-11 04:26:20 +02:00
import core.stdc.stdlib : EXIT_SUCCESS , EXIT_FAILURE , exit ;
2020-05-20 03:37:11 +02:00
import std.file , std . string , std . regex , std . stdio , std . process , std . algorithm . searching , std . getopt , std . conv , std . path ;
2019-04-11 04:26:20 +02:00
import std.algorithm.sorting : sort ;
2017-03-24 22:30:03 +01:00
import selective ;
2016-08-04 23:35:58 +02:00
static import log ;
2015-09-01 20:45:34 +02:00
2016-08-04 23:35:58 +02:00
final class Config
2015-09-01 20:45:34 +02:00
{
2020-05-20 03:37:11 +02:00
// application defaults
2019-08-24 09:18:58 +02:00
public string defaultSyncDir = "~/OneDrive" ;
public string defaultSkipFile = "~*|.~*|*.tmp" ;
public string defaultSkipDir = "" ;
2021-02-06 10:46:56 +01:00
public string defaultLogFileDir = "/var/log/onedrive/" ;
2020-05-20 03:37:11 +02:00
// application set items
public string refreshTokenFilePath = "" ;
public string deltaLinkFilePath = "" ;
public string databaseFilePath = "" ;
public string databaseFilePathDryRun = "" ;
public string uploadStateFilePath = "" ;
public string syncListFilePath = "" ;
public string homePath = "" ;
public string configDirName = "" ;
2020-06-05 06:49:31 +02:00
public string systemConfigDirName = "" ;
2020-05-20 03:37:11 +02:00
public string configFileSyncDir = "" ;
public string configFileSkipFile = "" ;
public string configFileSkipDir = "" ;
2020-06-27 11:10:37 +02:00
public string businessSharedFolderFilePath = "" ;
2020-05-20 03:37:11 +02:00
private string userConfigFilePath = "" ;
2020-06-05 06:49:31 +02:00
private string systemConfigFilePath = "" ;
2020-03-14 20:29:44 +01:00
// was the application just authorised - paste of response uri
public bool applicationAuthorizeResponseUri = false ;
2016-08-04 23:35:58 +02:00
// hashmap for the values found in the user config file
2019-04-11 04:26:20 +02:00
// ARGGGG D is stupid and cannot make hashmap initializations!!!
// private string[string] foobar = [ "aa": "bb" ] does NOT work!!!
private string [ string ] stringValues ;
private bool [ string ] boolValues ;
private long [ string ] longValues ;
2020-06-27 11:10:37 +02:00
// Compile time regex - this does not change
2020-05-20 03:37:11 +02:00
public auto configRegex = ctRegex ! ( `^(\w+)\s*=\s*"(.*)"\s*$` ) ;
2020-10-29 22:00:26 +01:00
// Default directory permission mode
public long defaultDirectoryPermissionMode = 700 ;
public int configuredDirectoryPermissionMode ;
// Default file permission mode
public long defaultFilePermissionMode = 600 ;
public int configuredFilePermissionMode ;
2021-11-23 20:54:28 +01:00
2019-04-11 04:26:20 +02:00
this ( string confdirOption )
2016-08-04 23:35:58 +02:00
{
2020-03-14 20:29:44 +01:00
// default configuration - entries in config file ~/.config/onedrive/config
2020-10-29 22:00:26 +01:00
// an entry here means it can be set via the config file if there is a coresponding entry, read from config and set via update_from_args()
2020-05-20 03:37:11 +02:00
stringValues [ "sync_dir" ] = defaultSyncDir ;
stringValues [ "skip_file" ] = defaultSkipFile ;
stringValues [ "skip_dir" ] = defaultSkipDir ;
2021-02-06 10:46:56 +01:00
stringValues [ "log_dir" ] = defaultLogFileDir ;
2020-05-20 03:37:11 +02:00
stringValues [ "drive_id" ] = "" ;
stringValues [ "user_agent" ] = "" ;
boolValues [ "upload_only" ] = false ;
boolValues [ "check_nomount" ] = false ;
boolValues [ "check_nosync" ] = false ;
boolValues [ "download_only" ] = false ;
2019-04-11 04:26:20 +02:00
boolValues [ "disable_notifications" ] = false ;
2021-11-22 21:34:16 +01:00
boolValues [ "disable_download_validation" ] = false ;
2019-04-11 04:26:20 +02:00
boolValues [ "disable_upload_validation" ] = false ;
2020-05-20 03:37:11 +02:00
boolValues [ "enable_logging" ] = false ;
2022-07-01 07:09:11 +02:00
boolValues [ "force_http_11" ] = false ;
2020-05-20 03:37:11 +02:00
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 ;
boolValues [ "sync_root_files" ] = false ;
longValues [ "verbose" ] = log . verbose ; // might be initialized by the first getopt call!
2020-05-21 03:08:07 +02:00
// The amount of time (seconds) between monitor sync loops
longValues [ "monitor_interval" ] = 300 ;
2020-05-20 03:37:11 +02:00
longValues [ "skip_size" ] = 0 ;
longValues [ "min_notify_changes" ] = 5 ;
2022-10-06 21:52:38 +02:00
longValues [ "monitor_log_frequency" ] = 6 ;
2022-04-28 03:51:50 +02:00
// Number of N sync runs before performing a full local scan of sync_dir
// By default 12 which means every ~60 minutes a full disk scan of sync_dir will occur
// 'monitor_interval' * 'monitor_fullscan_frequency' = 3600 = 1 hour
longValues [ "monitor_fullscan_frequency" ] = 12 ;
2020-01-02 21:46:58 +01:00
// Number of children in a path that is locally removed which will be classified as a 'big data delete'
longValues [ "classify_as_big_delete" ] = 1000 ;
2020-01-26 22:42:00 +01:00
// Delete source after successful transfer
boolValues [ "remove_source_files" ] = false ;
2020-01-29 06:37:50 +01:00
// Strict matching for skip_dir
boolValues [ "skip_dir_strict_match" ] = false ;
2020-03-22 21:01:51 +01:00
// Allow for a custom Client ID / Application ID to be used to replace the inbuilt default
// This is a config file option ONLY
2020-05-20 03:37:11 +02:00
stringValues [ "application_id" ] = "" ;
2020-04-20 05:03:36 +02:00
// allow for resync to be set via config file
2020-05-20 03:37:11 +02:00
boolValues [ "resync" ] = false ;
2022-01-27 22:58:38 +01:00
// resync now needs to be acknowledged based on the 'risk' of using it
boolValues [ "resync_auth" ] = false ;
2020-05-20 22:48:02 +02:00
// Ignore data safety checks and overwrite local data rather than preserve & rename
// This is a config file option ONLY
boolValues [ "bypass_data_preservation" ] = false ;
2020-06-16 23:57:14 +02:00
// Support National Azure AD endpoints as per https://docs.microsoft.com/en-us/graph/deployments
// By default, if empty, use standard Azure AD URL's
// Will support the following options:
// - USL4
// AD Endpoint: https://login.microsoftonline.us
// Graph Endpoint: https://graph.microsoft.us
// - USL5
// AD Endpoint: https://login.microsoftonline.us
// Graph Endpoint: https://dod-graph.microsoft.us
// - DE
// AD Endpoint: https://portal.microsoftazure.de
// Graph Endpoint: https://graph.microsoft.de
// - CN
// AD Endpoint: https://login.chinacloudapi.cn
// Graph Endpoint: https://microsoftgraph.chinacloudapi.cn
stringValues [ "azure_ad_endpoint" ] = "" ;
2020-10-05 23:06:04 +02:00
// Support single-tenant applications that are not able to use the "common" multiplexer
stringValues [ "azure_tenant_id" ] = "common" ;
2020-06-27 11:10:37 +02:00
// Allow enable / disable of the syncing of OneDrive Business Shared Folders via configuration file
boolValues [ "sync_business_shared_folders" ] = false ;
2020-10-29 22:00:26 +01:00
// Configure the default folder permission attributes for newly created folders
longValues [ "sync_dir_permissions" ] = defaultDirectoryPermissionMode ;
// Configure the default file permission attributes for newly created file
longValues [ "sync_file_permissions" ] = defaultFilePermissionMode ;
2021-01-03 22:28:34 +01:00
// Configure download / upload rate limits
longValues [ "rate_limit" ] = 0 ;
2021-11-22 21:34:16 +01:00
// maximum time an operation is allowed to take
// This includes dns resolution, connecting, data transfer, etc.
2022-05-31 21:57:05 +02:00
longValues [ "operation_timeout" ] = 3600 ;
// To ensure we do not fill up the load disk, how much disk space should be reserved by default
longValues [ "space_reservation" ] = 50 * 2 ^ ^ 20 ; // 50 MB as Bytes
2021-11-23 20:54:28 +01:00
// Webhook options
boolValues [ "webhook_enabled" ] = false ;
stringValues [ "webhook_public_url" ] = "" ;
stringValues [ "webhook_listening_host" ] = "" ;
longValues [ "webhook_listening_port" ] = 8888 ;
longValues [ "webhook_expiration_interval" ] = 3600 * 24 ;
longValues [ "webhook_renewal_interval" ] = 3600 * 12 ;
2022-08-05 22:25:58 +02:00
// Log to application output running configuration values
boolValues [ "display_running_config" ] = false ;
2022-08-06 00:24:51 +02:00
// Configure read-only authentication scope
boolValues [ "read_only_auth_scope" ] = false ;
2022-08-30 22:41:52 +02:00
// Flag to cleanup local files when using --download-only
boolValues [ "cleanup_local_files" ] = false ;
2021-11-23 20:54:28 +01:00
2021-11-22 21:06:13 +01:00
// DEVELOPER OPTIONS
2020-05-20 03:37:11 +02:00
// display_memory = true | false
// - It may be desirable to display the memory usage of the application to assist with diagnosing memory issues with the application
// - This is especially beneficial when debugging or performing memory tests with Valgrind
boolValues [ "display_memory" ] = false ;
// monitor_max_loop = long value
// - It may be desirable to, when running in monitor mode, force monitor mode to 'quit' after X number of loops
// - This is especially beneficial when debugging or performing memory tests with Valgrind
longValues [ "monitor_max_loop" ] = 0 ;
2020-05-25 03:30:04 +02:00
// display_sync_options = true | false
// - It may be desirable to see what options are being passed in to performSync() without enabling the full verbose debug logging
boolValues [ "display_sync_options" ] = false ;
2022-06-29 23:08:29 +02:00
// force_children_scan = true | false
// - Force client to use /children rather than /delta to query changes on OneDrive
// - This option flags nationalCloudDeployment as true, forcing the client to act like it is using a National Cloud Deployment
boolValues [ "force_children_scan" ] = false ;
2022-10-12 04:34:42 +02:00
// display_processing_time = true | false
// - Enabling this option will add function processing times to the console output
// - This then enables tracking of where the application is spending most amount of time when processing data when users have questions re performance
boolValues [ "display_processing_time" ] = false ;
2022-06-29 23:08:29 +02:00
// EXPAND USERS HOME DIRECTORY
2021-11-22 21:06:13 +01:00
// Determine the users home directory.
2019-04-11 04:26:20 +02:00
// 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 = "~" ;
}
}
2021-11-23 20:54:28 +01:00
2019-04-11 04:26:20 +02:00
// Output homePath calculation
log . vdebug ( "homePath: " , homePath ) ;
2021-11-23 20:54:28 +01:00
2019-04-11 04:26:20 +02:00
// Determine the correct configuration directory to use
string configDirBase ;
2020-06-05 06:49:31 +02:00
string systemConfigDirBase ;
2019-04-11 04:26:20 +02:00
if ( confdirOption ! = "" ) {
// A CLI 'confdir' was passed in
2022-07-03 21:59:00 +02:00
// Clean up any stray " .. these should not be there ...
confdirOption = strip ( confdirOption , "\"" ) ;
2019-04-11 04:26:20 +02:00
log . vdebug ( "configDirName: CLI override to set configDirName to: " , confdirOption ) ;
if ( canFind ( confdirOption , "~" ) ) {
// A ~ was found
log . vdebug ( "configDirName: A '~' was found in configDirName, using the calculated 'homePath' to replace '~'" ) ;
configDirName = homePath ~ strip ( confdirOption , "~" , "~" ) ;
} else {
configDirName = confdirOption ;
}
} else {
// Determine the base directory relative to which user specific configuration files should be stored.
if ( environment . get ( "XDG_CONFIG_HOME" ) ! = "" ) {
log . vdebug ( "configDirBase: XDG_CONFIG_HOME environment variable set" ) ;
configDirBase = environment . get ( "XDG_CONFIG_HOME" ) ;
} else {
// XDG_CONFIG_HOME does not exist on systems where X11 is not present - ie - headless systems / servers
log . vdebug ( "configDirBase: WARNING - no XDG_CONFIG_HOME environment variable set" ) ;
configDirBase = homePath ~ "/.config" ;
2020-06-05 06:49:31 +02:00
// Also set up a path to pre-shipped shared configs (which can be overridden by supplying a config file in userspace)
systemConfigDirBase = "/etc" ;
2019-04-11 04:26:20 +02:00
}
2021-11-23 20:54:28 +01:00
2019-04-11 04:26:20 +02:00
// 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" ;
2020-06-05 06:49:31 +02:00
// systemConfigDirBase contains the correct path so we do not need to check for presence of '~'
systemConfigDirName = systemConfigDirBase ~ "/onedrive" ;
2019-04-11 04:26:20 +02:00
}
2021-11-23 20:54:28 +01:00
2020-05-20 03:37:11 +02:00
// Config directory options all determined
2020-10-29 22:00:26 +01:00
if ( ! exists ( configDirName ) ) {
// create the directory
mkdirRecurse ( configDirName ) ;
// Configure the applicable permissions for the folder
configDirName . setAttributes ( returnRequiredDirectoryPermisions ( ) ) ;
2022-07-20 02:09:20 +02:00
} else {
// The config path exists
// The path that exists must be a directory, not a file
if ( ! isDir ( configDirName ) ) {
if ( ! confdirOption . empty ) {
// the configuration path was passed in by the user .. user error
writeln ( "ERROR: --confdir entered value is an existing file instead of an existing directory" ) ;
} else {
// other error
writeln ( "ERROR: ~/.config/onedrive is a file rather than a directory" ) ;
}
// Must exit
exit ( EXIT_FAILURE ) ;
}
2020-10-29 22:00:26 +01:00
}
2021-11-23 20:54:28 +01:00
2020-06-05 06:49:31 +02:00
// configDirName has a trailing /
2022-01-27 22:58:38 +01:00
if ( ! configDirName . empty ) log . vlog ( "Using 'user' Config Dir: " , configDirName ) ;
if ( ! systemConfigDirName . empty ) log . vlog ( "Using 'system' Config Dir: " , systemConfigDirName ) ;
2021-11-23 20:54:28 +01:00
2020-05-20 03:37:11 +02:00
// Update application set variables based on configDirName
refreshTokenFilePath = buildNormalizedPath ( configDirName ~ "/refresh_token" ) ;
deltaLinkFilePath = buildNormalizedPath ( configDirName ~ "/delta_link" ) ;
databaseFilePath = buildNormalizedPath ( configDirName ~ "/items.sqlite3" ) ;
databaseFilePathDryRun = buildNormalizedPath ( configDirName ~ "/items-dryrun.sqlite3" ) ;
uploadStateFilePath = buildNormalizedPath ( configDirName ~ "/resume_upload" ) ;
userConfigFilePath = buildNormalizedPath ( configDirName ~ "/config" ) ;
syncListFilePath = buildNormalizedPath ( configDirName ~ "/sync_list" ) ;
2020-06-05 06:49:31 +02:00
systemConfigFilePath = buildNormalizedPath ( systemConfigDirName ~ "/config" ) ;
2020-06-27 11:10:37 +02:00
businessSharedFolderFilePath = buildNormalizedPath ( configDirName ~ "/business_shared_folders" ) ;
2021-11-23 20:54:28 +01:00
2020-05-20 03:37:11 +02:00
// Debug Output for application set variables based on configDirName
log . vdebug ( "refreshTokenFilePath = " , refreshTokenFilePath ) ;
log . vdebug ( "deltaLinkFilePath = " , deltaLinkFilePath ) ;
log . vdebug ( "databaseFilePath = " , databaseFilePath ) ;
log . vdebug ( "databaseFilePathDryRun = " , databaseFilePathDryRun ) ;
log . vdebug ( "uploadStateFilePath = " , uploadStateFilePath ) ;
log . vdebug ( "userConfigFilePath = " , userConfigFilePath ) ;
log . vdebug ( "syncListFilePath = " , syncListFilePath ) ;
2020-06-05 06:49:31 +02:00
log . vdebug ( "systemConfigFilePath = " , systemConfigFilePath ) ;
2020-06-27 11:10:37 +02:00
log . vdebug ( "businessSharedFolderFilePath = " , businessSharedFolderFilePath ) ;
2016-08-04 23:35:58 +02:00
}
2019-04-11 04:26:20 +02:00
bool initialize ( )
2015-09-01 20:45:34 +02:00
{
2020-05-20 03:31:30 +02:00
// Initialise the application
if ( ! exists ( userConfigFilePath ) ) {
2020-06-05 06:49:31 +02:00
// 'user' configuration file does not exist
// Is there a system configuration file?
if ( ! exists ( systemConfigFilePath ) ) {
// 'system' configuration file does not exist
log . vlog ( "No user or system config file found, using application defaults" ) ;
return true ;
} else {
// 'system' configuration file exists
// can we load the configuration file without error?
if ( load ( systemConfigFilePath ) ) {
// configuration file loaded without error
log . log ( "System configuration file successfully loaded" ) ;
return true ;
} else {
// there was a problem loading the configuration file
log . log ( "System configuration file has errors - please check your configuration" ) ;
return false ;
}
}
2020-05-20 03:31:30 +02:00
} else {
2020-06-05 06:49:31 +02:00
// 'user' configuration file exists
2020-05-20 03:31:30 +02:00
// can we load the configuration file without error?
if ( load ( userConfigFilePath ) ) {
// configuration file loaded without error
log . log ( "Configuration file successfully loaded" ) ;
2018-12-20 00:51:21 +01:00
return true ;
} else {
2020-05-20 03:31:30 +02:00
// there was a problem loading the configuration file
2018-12-20 00:51:21 +01:00
log . log ( "Configuration file has errors - please check your configuration" ) ;
return false ;
}
2017-03-12 16:07:45 +01:00
}
2015-09-01 20:45:34 +02:00
}
2019-04-11 04:26:20 +02:00
void update_from_args ( string [ ] args )
2016-09-18 11:37:41 +02:00
{
2019-04-11 04:26:20 +02:00
// Add additional options that are NOT configurable via config file
stringValues [ "create_directory" ] = "" ;
2020-09-14 09:49:50 +02:00
stringValues [ "create_share_link" ] = "" ;
2019-04-11 04:26:20 +02:00
stringValues [ "destination_directory" ] = "" ;
2019-08-02 10:43:31 +02:00
stringValues [ "get_file_link" ] = "" ;
2022-03-09 21:00:07 +01:00
stringValues [ "modified_by" ] = "" ;
2019-04-11 04:26:20 +02:00
stringValues [ "get_o365_drive_id" ] = "" ;
stringValues [ "remove_directory" ] = "" ;
stringValues [ "single_directory" ] = "" ;
stringValues [ "source_directory" ] = "" ;
2019-06-11 09:12:36 +02:00
stringValues [ "auth_files" ] = "" ;
2021-11-22 21:08:04 +01:00
stringValues [ "auth_response" ] = "" ;
2019-04-11 04:26:20 +02:00
boolValues [ "display_config" ] = false ;
boolValues [ "display_sync_status" ] = false ;
boolValues [ "print_token" ] = false ;
boolValues [ "logout" ] = false ;
2022-03-07 19:35:00 +01:00
boolValues [ "reauth" ] = false ;
2019-04-11 04:26:20 +02:00
boolValues [ "monitor" ] = false ;
boolValues [ "synchronize" ] = false ;
2020-01-02 21:46:58 +01:00
boolValues [ "force" ] = false ;
2020-06-27 11:10:37 +02:00
boolValues [ "list_business_shared_folders" ] = false ;
2022-05-16 02:16:50 +02:00
boolValues [ "force_sync" ] = false ;
2022-09-26 09:56:42 +02:00
boolValues [ "with_editing_perms" ] = false ;
2021-11-23 20:54:28 +01:00
2019-04-11 04:26:20 +02:00
// Application Startup option validation
try {
string tmpStr ;
bool tmpBol ;
long tmpVerb ;
2020-06-27 11:10:37 +02:00
// duplicated from main.d to get full help output!
2019-04-11 04:26:20 +02:00
auto opt = getopt (
2021-11-23 20:54:28 +01:00
2019-04-11 04:26:20 +02:00
args ,
std . getopt . config . bundling ,
std . getopt . config . caseSensitive ,
2019-06-11 09:12:36 +02:00
"auth-files" ,
"Perform authentication not via interactive dialog but via files read/writes to these files." ,
& stringValues [ "auth_files" ] ,
2021-11-22 21:08:04 +01:00
"auth-response" ,
2022-07-01 21:14:50 +02:00
"Perform authentication not via interactive dialog but via providing the response url directly." ,
2021-11-22 21:08:04 +01:00
& stringValues [ "auth_response" ] ,
2019-04-11 04:26:20 +02:00
"check-for-nomount" ,
2021-11-23 20:54:28 +01:00
"Check for the presence of .nosync in the syncdir root. If found, do not perform sync." ,
2019-04-11 04:26:20 +02:00
& boolValues [ "check_nomount" ] ,
"check-for-nosync" ,
"Check for the presence of .nosync in each directory. If found, skip directory from sync." ,
& boolValues [ "check_nosync" ] ,
2020-01-02 21:46:58 +01:00
"classify-as-big-delete" ,
"Number of children in a path that is locally removed which will be classified as a 'big data delete'" ,
& longValues [ "classify_as_big_delete" ] ,
2022-08-30 22:41:52 +02:00
"cleanup-local-files" ,
"Cleanup additional local files when using --download-only. This will remove local data." ,
& boolValues [ "cleanup_local_files" ] ,
2019-04-11 04:26:20 +02:00
"create-directory" ,
"Create a directory on OneDrive - no sync will be performed." ,
& stringValues [ "create_directory" ] ,
2020-09-14 09:49:50 +02:00
"create-share-link" ,
"Create a shareable link for an existing file on OneDrive" ,
& stringValues [ "create_share_link" ] ,
2021-11-23 20:54:28 +01:00
"debug-https" ,
"Debug OneDrive HTTPS communication." ,
2019-04-11 04:26:20 +02:00
& 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" ] ,
2021-11-22 21:34:16 +01:00
"disable-download-validation" ,
"Disable download validation when downloading from OneDrive" ,
& boolValues [ "disable_download_validation" ] ,
2019-04-11 04:26:20 +02:00
"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" ] ,
2022-08-05 22:25:58 +02:00
"display-running-config" ,
"Display what options the client has been configured to use on application startup." ,
& boolValues [ "display_running_config" ] ,
2019-04-11 04:26:20 +02:00
"display-sync-status" ,
"Display the sync status of the client - no sync will be performed." ,
& boolValues [ "display_sync_status" ] ,
2019-07-10 05:23:44 +02:00
"download-only" ,
2020-06-21 00:09:06 +02:00
"Replicate the OneDrive online state locally, by only downloading changes from OneDrive. Do not upload local changes to OneDrive." ,
2019-04-11 04:26:20 +02:00
& 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" ] ,
2022-07-01 07:09:11 +02:00
"force-http-11" ,
"Force the use of HTTP 1.1 for all operations" ,
& boolValues [ "force_http_11" ] ,
2020-01-02 21:46:58 +01:00
"force" ,
"Force the deletion of data when a 'big delete' is detected" ,
& boolValues [ "force" ] ,
2022-05-16 02:16:50 +02:00
"force-sync" ,
"Force a synchronization of a specific folder, only when using --synchronize --single-directory and ignore all non-default skip_dir and skip_file rules" ,
& boolValues [ "force_sync" ] ,
2019-08-02 10:43:31 +02:00
"get-file-link" ,
"Display the file link of a synced file" ,
& stringValues [ "get_file_link" ] ,
2019-04-11 04:26:20 +02:00
"get-O365-drive-id" ,
"Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library" ,
& stringValues [ "get_o365_drive_id" ] ,
"local-first" ,
"Synchronize from the local directory source first, before downloading changes from OneDrive." ,
& boolValues [ "local_first" ] ,
"log-dir" ,
"Directory where logging output is saved to, needs to end with a slash." ,
& stringValues [ "log_dir" ] ,
"logout" ,
"Logout the current user" ,
& boolValues [ "logout" ] ,
2019-06-11 10:40:05 +02:00
"min-notify-changes" ,
2019-04-11 04:26:20 +02:00
"Minimum number of pending incoming changes necessary to trigger a desktop notification" ,
2019-04-14 22:28:32 +02:00
& longValues [ "min_notify_changes" ] ,
2022-03-09 21:00:07 +01:00
"modified-by" ,
"Display the last modified by details of a given path" ,
& stringValues [ "modified_by" ] ,
2019-04-11 04:26:20 +02:00
"monitor|m" ,
"Keep monitoring for local and remote changes" ,
& boolValues [ "monitor" ] ,
"monitor-interval" ,
"Number of seconds by which each sync operation is undertaken when idle under monitor mode." ,
& longValues [ "monitor_interval" ] ,
"monitor-fullscan-frequency" ,
"Number of sync runs before performing a full local scan of the synced directory" ,
& longValues [ "monitor_fullscan_frequency" ] ,
"monitor-log-frequency" ,
"Frequency of logging in monitor mode" ,
& longValues [ "monitor_log_frequency" ] ,
"no-remote-delete" ,
"Do not delete local file 'deletes' from OneDrive when using --upload-only" ,
& boolValues [ "no_remote_delete" ] ,
2021-12-30 01:19:55 +01:00
"operation-timeout" ,
"Maximum amount of time (in seconds) an operation is allowed to take" ,
& longValues [ "operation_timeout" ] ,
2019-04-11 04:26:20 +02:00
"print-token" ,
"Print the access token, useful for debugging" ,
& boolValues [ "print_token" ] ,
2022-03-07 19:35:00 +01:00
"reauth" ,
"Reauthenticate the client with OneDrive" ,
& boolValues [ "reauth" ] ,
2019-04-11 04:26:20 +02:00
"resync" ,
"Forget the last saved state, perform a full sync" ,
& boolValues [ "resync" ] ,
2022-01-27 22:58:38 +01:00
"resync-auth" ,
"Approve the use of performing a --resync action" ,
& boolValues [ "resync_auth" ] ,
2019-04-11 04:26:20 +02:00
"remove-directory" ,
"Remove a directory on OneDrive - no sync will be performed." ,
& stringValues [ "remove_directory" ] ,
2020-01-26 22:42:00 +01:00
"remove-source-files" ,
"Remove source file after successful transfer to OneDrive when using --upload-only" ,
& boolValues [ "remove_source_files" ] ,
2019-04-11 04:26:20 +02:00
"single-directory" ,
"Specify a single local directory within the OneDrive root to sync." ,
& stringValues [ "single_directory" ] ,
"skip-dot-files" ,
"Skip dot files and folders from syncing" ,
& boolValues [ "skip_dotfiles" ] ,
"skip-file" ,
"Skip any files that match this pattern from syncing" ,
& stringValues [ "skip_file" ] ,
2019-08-24 09:18:58 +02:00
"skip-dir" ,
"Skip any directories that match this pattern from syncing" ,
& stringValues [ "skip_dir" ] ,
2019-06-07 22:37:41 +02:00
"skip-size" ,
"Skip new files larger than this size (in MB)" ,
& longValues [ "skip_size" ] ,
2020-01-29 06:37:50 +01:00
"skip-dir-strict-match" ,
"When matching skip_dir directories, only match explicit matches" ,
& boolValues [ "skip_dir_strict_match" ] ,
2019-04-11 04:26:20 +02:00
"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" ] ,
2022-05-31 21:57:05 +02:00
"space-reservation" ,
"The amount of disk space to reserve (in MB) to avoid 100% disk space utilisation" ,
& longValues [ "space_reservation" ] ,
2019-04-11 04:26:20 +02:00
"syncdir" ,
"Specify the local directory used for synchronization to OneDrive" ,
& stringValues [ "sync_dir" ] ,
"synchronize" ,
"Perform a synchronization" ,
& boolValues [ "synchronize" ] ,
2019-05-09 13:18:49 +02:00
"sync-root-files" ,
"Sync all files in sync_dir root when using sync_list." ,
& boolValues [ "sync_root_files" ] ,
2019-04-11 04:26:20 +02:00
"upload-only" ,
2020-06-21 00:09:06 +02:00
"Replicate the locally configured sync_dir state to OneDrive, by only uploading local changes to OneDrive. Do not download changes from OneDrive." ,
2019-04-11 04:26:20 +02:00
& boolValues [ "upload_only" ] ,
2019-09-24 08:42:40 +02:00
"user-agent" ,
"Specify a User Agent string to the http client" ,
& stringValues [ "user_agent" ] ,
2019-04-11 04:26:20 +02:00
"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" ,
2020-06-27 11:10:37 +02:00
& tmpBol ,
"list-shared-folders" ,
"List OneDrive Business Shared Folders" ,
& boolValues [ "list_business_shared_folders" ] ,
"sync-shared-folders" ,
"Sync OneDrive Business Shared Folders" ,
2022-09-26 09:56:42 +02:00
& boolValues [ "sync_business_shared_folders" ] ,
"with-editing-perms" ,
"Create a read-write shareable link for an existing file on OneDrive when used with --create-share-link <file>" ,
& boolValues [ "with_editing_perms" ]
2019-04-11 04:26:20 +02:00
) ;
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 ;
2019-02-12 09:58:10 +01:00
if ( p ) {
return * p ;
2019-01-26 01:03:00 +01:00
} else {
2019-02-22 08:15:10 +01:00
throw new Exception ( "Missing config value: " ~ key ) ;
2019-01-26 01:03:00 +01:00
}
}
2019-04-11 04:26:20 +02:00
long getValueLong ( string key )
2019-02-22 08:15:10 +01:00
{
2019-04-11 04:26:20 +02:00
auto p = key in longValues ;
2019-02-22 08:15:10 +01:00
if ( p ) {
return * p ;
} else {
2019-04-11 04:26:20 +02:00
throw new Exception ( "Missing config value: " ~ key ) ;
2019-02-22 08:15:10 +01:00
}
}
2019-04-11 04:26:20 +02:00
bool getValueBool ( string key )
{
auto p = key in boolValues ;
if ( p ) {
return * p ;
} else {
throw new Exception ( "Missing config value: " ~ key ) ;
}
}
void setValueBool ( string key , bool value )
{
boolValues [ key ] = value ;
}
void setValueString ( string key , string value )
2019-02-12 09:58:10 +01:00
{
2019-04-11 04:26:20 +02:00
stringValues [ key ] = value ;
}
void setValueLong ( string key , long value )
{
longValues [ key ] = value ;
2019-01-26 01:03:00 +01:00
}
2020-05-20 03:37:11 +02:00
// load a configuration file
2019-02-12 09:58:10 +01:00
private bool load ( string filename )
{
2020-05-20 03:37:11 +02:00
// configure function variables
2022-05-19 08:12:53 +02:00
try {
readText ( filename ) ;
} catch ( std . file . FileException e ) {
// Unable to access required file
log . error ( "ERROR: Unable to access " , e . msg ) ;
// Use exit scopes to shutdown API
return false ;
}
// We were able to readText the config file - so, we should be able to open and read it
2019-02-12 09:58:10 +01:00
auto file = File ( filename , "r" ) ;
2020-05-20 03:37:11 +02:00
string lineBuffer ;
2022-05-19 08:12:53 +02:00
2020-05-20 03:37:11 +02:00
// configure scopes
// - failure
scope ( failure ) {
// close file if open
if ( file . isOpen ( ) ) {
// close open file
file . close ( ) ;
}
}
// - exit
scope ( exit ) {
// close file if open
if ( file . isOpen ( ) ) {
// close open file
file . close ( ) ;
}
}
2021-11-23 20:54:28 +01:00
2020-05-20 03:37:11 +02:00
// read file line by line
auto range = file . byLine ( ) ;
foreach ( line ; range ) {
lineBuffer = stripLeft ( line ) . to ! string ;
if ( lineBuffer . length = = 0 | | lineBuffer [ 0 ] = = ';' | | lineBuffer [ 0 ] = = '#' ) continue ;
auto c = lineBuffer . matchFirst ( configRegex ) ;
2019-02-12 09:58:10 +01:00
if ( ! c . empty ) {
c . popFront ( ) ; // skip the whole match
string key = c . front . dup ;
2019-04-11 04:26:20 +02:00
auto p = key in boolValues ;
2019-02-12 09:58:10 +01:00
if ( p ) {
c . popFront ( ) ;
2019-04-11 04:26:20 +02:00
// only accept "true" as true value. TODO Should we support other formats?
setValueBool ( key , c . front . dup = = "true" ? true : false ) ;
2019-02-12 09:58:10 +01:00
} else {
2019-04-11 04:26:20 +02:00
auto pp = key in stringValues ;
if ( pp ) {
c . popFront ( ) ;
setValueString ( key , c . front . dup ) ;
2019-08-24 09:18:58 +02:00
// detect need for --resync for these:
// --syncdir ARG
// --skip-file ARG
// --skip-dir ARG
if ( key = = "sync_dir" ) configFileSyncDir = c . front . dup ;
2020-09-18 23:51:49 +02:00
if ( key = = "skip_file" ) {
// Handle multiple entries of skip_file
if ( configFileSkipFile . empty ) {
// currently no entry exists
configFileSkipFile = c . front . dup ;
} else {
// add to existing entry
configFileSkipFile = configFileSkipFile ~ "|" ~ to ! string ( c . front . dup ) ;
setValueString ( "skip_file" , configFileSkipFile ) ;
}
}
if ( key = = "skip_dir" ) {
// Handle multiple entries of skip_dir
if ( configFileSkipDir . empty ) {
// currently no entry exists
configFileSkipDir = c . front . dup ;
} else {
// add to existing entry
configFileSkipDir = configFileSkipDir ~ "|" ~ to ! string ( c . front . dup ) ;
setValueString ( "skip_dir" , configFileSkipDir ) ;
}
}
2021-11-23 20:54:28 +01:00
2020-06-16 23:57:14 +02:00
// Azure AD Configuration
if ( key = = "azure_ad_endpoint" ) {
string azureConfigValue = c . front . dup ;
switch ( azureConfigValue ) {
case "" :
log . log ( "Using config option for Global Azure AD Endpoints" ) ;
break ;
case "USL4" :
log . log ( "Using config option for Azure AD for US Government Endpoints" ) ;
break ;
case "USL5" :
log . log ( "Using config option for Azure AD for US Government Endpoints (DOD)" ) ;
break ;
case "DE" :
log . log ( "Using config option for Azure AD Germany" ) ;
break ;
case "CN" :
log . log ( "Using config option for Azure AD China operated by 21Vianet" ) ;
break ;
2021-11-23 20:54:28 +01:00
// Default - all other entries
2020-06-16 23:57:14 +02:00
default :
log . log ( "Unknown Azure AD Endpoint - using Global Azure AD Endpoints" ) ;
}
}
2019-04-11 04:26:20 +02:00
} else {
auto ppp = key in longValues ;
if ( ppp ) {
c . popFront ( ) ;
setValueLong ( key , to ! long ( c . front . dup ) ) ;
2022-05-31 21:57:05 +02:00
// if key is space_reservation we have to calculate MB -> bytes
if ( key = = "space_reservation" ) {
// temp value
ulong tempValue = to ! long ( c . front . dup ) ;
// a value of 0 needs to be made at least 1MB ..
if ( tempValue = = 0 ) {
tempValue = 1 ;
}
setValueLong ( "space_reservation" , to ! long ( tempValue * 2 ^ ^ 20 ) ) ;
}
2019-04-11 04:26:20 +02:00
} else {
log . log ( "Unknown key in config file: " , key ) ;
return false ;
}
}
2019-02-12 09:58:10 +01:00
}
} else {
2020-05-20 03:37:11 +02:00
log . log ( "Malformed config line: " , lineBuffer ) ;
2019-02-12 09:58:10 +01:00
return false ;
}
}
return true ;
}
2021-11-23 20:54:28 +01:00
2020-10-29 22:00:26 +01:00
void configureRequiredDirectoryPermisions ( ) {
// return the directory permission mode required
2021-11-23 20:54:28 +01:00
// - return octal!defaultDirectoryPermissionMode; ... cant be used .. which is odd
2020-10-29 22:00:26 +01:00
// Error: variable defaultDirectoryPermissionMode cannot be read at compile time
if ( getValueLong ( "sync_dir_permissions" ) ! = defaultDirectoryPermissionMode ) {
// return user configured permissions as octal integer
string valueToConvert = to ! string ( getValueLong ( "sync_dir_permissions" ) ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
configuredDirectoryPermissionMode = to ! int ( convertedValue ) ;
} else {
// return default as octal integer
string valueToConvert = to ! string ( defaultDirectoryPermissionMode ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
configuredDirectoryPermissionMode = to ! int ( convertedValue ) ;
}
}
2021-11-23 20:54:28 +01:00
2020-10-29 22:00:26 +01:00
void configureRequiredFilePermisions ( ) {
// return the file permission mode required
2021-11-23 20:54:28 +01:00
// - return octal!defaultFilePermissionMode; ... cant be used .. which is odd
2020-10-29 22:00:26 +01:00
// Error: variable defaultFilePermissionMode cannot be read at compile time
if ( getValueLong ( "sync_file_permissions" ) ! = defaultFilePermissionMode ) {
// return user configured permissions as octal integer
string valueToConvert = to ! string ( getValueLong ( "sync_file_permissions" ) ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
configuredFilePermissionMode = to ! int ( convertedValue ) ;
} else {
// return default as octal integer
string valueToConvert = to ! string ( defaultFilePermissionMode ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
configuredFilePermissionMode = to ! int ( convertedValue ) ;
}
}
2021-11-23 20:54:28 +01:00
2020-10-29 22:00:26 +01:00
int returnRequiredDirectoryPermisions ( ) {
// read the configuredDirectoryPermissionMode and return
2020-11-08 22:06:48 +01:00
if ( configuredDirectoryPermissionMode = = 0 ) {
// the configured value is zero, this means that directories would get
// values of d---------
configureRequiredDirectoryPermisions ( ) ;
}
2020-10-29 22:00:26 +01:00
return configuredDirectoryPermissionMode ;
}
2021-11-23 20:54:28 +01:00
2020-10-29 22:00:26 +01:00
int returnRequiredFilePermisions ( ) {
// read the configuredFilePermissionMode and return
2020-11-08 22:06:48 +01:00
if ( configuredFilePermissionMode = = 0 ) {
// the configured value is zero
configureRequiredFilePermisions ( ) ;
}
2020-10-29 22:00:26 +01:00
return configuredFilePermissionMode ;
}
2022-05-16 02:16:50 +02:00
void resetSkipToDefaults ( ) {
// reset skip_file and skip_dir to application defaults
// skip_file
log . vdebug ( "original skip_file: " , getValueString ( "skip_file" ) ) ;
log . vdebug ( "resetting skip_file" ) ;
setValueString ( "skip_file" , defaultSkipFile ) ;
log . vdebug ( "reset skip_file: " , getValueString ( "skip_file" ) ) ;
// skip_dir
log . vdebug ( "original skip_dir: " , getValueString ( "skip_dir" ) ) ;
log . vdebug ( "resetting skip_dir" ) ;
setValueString ( "skip_dir" , defaultSkipDir ) ;
log . vdebug ( "reset skip_dir: " , getValueString ( "skip_dir" ) ) ;
}
2015-09-01 20:45:34 +02:00
}
2019-04-11 04:26:20 +02:00
void outputLongHelp ( Option [ ] opt )
{
auto argsNeedingOptions = [
2022-05-16 02:16:50 +02:00
"--auth-files" ,
"--auth-response" ,
2019-04-11 04:26:20 +02:00
"--confdir" ,
"--create-directory" ,
2020-09-14 09:49:50 +02:00
"--create-share-link" ,
2019-04-11 04:26:20 +02:00
"--destination-directory" ,
2020-09-14 09:49:50 +02:00
"--get-file-link" ,
2019-04-11 04:26:20 +02:00
"--get-O365-drive-id" ,
"--log-dir" ,
2019-06-11 10:40:05 +02:00
"--min-notify-changes" ,
2022-03-09 21:00:07 +01:00
"--modified-by" ,
2019-04-11 04:26:20 +02:00
"--monitor-interval" ,
"--monitor-log-frequency" ,
"--monitor-fullscan-frequency" ,
2022-08-08 02:19:06 +02:00
"--operation-timeout" ,
2019-04-11 04:26:20 +02:00
"--remove-directory" ,
"--single-directory" ,
2022-05-16 02:16:50 +02:00
"--skip-dir" ,
2019-04-11 04:26:20 +02:00
"--skip-file" ,
2022-05-16 02:16:50 +02:00
"--skip-size" ,
2019-04-11 04:26:20 +02:00
"--source-directory" ,
2022-05-31 21:57:05 +02:00
"--space-reservation" ,
2019-09-24 08:42:40 +02:00
"--syncdir" ,
2020-01-26 22:42:00 +01:00
"--user-agent" ] ;
2019-04-11 04:26:20 +02:00
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 ) ;
}
}
2015-09-01 20:45:34 +02:00
unittest
{
2016-08-04 23:35:58 +02:00
auto cfg = new Config ( "" ) ;
2017-03-12 16:07:45 +01:00
cfg . load ( "config" ) ;
2019-04-11 04:26:20 +02:00
assert ( cfg . getValueString ( "sync_dir" ) = = "~/OneDrive" ) ;
2015-09-24 18:59:17 +02:00
}