2024-01-08 23:13:17 +01:00
// What is this module called?
module config ;
// What does this module require to function?
2019-04-11 04:26:20 +02:00
import core.stdc.stdlib : EXIT_SUCCESS , EXIT_FAILURE , exit ;
2024-01-08 23:13:17 +01:00
import std.stdio ;
import std.process ;
import std.regex ;
import std.string ;
import std.algorithm.searching ;
2019-04-11 04:26:20 +02:00
import std.algorithm.sorting : sort ;
2024-01-08 23:13:17 +01:00
import std.file ;
import std.conv ;
import std.path ;
import std.getopt ;
import std.format ;
import std.ascii ;
import std.datetime ;
2015-09-01 20:45:34 +02:00
2024-01-08 23:13:17 +01:00
// What other modules that we have created do we need to import?
import log ;
import util ;
class ApplicationConfig {
// Application default values - these do not change
// - Compile time regex
immutable auto configRegex = ctRegex ! ( `^(\w+)\s*=\s*"(.*)"\s*$` ) ;
// - Default directory to store data
immutable string defaultSyncDir = "~/OneDrive" ;
// - Default Directory Permissions
immutable long defaultDirectoryPermissionMode = 700 ;
// - Default File Permissions
immutable long defaultFilePermissionMode = 600 ;
// - Default types of files to skip
// v2.0.x - 2.4.x: ~*|.~*|*.tmp
// v2.5.x : ~*|.~*|*.tmp|*.swp|*.partial
immutable string defaultSkipFile = "~*|.~*|*.tmp|*.swp|*.partial" ;
// - Default directories to skip (default is skip none)
immutable string defaultSkipDir = "" ;
// - Default application logging directory
immutable string defaultLogFileDir = "/var/log/onedrive" ;
// - Default configuration directory
immutable string defaultConfigDirName = "~/.config/onedrive" ;
2024-02-26 04:48:33 +01:00
// - Default 'OneDrive Business Shared Files' Folder Name
immutable string defaultBusinessSharedFilesDirectoryName = "Files Shared With Me" ;
2024-01-08 23:13:17 +01:00
// Microsoft Requirements
// - Default Application ID (abraunegg)
immutable string defaultApplicationId = "d50ca740-c83f-4d1b-b616-12c519384f0c" ;
// - Microsoft User Agent ISV Tag
immutable string isvTag = "ISV" ;
// - Microsoft User Agent Company name
immutable string companyName = "abraunegg" ;
// - Microsoft Application name as per Microsoft Azure application registration
immutable string appTitle = "OneDrive Client for Linux" ;
// Comply with OneDrive traffic decoration requirements
// https://docs.microsoft.com/en-us/sharepoint/dev/general-development/how-to-avoid-getting-throttled-or-blocked-in-sharepoint-online
// - Identify as ISV and include Company Name, App Name separated by a pipe character and then adding Version number separated with a slash character
//immutable string defaultUserAgent = isvTag ~ "|" ~ companyName ~ "|" ~ appTitle ~ "/" ~ strip(import("version"));
immutable string defaultUserAgent = isvTag ~ "|" ~ companyName ~ "|" ~ appTitle ~ "/" ~ "v2.5.0-alpha-5" ;
2023-06-20 01:28:17 +02:00
// HTTP Struct items, used for configuring HTTP()
// Curl Timeout Handling
// libcurl dns_cache_timeout timeout
immutable int defaultDnsTimeout = 60 ;
// Connect timeout for HTTP|HTTPS connections
2024-01-19 20:15:33 +01:00
// Controls CURLOPT_CONNECTTIMEOUT
immutable int defaultConnectTimeout = 10 ;
2024-01-08 23:13:17 +01:00
// Default data timeout for HTTP
// curl.d has a default of: _defaultDataTimeout = dur!"minutes"(2);
immutable int defaultDataTimeout = 240 ;
2023-06-20 01:28:17 +02:00
// Maximum time any operation is allowed to take
// This includes dns resolution, connecting, data transfer, etc.
2024-01-19 20:15:33 +01:00
// Controls CURLOPT_TIMEOUT
2023-06-20 01:28:17 +02:00
immutable int defaultOperationTimeout = 3600 ;
// Specify what IP protocol version should be used when communicating with OneDrive
immutable int defaultIpProtocol = 0 ; // 0 = IPv4 + IPv6, 1 = IPv4 Only, 2 = IPv6 Only
2024-01-08 23:13:17 +01:00
// Specify how many redirects should be allowed
immutable int defaultMaxRedirects = 5 ;
// Azure Active Directory & Graph Explorer Endpoints
// - Global & Default
immutable string globalAuthEndpoint = "https://login.microsoftonline.com" ;
immutable string globalGraphEndpoint = "https://graph.microsoft.com" ;
// - US Government L4
immutable string usl4AuthEndpoint = "https://login.microsoftonline.us" ;
immutable string usl4GraphEndpoint = "https://graph.microsoft.us" ;
// - US Government L5
immutable string usl5AuthEndpoint = "https://login.microsoftonline.us" ;
immutable string usl5GraphEndpoint = "https://dod-graph.microsoft.us" ;
// - Germany
immutable string deAuthEndpoint = "https://login.microsoftonline.de" ;
immutable string deGraphEndpoint = "https://graph.microsoft.de" ;
// - China
immutable string cnAuthEndpoint = "https://login.chinacloudapi.cn" ;
immutable string cnGraphEndpoint = "https://microsoftgraph.chinacloudapi.cn" ;
2023-06-20 01:28:17 +02:00
2024-01-08 23:13:17 +01:00
// Application Version
//immutable string applicationVersion = "onedrive " ~ strip(import("version"));
immutable string applicationVersion = "v2.5.0-alpha-5" ~ " GitHub version: " ~ strip ( import ( "version" ) ) ;
2023-06-20 01:28:17 +02:00
2024-01-08 23:13:17 +01:00
// Application items that depend on application run-time environment, thus cannot be immutable
// Public variables
// Logging output
bool verboseLogging = false ;
bool debugLogging = false ;
long verbosityCount = 0 ;
// Was the application just authorised - paste of response uri
bool applicationAuthorizeResponseUri = false ;
// Store the refreshToken for use within the application
const ( char ) [ ] refreshToken ;
// Store the current accessToken for use within the application
const ( char ) [ ] accessToken ;
// Store the 'refresh_token' file path
string refreshTokenFilePath = "" ;
// Store the accessTokenExpiration for use within the application
SysTime accessTokenExpiration ;
// Store the 'session_upload.CRC32-HASH' file path
string uploadSessionFilePath = "" ;
2024-02-26 04:48:33 +01:00
// API initialisation flags
2024-01-08 23:13:17 +01:00
bool apiWasInitialised = false ;
bool syncEngineWasInitialised = false ;
2024-02-02 19:49:07 +01:00
// Important Account Details
2024-01-08 23:13:17 +01:00
string accountType ;
string defaultDriveId ;
string defaultRootId ;
2024-02-02 19:49:07 +01:00
// Sync Operations
2024-01-08 23:13:17 +01:00
bool fullScanTrueUpRequired = false ;
bool surpressLoggingOutput = false ;
2024-01-19 20:15:33 +01:00
// Number of concurrent threads when downloading and uploading data
ulong defaultConcurrentThreads = 8 ;
2024-01-08 23:13:17 +01:00
// All application run-time paths are formulated from this as a set of defaults
// - What is the home path of the actual 'user' that is running the application
string defaultHomePath = "" ;
// - What is the config path for the application. By default, this is ~/.config/onedrive but can be overridden by using --confdir
string configDirName = defaultConfigDirName ;
// - In case we have to use a system config directory such as '/etc/onedrive' or similar, store that path in this variable
private string systemConfigDirName = "" ;
// - Store the configured converted octal value for directory permissions
private int configuredDirectoryPermissionMode ;
// - Store the configured converted octal value for file permissions
private int configuredFilePermissionMode ;
// - Store the 'delta_link' file path
private string deltaLinkFilePath = "" ;
// - Store the 'items.sqlite3' file path
string databaseFilePath = "" ;
// - Store the 'items-dryrun.sqlite3' file path
string databaseFilePathDryRun = "" ;
// - Store the user 'config' file path
private string userConfigFilePath = "" ;
// - Store the system 'config' file path
private string systemConfigFilePath = "" ;
// - What is the 'config' file path that will be used?
private string applicableConfigFilePath = "" ;
// - Store the 'sync_list' file path
string syncListFilePath = "" ;
2024-02-26 04:48:33 +01:00
// OneDrive Business Shared File handling - what directory will be used?
string configuredBusinessSharedFilesDirectoryName = "" ;
2024-01-08 23:13:17 +01:00
// Hash files so that we can detect when the configuration has changed, in items that will require a --resync
private string configHashFile = "" ;
private string configBackupFile = "" ;
private string syncListHashFile = "" ;
// Store the actual 'runtime' hash
private string currentConfigHash = "" ;
private string currentSyncListHash = "" ;
// Store the previous config files hash values (file contents)
private string previousConfigHash = "" ;
private string previousSyncListHash = "" ;
2024-02-26 04:48:33 +01:00
2024-01-08 23:13:17 +01:00
// Store items that come in from the 'config' file, otherwise these need to be set the the defaults
private string configFileSyncDir = defaultSyncDir ;
private string configFileSkipFile = defaultSkipFile ;
private string configFileSkipDir = "" ; // Default here is no directories are skipped
private string configFileDriveId = "" ; // Default here is that no drive id is specified
private bool configFileSkipDotfiles = false ;
private bool configFileSkipSymbolicLinks = false ;
private bool configFileSyncBusinessSharedItems = false ;
// File permission values (set via initialise function)
private int convertedPermissionValue ;
// Array of values that are the actual application runtime configuration
// The values stored in these array's are the actual application configuration which can then be accessed by getValue & setValue
string [ string ] stringValues ;
long [ string ] longValues ;
bool [ string ] boolValues ;
bool shellEnvironmentSet = false ;
// Initialise the application configuration
2024-01-13 20:54:15 +01:00
bool initialise ( string confdirOption , bool helpRequested ) {
2024-01-08 23:13:17 +01:00
// Default runtime configuration - entries in config file ~/.config/onedrive/config or derived from variables above
// 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()
// The below becomes the 'default' application configuration before config file and/or cli options are overlayed on top
// - Set the required default values
stringValues [ "application_id" ] = defaultApplicationId ;
2021-02-06 10:46:56 +01:00
stringValues [ "log_dir" ] = defaultLogFileDir ;
2024-01-08 23:13:17 +01:00
stringValues [ "skip_dir" ] = defaultSkipDir ;
stringValues [ "skip_file" ] = defaultSkipFile ;
stringValues [ "sync_dir" ] = defaultSyncDir ;
stringValues [ "user_agent" ] = defaultUserAgent ;
// - The 'drive_id' is used when we specify a specific OneDrive ID when attempting to sync Shared Folders and SharePoint items
2020-05-20 03:37:11 +02:00
stringValues [ "drive_id" ] = "" ;
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" ] = "" ;
2024-01-08 23:13:17 +01:00
2020-10-05 23:06:04 +02:00
// Support single-tenant applications that are not able to use the "common" multiplexer
2024-01-08 23:13:17 +01:00
stringValues [ "azure_tenant_id" ] = "" ;
// - Store how many times was --verbose added
longValues [ "verbose" ] = verbosityCount ;
// - The amount of time (seconds) between monitor sync loops
longValues [ "monitor_interval" ] = 300 ;
// - What size of file should be skipped?
longValues [ "skip_size" ] = 0 ;
// - How many 'loops' when using --monitor, before we print out high frequency recurring items?
longValues [ "monitor_log_frequency" ] = 12 ;
// - 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 ;
// - 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 ;
// - Configure the default folder permission attributes for newly created folders
2020-10-29 22:00:26 +01:00
longValues [ "sync_dir_permissions" ] = defaultDirectoryPermissionMode ;
2024-01-08 23:13:17 +01:00
// - Configure the default file permission attributes for newly created file
2020-10-29 22:00:26 +01:00
longValues [ "sync_file_permissions" ] = defaultFilePermissionMode ;
2024-01-08 23:13:17 +01:00
// - Configure download / upload rate limits
2021-01-03 22:28:34 +01:00
longValues [ "rate_limit" ] = 0 ;
2024-01-08 23:13:17 +01:00
// - To ensure we do not fill up the load disk, how much disk space should be reserved by default
2022-05-31 21:57:05 +02:00
longValues [ "space_reservation" ] = 50 * 2 ^ ^ 20 ; // 50 MB as Bytes
2023-06-20 01:28:17 +02:00
// HTTPS & CURL Operation Settings
// - Maximum time an operation is allowed to take
2024-01-19 20:15:33 +01:00
// This includes dns resolution, connecting, data transfer, etc - controls CURLOPT_TIMEOUT
// CURLOPT_TIMEOUT: This option sets the maximum time in seconds that you allow the libcurl transfer operation to take.
// This is useful for controlling how long a specific transfer should take before it is considered too slow and aborted. However, it does not directly control the keep-alive time of a socket.
2023-06-20 01:28:17 +02:00
longValues [ "operation_timeout" ] = defaultOperationTimeout ;
// libcurl dns_cache_timeout timeout
longValues [ "dns_timeout" ] = defaultDnsTimeout ;
2024-01-19 20:15:33 +01:00
// Timeout for HTTPS connections - controls CURLOPT_CONNECTTIMEOUT
// CURLOPT_CONNECTTIMEOUT: This option sets the timeout, in seconds, for the connection phase. It is the maximum time allowed for the connection to be established.
2023-06-20 01:28:17 +02:00
longValues [ "connect_timeout" ] = defaultConnectTimeout ;
// Timeout for activity on a HTTPS connection
longValues [ "data_timeout" ] = defaultDataTimeout ;
// What IP protocol version should be used when communicating with OneDrive
longValues [ "ip_protocol_version" ] = defaultIpProtocol ; // 0 = IPv4 + IPv6, 1 = IPv4 Only, 2 = IPv6 Only
2024-01-08 23:13:17 +01:00
2024-01-19 20:15:33 +01:00
// Number of concurrent threads
2024-02-26 04:48:33 +01:00
longValues [ "threads" ] = defaultConcurrentThreads ; // Default is 8, user can increase to max of 16 or decrease
2024-01-19 20:15:33 +01:00
2024-01-08 23:13:17 +01:00
// - Do we wish to upload only?
boolValues [ "upload_only" ] = false ;
// - Do we need to check for the .nomount file on the mount point?
boolValues [ "check_nomount" ] = false ;
// - Do we need to check for the .nosync file anywhere?
boolValues [ "check_nosync" ] = false ;
// - Do we wish to download only?
boolValues [ "download_only" ] = false ;
// - Do we disable notifications?
boolValues [ "disable_notifications" ] = false ;
// - Do we bypass all the download validation?
// This is critically important not to disable, but because of SharePoint 'feature' can be highly desirable to enable
boolValues [ "disable_download_validation" ] = false ;
// - Do we bypass all the upload validation?
// This is critically important not to disable, but because of SharePoint 'feature' can be highly desirable to enable
boolValues [ "disable_upload_validation" ] = false ;
// - Do we enable logging?
boolValues [ "enable_logging" ] = false ;
// - Do we force HTTP 1.1 for connections to the OneDrive API
// By default we use the curl library default, which should be HTTP2 for most operations governed by the OneDrive API
boolValues [ "force_http_11" ] = false ;
// - Do we treat the local file system as the source of truth for our data?
boolValues [ "local_first" ] = false ;
// - Do we ignore local file deletes, so that all files are retained online?
boolValues [ "no_remote_delete" ] = false ;
// - Do we skip symbolic links?
boolValues [ "skip_symlinks" ] = false ;
// - Do we enable debugging for all HTTPS flows. Critically important for debugging API issues.
boolValues [ "debug_https" ] = false ;
// - Do we skip .files and .folders?
boolValues [ "skip_dotfiles" ] = false ;
// - Do we perform a 'dry-run' with no local or remote changes actually being performed?
boolValues [ "dry_run" ] = false ;
// - Do we sync all the files in the 'sync_dir' root?
boolValues [ "sync_root_files" ] = false ;
// - Do we delete source after successful transfer?
boolValues [ "remove_source_files" ] = false ;
// - Do we perform strict matching for skip_dir?
boolValues [ "skip_dir_strict_match" ] = false ;
// - Do we perform a --resync?
boolValues [ "resync" ] = false ;
// - resync now needs to be acknowledged based on the 'risk' of using it
boolValues [ "resync_auth" ] = false ;
// - Ignore data safety checks and overwrite local data rather than preserve & rename
// This is a config file option ONLY
boolValues [ "bypass_data_preservation" ] = false ;
// - Allow enable / disable of the syncing of OneDrive Business Shared items (files & folders) via configuration file
boolValues [ "sync_business_shared_items" ] = false ;
// - Log to application output running configuration values
boolValues [ "display_running_config" ] = false ;
// - Configure read-only authentication scope
boolValues [ "read_only_auth_scope" ] = false ;
// - Flag to cleanup local files when using --download-only
boolValues [ "cleanup_local_files" ] = false ;
// Webhook Feature Options
boolValues [ "webhook_enabled" ] = false ;
stringValues [ "webhook_public_url" ] = "" ;
stringValues [ "webhook_listening_host" ] = "" ;
longValues [ "webhook_listening_port" ] = 8888 ;
longValues [ "webhook_expiration_interval" ] = 600 ;
longValues [ "webhook_renewal_interval" ] = 300 ;
longValues [ "webhook_retry_interval" ] = 60 ;
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
2024-01-08 23:13:17 +01:00
addLogEntry ( "runtime_environment: HOME environment variable detected, expansion of '~' should be possible" , [ "debug" ] ) ;
defaultHomePath = environment . get ( "HOME" ) ;
shellEnvironmentSet = true ;
2019-04-11 04:26:20 +02:00
} 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
2024-01-08 23:13:17 +01:00
addLogEntry ( "runtime_environment: No HOME, SHELL or USER environment variable configuration detected. Expansion of '~' not possible" , [ "debug" ] ) ;
defaultHomePath = "/root" ;
shellEnvironmentSet = false ;
2019-04-11 04:26:20 +02:00
} else {
// A shell & valid user is set, but no HOME is set, use ~ which can be expanded
2024-01-08 23:13:17 +01:00
addLogEntry ( "runtime_environment: SHELL and USER environment variable detected, expansion of '~' should be possible" , [ "debug" ] ) ;
defaultHomePath = "~" ;
shellEnvironmentSet = true ;
2019-04-11 04:26:20 +02:00
}
}
2024-01-08 23:13:17 +01:00
// outcome of setting defaultHomePath
addLogEntry ( "runtime_environment: Calculated defaultHomePath: " ~ defaultHomePath , [ "debug" ] ) ;
// DEVELOPER OPTIONS
// 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 ;
// 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 ;
// 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 model
boolValues [ "force_children_scan" ] = false ;
// 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 ;
// Function variables
2019-04-11 04:26:20 +02:00
string configDirBase ;
2020-06-05 06:49:31 +02:00
string systemConfigDirBase ;
2024-01-08 23:13:17 +01:00
bool configurationInitialised = false ;
// Initialise the application configuration, using the provided --confdir option was passed in
if ( ! confdirOption . empty ) {
2019-04-11 04:26:20 +02:00
// A CLI 'confdir' was passed in
2024-01-08 23:13:17 +01:00
// Clean up any stray " .. these should not be there for correct process handling of the configuration option
2022-07-03 21:59:00 +02:00
confdirOption = strip ( confdirOption , "\"" ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "configDirName: CLI override to set configDirName to: " ~ confdirOption , [ "debug" ] ) ;
2019-04-11 04:26:20 +02:00
if ( canFind ( confdirOption , "~" ) ) {
// A ~ was found
2024-01-08 23:13:17 +01:00
addLogEntry ( "configDirName: A '~' was found in configDirName, using the calculated 'defaultHomePath' to replace '~'" , [ "debug" ] ) ;
configDirName = defaultHomePath ~ strip ( confdirOption , "~" , "~" ) ;
2019-04-11 04:26:20 +02:00
} else {
configDirName = confdirOption ;
}
} else {
2024-01-08 23:13:17 +01:00
// Determine the base directory relative to which user specific configuration files should be stored
2019-04-11 04:26:20 +02:00
if ( environment . get ( "XDG_CONFIG_HOME" ) ! = "" ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "configDirBase: XDG_CONFIG_HOME environment variable set" , [ "debug" ] ) ;
2019-04-11 04:26:20 +02:00
configDirBase = environment . get ( "XDG_CONFIG_HOME" ) ;
} else {
// XDG_CONFIG_HOME does not exist on systems where X11 is not present - ie - headless systems / servers
2024-01-08 23:13:17 +01:00
addLogEntry ( "configDirBase: WARNING - no XDG_CONFIG_HOME environment variable set" , [ "debug" ] ) ;
configDirBase = buildNormalizedPath ( buildPath ( defaultHomePath , ".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
}
2024-01-08 23:13:17 +01:00
2019-04-11 04:26:20 +02:00
// Output configDirBase calculation
2024-01-08 23:13:17 +01:00
addLogEntry ( "configDirBase: " ~ configDirBase , [ "debug" ] ) ;
// Set the calculated application configuration directory
addLogEntry ( "configDirName: Configuring application to use calculated config path" , [ "debug" ] ) ;
2019-04-11 04:26:20 +02:00
// configDirBase contains the correct path so we do not need to check for presence of '~'
2024-01-08 23:13:17 +01:00
configDirName = buildNormalizedPath ( buildPath ( 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 '~'
2024-01-08 23:13:17 +01:00
systemConfigDirName = buildNormalizedPath ( buildPath ( systemConfigDirBase , "onedrive" ) ) ;
2019-04-11 04:26:20 +02:00
}
2024-01-08 23:13:17 +01:00
// Configuration directory should now have been correctly identified
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
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: --confdir entered value is an existing file instead of an existing directory" ) ;
2022-07-20 02:09:20 +02:00
} else {
// other error
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: " ~ confdirOption ~ " is a file rather than a directory" ) ;
2022-07-20 02:09:20 +02:00
}
// Must exit
exit ( EXIT_FAILURE ) ;
}
2020-10-29 22:00:26 +01:00
}
2024-01-08 23:13:17 +01:00
2020-05-20 03:37:11 +02:00
// Update application set variables based on configDirName
2024-01-08 23:13:17 +01:00
// - What is the full path for the 'refresh_token'
refreshTokenFilePath = buildNormalizedPath ( buildPath ( configDirName , "refresh_token" ) ) ;
// - What is the full path for the 'delta_link'
deltaLinkFilePath = buildNormalizedPath ( buildPath ( configDirName , "delta_link" ) ) ;
// - What is the full path for the 'items.sqlite3' - the database cache file
databaseFilePath = buildNormalizedPath ( buildPath ( configDirName , "items.sqlite3" ) ) ;
// - What is the full path for the 'items-dryrun.sqlite3' - the dry-run database cache file
databaseFilePathDryRun = buildNormalizedPath ( buildPath ( configDirName , "items-dryrun.sqlite3" ) ) ;
// - What is the full path for the 'resume_upload'
uploadSessionFilePath = buildNormalizedPath ( buildPath ( configDirName , "session_upload" ) ) ;
// - What is the full path for the 'sync_list' file
syncListFilePath = buildNormalizedPath ( buildPath ( configDirName , "sync_list" ) ) ;
// - What is the full path for the 'config' - the user file to configure the application
userConfigFilePath = buildNormalizedPath ( buildPath ( configDirName , "config" ) ) ;
// - What is the full path for the system 'config' file if it is required
systemConfigFilePath = buildNormalizedPath ( buildPath ( systemConfigDirName , "config" ) ) ;
// To determine if any configuration items has changed, where a --resync would be required, we need to have a hash file for the following items
// - 'config.backup' file
// - applicable 'config' file
// - 'sync_list' file
// - 'business_shared_items' file
configBackupFile = buildNormalizedPath ( buildPath ( configDirName , ".config.backup" ) ) ;
configHashFile = buildNormalizedPath ( buildPath ( configDirName , ".config.hash" ) ) ;
syncListHashFile = buildNormalizedPath ( buildPath ( configDirName , ".sync_list.hash" ) ) ;
2024-02-26 04:48:33 +01:00
2020-05-20 03:37:11 +02:00
// Debug Output for application set variables based on configDirName
2024-01-08 23:13:17 +01:00
addLogEntry ( "refreshTokenFilePath = " ~ refreshTokenFilePath , [ "debug" ] ) ;
addLogEntry ( "deltaLinkFilePath = " ~ deltaLinkFilePath , [ "debug" ] ) ;
addLogEntry ( "databaseFilePath = " ~ databaseFilePath , [ "debug" ] ) ;
addLogEntry ( "databaseFilePathDryRun = " ~ databaseFilePathDryRun , [ "debug" ] ) ;
addLogEntry ( "uploadSessionFilePath = " ~ uploadSessionFilePath , [ "debug" ] ) ;
addLogEntry ( "userConfigFilePath = " ~ userConfigFilePath , [ "debug" ] ) ;
addLogEntry ( "syncListFilePath = " ~ syncListFilePath , [ "debug" ] ) ;
addLogEntry ( "systemConfigFilePath = " ~ systemConfigFilePath , [ "debug" ] ) ;
addLogEntry ( "configBackupFile = " ~ configBackupFile , [ "debug" ] ) ;
addLogEntry ( "configHashFile = " ~ configHashFile , [ "debug" ] ) ;
addLogEntry ( "syncListHashFile = " ~ syncListHashFile , [ "debug" ] ) ;
// Configure the Hash and Backup File Permission Value
string valueToConvert = to ! string ( defaultFilePermissionMode ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
convertedPermissionValue = to ! int ( convertedValue ) ;
2024-01-13 20:54:15 +01:00
// Do not try and load any user configuration file if --help was used
if ( helpRequested ) {
return true ;
} else {
// Initialise the application using the configuration file if it exists
if ( ! exists ( userConfigFilePath ) ) {
// 'user' configuration file does not exist
// Is there a system configuration file?
if ( ! exists ( systemConfigFilePath ) ) {
// 'system' configuration file does not exist
addLogEntry ( "No user or system config file found, using application defaults" , [ "verbose" ] ) ;
applicableConfigFilePath = userConfigFilePath ;
configurationInitialised = true ;
} else {
// 'system' configuration file exists
// can we load the configuration file without error?
if ( loadConfigFile ( systemConfigFilePath ) ) {
// configuration file loaded without error
addLogEntry ( "System configuration file successfully loaded" ) ;
// Set 'applicableConfigFilePath' to equal the 'config' we loaded
applicableConfigFilePath = systemConfigFilePath ;
// Update the configHashFile path value to ensure we are using the system 'config' file for the hash
configHashFile = buildNormalizedPath ( buildPath ( systemConfigDirName , ".config.hash" ) ) ;
configurationInitialised = true ;
} else {
// there was a problem loading the configuration file
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "System configuration file has errors - please check your configuration" ) ;
}
}
2020-06-05 06:49:31 +02:00
} else {
2024-01-13 20:54:15 +01:00
// 'user' configuration file exists
2020-06-05 06:49:31 +02:00
// can we load the configuration file without error?
2024-01-13 20:54:15 +01:00
if ( loadConfigFile ( userConfigFilePath ) ) {
2020-06-05 06:49:31 +02:00
// configuration file loaded without error
2024-01-13 20:54:15 +01:00
addLogEntry ( "Configuration file successfully loaded" ) ;
2024-01-08 23:13:17 +01:00
// Set 'applicableConfigFilePath' to equal the 'config' we loaded
2024-01-13 20:54:15 +01:00
applicableConfigFilePath = userConfigFilePath ;
2024-01-08 23:13:17 +01:00
configurationInitialised = true ;
2020-06-05 06:49:31 +02:00
} else {
// there was a problem loading the configuration file
2024-01-08 23:13:17 +01:00
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
2024-01-13 20:54:15 +01:00
addLogEntry ( "Configuration file has errors - please check your configuration" ) ;
2020-06-05 06:49:31 +02:00
}
2024-01-08 23:13:17 +01:00
}
2024-01-13 20:54:15 +01:00
// Advise the user path that we will use for the application state data
if ( canFind ( applicableConfigFilePath , configDirName ) ) {
addLogEntry ( "Using 'user' configuration path for application state data: " ~ configDirName , [ "verbose" ] ) ;
} else {
if ( canFind ( applicableConfigFilePath , systemConfigDirName ) ) {
addLogEntry ( "Using 'system' configuration path for application state data: " ~ systemConfigDirName , [ "verbose" ] ) ;
}
2024-01-08 23:13:17 +01:00
}
}
// return if the configuration was initialised
return configurationInitialised ;
}
// Create a backup of the 'config' file if it does not exist
void createBackupConfigFile ( ) {
if ( ! getValueBool ( "dry_run" ) ) {
// Is there a backup of the config file if the config file exists?
if ( exists ( applicableConfigFilePath ) ) {
addLogEntry ( "Creating a backup of the applicable config file" , [ "debug" ] ) ;
// create backup copy of current config file
std . file . copy ( applicableConfigFilePath , configBackupFile ) ;
// File Copy should only be readable by the user who created it - 0600 permissions needed
configBackupFile . setAttributes ( convertedPermissionValue ) ;
2018-12-20 00:51:21 +01:00
}
2024-01-08 23:13:17 +01:00
} else {
// --dry-run scenario ... technically we should not be making any local file changes .......
addLogEntry ( "DRY RUN: Not creating backup config file as --dry-run has been used" ) ;
}
}
// Return a given string value based on the provided key
string getValueString ( string key ) {
auto p = key in stringValues ;
if ( p ) {
return * p ;
} else {
throw new Exception ( "Missing config value: " ~ key ) ;
2017-03-12 16:07:45 +01:00
}
2015-09-01 20:45:34 +02:00
}
2024-01-08 23:13:17 +01:00
// Return a given long value based on the provided key
long getValueLong ( string key ) {
auto p = key in longValues ;
if ( p ) {
return * p ;
} else {
throw new Exception ( "Missing config value: " ~ key ) ;
}
}
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// Return a given bool value based on the provided key
bool getValueBool ( string key ) {
auto p = key in boolValues ;
if ( p ) {
return * p ;
} else {
throw new Exception ( "Missing config value: " ~ key ) ;
}
}
// Set a given string value based on the provided key
void setValueString ( string key , string value ) {
stringValues [ key ] = value ;
}
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// Set a given long value based on the provided key
void setValueLong ( string key , long value ) {
longValues [ key ] = value ;
2019-04-11 04:26:20 +02:00
}
2024-01-08 23:13:17 +01:00
// Set a given long value based on the provided key
void setValueBool ( string key , bool value ) {
boolValues [ key ] = value ;
2019-01-26 01:03:00 +01:00
}
2024-01-08 23:13:17 +01:00
// Configure the directory octal permission value
void configureRequiredDirectoryPermisions ( ) {
// return the directory permission mode required
// - return octal!defaultDirectoryPermissionMode; ... cant be used .. which is odd
// 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 ) ;
2019-02-22 08:15:10 +01:00
} else {
2024-01-08 23:13:17 +01:00
// return default as octal integer
string valueToConvert = to ! string ( defaultDirectoryPermissionMode ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
configuredDirectoryPermissionMode = to ! int ( convertedValue ) ;
2019-02-22 08:15:10 +01:00
}
}
2024-01-08 23:13:17 +01:00
// Configure the file octal permission value
void configureRequiredFilePermisions ( ) {
// return the file permission mode required
// - return octal!defaultFilePermissionMode; ... cant be used .. which is odd
// 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 ) ;
2019-04-11 04:26:20 +02:00
} else {
2024-01-08 23:13:17 +01:00
// return default as octal integer
string valueToConvert = to ! string ( defaultFilePermissionMode ) ;
auto convertedValue = parse ! long ( valueToConvert , 8 ) ;
configuredFilePermissionMode = to ! int ( convertedValue ) ;
2019-04-11 04:26:20 +02:00
}
}
2024-01-08 23:13:17 +01:00
// Read the configuredDirectoryPermissionMode and return
int returnRequiredDirectoryPermisions ( ) {
if ( configuredDirectoryPermissionMode = = 0 ) {
// the configured value is zero, this means that directories would get
// values of d---------
configureRequiredDirectoryPermisions ( ) ;
}
return configuredDirectoryPermissionMode ;
2019-04-11 04:26:20 +02:00
}
2024-01-08 23:13:17 +01:00
// Read the configuredFilePermissionMode and return
int returnRequiredFilePermisions ( ) {
if ( configuredFilePermissionMode = = 0 ) {
// the configured value is zero
configureRequiredFilePermisions ( ) ;
}
return configuredFilePermissionMode ;
2019-01-26 01:03:00 +01:00
}
2024-01-08 23:13:17 +01:00
// Load a configuration file from the provided filename
private bool loadConfigFile ( string filename ) {
2022-05-19 08:12:53 +02:00
try {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Reading configuration file: " ~ filename ) ;
2022-05-19 08:12:53 +02:00
readText ( filename ) ;
} catch ( std . file . FileException e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: Unable to access " ~ e . msg ) ;
2022-05-19 08:12:53 +02:00
return false ;
}
2019-02-12 09:58:10 +01:00
auto file = File ( filename , "r" ) ;
2024-01-12 21:46:47 +01:00
scope ( exit ) file . close ( ) ;
scope ( failure ) file . close ( ) ;
foreach ( line ; file . byLine ( ) ) {
string lineBuffer = stripLeft ( line ) . to ! string ;
if ( lineBuffer . empty | | lineBuffer [ 0 ] = = ';' | | lineBuffer [ 0 ] = = '#' ) continue ;
auto c = lineBuffer . matchFirst ( configRegex ) ;
if ( c . empty ) {
addLogEntry ( "Malformed config line: " ~ lineBuffer ) ;
2024-01-13 08:08:55 +01:00
addLogEntry ( ) ;
addLogEntry ( "Please review the documentation on how to correctly configure this application." ) ;
forceExit ( ) ;
2020-05-20 03:37:11 +02:00
}
2024-01-12 21:46:47 +01:00
c . popFront ( ) ; // skip the whole match
string key = c . front . dup ;
c . popFront ( ) ;
// Handle deprecated keys
switch ( key ) {
case "min_notify_changes" :
case "force_http_2" :
addLogEntry ( "The option '" ~ key ~ "' has been depreciated and will be ignored. Please read the updated documentation and update your client configuration to remove this option." ) ;
continue ;
case "sync_business_shared_folders" :
addLogEntry ( ) ;
addLogEntry ( "The option 'sync_business_shared_folders' has been depreciated and the process for synchronising Microsoft OneDrive Business Shared Folders has changed." ) ;
addLogEntry ( "Please review the revised documentation on how to correctly configure this application feature." ) ;
addLogEntry ( "You must update your client configuration and make changes to your local filesystem and online data to use this capability." ) ;
return false ;
default :
break ;
2020-05-20 03:37:11 +02:00
}
2021-11-23 20:54:28 +01:00
2024-01-12 21:46:47 +01:00
// Process other keys
if ( key in boolValues ) {
// Only accept "true" as true value.
setValueBool ( key , c . front . dup = = "true" ? true : false ) ;
if ( key = = "skip_dotfiles" ) configFileSkipDotfiles = true ;
if ( key = = "skip_symlinks" ) configFileSkipSymbolicLinks = true ;
if ( key = = "sync_business_shared_items" ) configFileSyncBusinessSharedItems = true ;
} else if ( key in stringValues ) {
string value = c . front . dup ;
setValueString ( key , value ) ;
if ( key = = "sync_dir" ) {
if ( ! strip ( value ) . empty ) {
configFileSyncDir = value ;
} else {
addLogEntry ( ) ;
addLogEntry ( "Invalid value for key in config file: " ~ key ) ;
addLogEntry ( "ERROR: sync_dir in config file cannot be empty - this is a fatal error and must be corrected" ) ;
addLogEntry ( ) ;
forceExit ( ) ;
2024-01-08 23:13:17 +01:00
}
2024-01-12 21:46:47 +01:00
} else if ( key = = "skip_file" ) {
// Handle multiple 'config' file 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 ) ;
2024-01-08 23:13:17 +01:00
}
2024-01-12 21:46:47 +01:00
} else 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 ) ;
2024-01-08 23:13:17 +01:00
}
2024-01-12 21:46:47 +01:00
} else if ( key = = "single_directory" ) {
string configFileSingleDirectory = strip ( value , "\"" ) ;
setValueString ( "single_directory" , configFileSingleDirectory ) ;
} else if ( key = = "azure_ad_endpoint" ) {
switch ( value ) {
case "" :
addLogEntry ( "Using default config option for Global Azure AD Endpoints" ) ;
break ;
case "USL4" :
addLogEntry ( "Using config option for Azure AD for US Government Endpoints" ) ;
break ;
case "USL5" :
addLogEntry ( "Using config option for Azure AD for US Government Endpoints (DOD)" ) ;
break ;
case "DE" :
addLogEntry ( "Using config option for Azure AD Germany" ) ;
break ;
case "CN" :
addLogEntry ( "Using config option for Azure AD China operated by 21Vianet" ) ;
break ;
default :
addLogEntry ( "Unknown Azure AD Endpoint - using Global Azure AD Endpoints" ) ;
}
} else if ( key = = "application_id" ) {
string tempApplicationId = strip ( value ) ;
if ( tempApplicationId . empty ) {
addLogEntry ( "Invalid value for key in config file - using default value: " ~ key ) ;
addLogEntry ( "application_id in config file cannot be empty - using default application_id" , [ "debug" ] ) ;
setValueString ( "application_id" , defaultApplicationId ) ;
}
} else if ( key = = "drive_id" ) {
string tempDriveId = strip ( value ) ;
if ( tempDriveId . empty ) {
addLogEntry ( ) ;
addLogEntry ( "Invalid value for key in config file: " ~ key ) ;
addLogEntry ( "drive_id in config file cannot be empty - this is a fatal error and must be corrected by removing this entry from your config file." , [ "debug" ] ) ;
addLogEntry ( ) ;
forceExit ( ) ;
2019-04-11 04:26:20 +02:00
} else {
2024-01-12 21:46:47 +01:00
configFileDriveId = tempDriveId ;
}
} else if ( key = = "log_dir" ) {
string tempLogDir = strip ( value ) ;
if ( tempLogDir . empty ) {
addLogEntry ( "Invalid value for key in config file - using default value: " ~ key ) ;
addLogEntry ( "log_dir in config file cannot be empty - using default log_dir" , [ "debug" ] ) ;
setValueString ( "log_dir" , defaultLogFileDir ) ;
}
}
} else if ( key in longValues ) {
ulong thisConfigValue ;
try {
thisConfigValue = to ! ulong ( c . front . dup ) ;
} catch ( std . conv . ConvException ) {
addLogEntry ( "Invalid value for key in config file: " ~ key ) ;
return false ;
}
setValueLong ( key , thisConfigValue ) ;
if ( key = = "monitor_interval" ) { // if key is 'monitor_interval' the value must be 300 or greater
ulong tempValue = thisConfigValue ;
// the temp value needs to be 300 or greater
if ( tempValue < 300 ) {
addLogEntry ( "Invalid value for key in config file - using default value: " ~ key ) ;
tempValue = 300 ;
}
setValueLong ( "monitor_interval" , tempValue ) ;
} else if ( key = = "monitor_fullscan_frequency" ) { // if key is 'monitor_fullscan_frequency' the value must be 12 or greater
ulong tempValue = thisConfigValue ;
// the temp value needs to be 12 or greater
if ( tempValue < 12 ) {
// If this is not set to zero (0) then we are not disabling 'monitor_fullscan_frequency'
if ( tempValue ! = 0 ) {
// invalid value
addLogEntry ( "Invalid value for key in config file - using default value: " ~ key ) ;
tempValue = 12 ;
2019-04-11 04:26:20 +02:00
}
}
2024-01-12 21:46:47 +01:00
setValueLong ( "monitor_fullscan_frequency" , tempValue ) ;
} else if ( key = = "space_reservation" ) { // if key is 'space_reservation' we have to calculate MB -> bytes
ulong tempValue = thisConfigValue ;
// a value of 0 needs to be made at least 1MB ..
if ( tempValue = = 0 ) {
addLogEntry ( "Invalid value for key in config file - using 1MB: " ~ key ) ;
tempValue = 1 ;
}
setValueLong ( "space_reservation" , tempValue * 2 ^ ^ 20 ) ;
} else if ( key = = "ip_protocol_version" ) {
ulong tempValue = thisConfigValue ;
if ( tempValue > 2 ) {
addLogEntry ( "Invalid value for key in config file - using default value: " ~ key ) ;
2024-01-19 20:15:33 +01:00
tempValue = defaultIpProtocol ;
2024-01-12 21:46:47 +01:00
}
setValueLong ( "ip_protocol_version" , tempValue ) ;
2024-01-19 20:15:33 +01:00
} else if ( key = = "threads" ) {
ulong tempValue = thisConfigValue ;
if ( tempValue > 16 ) {
addLogEntry ( "Invalid value for key in config file - using default value: " ~ key ) ;
tempValue = defaultConcurrentThreads ;
}
setValueLong ( "threads" , tempValue ) ;
2019-02-12 09:58:10 +01:00
}
} else {
2024-01-12 21:46:47 +01:00
addLogEntry ( "Unknown key in config file: " ~ key ) ;
2019-02-12 09:58:10 +01:00
return false ;
}
}
2024-01-12 21:46:47 +01:00
// Return that we were able to read in the config file and parse the options without issue
2019-02-12 09:58:10 +01:00
return true ;
}
2024-01-12 21:46:47 +01:00
2024-01-08 23:13:17 +01:00
// Update the application configuration based on CLI passed in parameters
void updateFromArgs ( string [ ] cliArgs ) {
2024-01-19 20:15:33 +01:00
// Add additional CLI options that are NOT configurable via config file
2024-01-08 23:13:17 +01:00
stringValues [ "create_directory" ] = "" ;
stringValues [ "create_share_link" ] = "" ;
stringValues [ "destination_directory" ] = "" ;
stringValues [ "get_file_link" ] = "" ;
stringValues [ "modified_by" ] = "" ;
stringValues [ "sharepoint_library_name" ] = "" ;
stringValues [ "remove_directory" ] = "" ;
stringValues [ "single_directory" ] = "" ;
stringValues [ "source_directory" ] = "" ;
stringValues [ "auth_files" ] = "" ;
stringValues [ "auth_response" ] = "" ;
boolValues [ "display_config" ] = false ;
boolValues [ "display_sync_status" ] = false ;
boolValues [ "display_quota" ] = false ;
boolValues [ "print_token" ] = false ;
boolValues [ "logout" ] = false ;
boolValues [ "reauth" ] = false ;
boolValues [ "monitor" ] = false ;
boolValues [ "synchronize" ] = false ;
boolValues [ "force" ] = false ;
boolValues [ "list_business_shared_items" ] = false ;
2024-02-26 04:48:33 +01:00
boolValues [ "sync_business_shared_files" ] = false ;
2024-01-08 23:13:17 +01:00
boolValues [ "force_sync" ] = false ;
boolValues [ "with_editing_perms" ] = false ;
// Specific options for CLI input handling
stringValues [ "sync_dir_cli" ] = "" ;
// Application Startup option validation
try {
string tmpStr ;
bool tmpBol ;
long tmpVerb ;
// duplicated from main.d to get full help output!
auto opt = getopt (
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
cliArgs ,
std . getopt . config . bundling ,
std . getopt . config . caseSensitive ,
"auth-files" ,
"Perform authentication not via interactive dialog but via files read/writes to these files." ,
& stringValues [ "auth_files" ] ,
"auth-response" ,
"Perform authentication not via interactive dialog but via providing the response url directly." ,
& stringValues [ "auth_response" ] ,
"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" ] ,
"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" ] ,
"cleanup-local-files" ,
"Cleanup additional local files when using --download-only. This will remove local data." ,
& boolValues [ "cleanup_local_files" ] ,
"create-directory" ,
"Create a directory on OneDrive - no sync will be performed." ,
& stringValues [ "create_directory" ] ,
"create-share-link" ,
"Create a shareable link for an existing file on OneDrive" ,
& stringValues [ "create_share_link" ] ,
"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-download-validation" ,
"Disable download validation when downloading from OneDrive" ,
& boolValues [ "disable_download_validation" ] ,
"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-running-config" ,
"Display what options the client has been configured to use on application startup." ,
& boolValues [ "display_running_config" ] ,
"display-sync-status" ,
"Display the sync status of the client - no sync will be performed." ,
& boolValues [ "display_sync_status" ] ,
"display-quota" ,
"Display the quota status of the client - no sync will be performed." ,
& boolValues [ "display_quota" ] ,
"download-only" ,
"Replicate the OneDrive online state locally, by only downloading changes from OneDrive. Do not upload local changes to OneDrive." ,
& 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-11" ,
"Force the use of HTTP 1.1 for all operations" ,
& boolValues [ "force_http_11" ] ,
"force" ,
"Force the deletion of data when a 'big delete' is detected" ,
& boolValues [ "force" ] ,
"force-sync" ,
"Force a synchronization of a specific folder, only when using --sync --single-directory and ignore all non-default skip_dir and skip_file rules" ,
& boolValues [ "force_sync" ] ,
"get-file-link" ,
"Display the file link of a synced file" ,
& stringValues [ "get_file_link" ] ,
"get-sharepoint-drive-id" ,
"Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library" ,
& stringValues [ "sharepoint_library_name" ] ,
"get-O365-drive-id" ,
"Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library (DEPRECIATED)" ,
& stringValues [ "sharepoint_library_name" ] ,
2024-02-26 04:48:33 +01:00
"list-shared-items" ,
"List OneDrive Business Shared Items" ,
& boolValues [ "list_business_shared_items" ] ,
"sync-shared-files" ,
"Sync OneDrive Business Shared Files to the local filesystem" ,
& boolValues [ "sync_business_shared_files" ] ,
2024-01-08 23:13:17 +01:00
"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" ] ,
"modified-by" ,
"Display the last modified by details of a given path" ,
& stringValues [ "modified_by" ] ,
"monitor|m" ,
"Keep monitoring for local and remote changes" ,
& boolValues [ "monitor" ] ,
"monitor-interval" ,
"Number of seconds by which each sync operation is undertaken when idle under monitor mode." ,
& longValues [ "monitor_interval" ] ,
"monitor-fullscan-frequency" ,
"Number of sync runs before performing a full local scan of the synced directory" ,
& longValues [ "monitor_fullscan_frequency" ] ,
"monitor-log-frequency" ,
"Frequency of logging in monitor mode" ,
& longValues [ "monitor_log_frequency" ] ,
"no-remote-delete" ,
"Do not delete local file 'deletes' from OneDrive when using --upload-only" ,
& boolValues [ "no_remote_delete" ] ,
"print-access-token" ,
"Print the access token, useful for debugging" ,
& boolValues [ "print_token" ] ,
"reauth" ,
"Reauthenticate the client with OneDrive" ,
& boolValues [ "reauth" ] ,
"resync" ,
"Forget the last saved state, perform a full sync" ,
& boolValues [ "resync" ] ,
"resync-auth" ,
"Approve the use of performing a --resync action" ,
& boolValues [ "resync_auth" ] ,
"remove-directory" ,
"Remove a directory on OneDrive - no sync will be performed." ,
& stringValues [ "remove_directory" ] ,
"remove-source-files" ,
"Remove source file after successful transfer to OneDrive when using --upload-only" ,
& boolValues [ "remove_source_files" ] ,
"single-directory" ,
"Specify a single local directory within the OneDrive root to sync." ,
& stringValues [ "single_directory" ] ,
"skip-dot-files" ,
"Skip dot files and folders from syncing" ,
& boolValues [ "skip_dotfiles" ] ,
"skip-file" ,
"Skip any files that match this pattern from syncing" ,
& stringValues [ "skip_file" ] ,
"skip-dir" ,
"Skip any directories that match this pattern from syncing" ,
& stringValues [ "skip_dir" ] ,
"skip-size" ,
"Skip new files larger than this size (in MB)" ,
& longValues [ "skip_size" ] ,
"skip-dir-strict-match" ,
"When matching skip_dir directories, only match explicit matches" ,
& boolValues [ "skip_dir_strict_match" ] ,
"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" ] ,
"space-reservation" ,
"The amount of disk space to reserve (in MB) to avoid 100% disk space utilisation" ,
& longValues [ "space_reservation" ] ,
"syncdir" ,
"Specify the local directory used for synchronisation to OneDrive" ,
& stringValues [ "sync_dir_cli" ] ,
"sync|s" ,
"Perform a synchronisation with Microsoft OneDrive" ,
& boolValues [ "synchronize" ] ,
"synchronize" ,
"Perform a synchronisation with Microsoft OneDrive (DEPRECIATED)" ,
& boolValues [ "synchronize" ] ,
"sync-root-files" ,
"Sync all files in sync_dir root when using sync_list." ,
& boolValues [ "sync_root_files" ] ,
"upload-only" ,
"Replicate the locally configured sync_dir state to OneDrive, by only uploading local changes to OneDrive. Do not download changes from OneDrive." ,
& boolValues [ "upload_only" ] ,
"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 ,
"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" ]
) ;
// Was --syncdir used?
if ( ! getValueString ( "sync_dir_cli" ) . empty ) {
// Build the line we need to update and/or write out
string newConfigOptionSyncDirLine = "sync_dir = \"" ~ getValueString ( "sync_dir_cli" ) ~ "\"" ;
// Does a 'config' file exist?
if ( ! exists ( applicableConfigFilePath ) ) {
// No existing 'config' file exists, create it, and write the 'sync_dir' configuration to it
if ( ! getValueBool ( "dry_run" ) ) {
std . file . write ( applicableConfigFilePath , newConfigOptionSyncDirLine ) ;
// Config file should only be readable by the user who created it - 0600 permissions needed
applicableConfigFilePath . setAttributes ( convertedPermissionValue ) ;
}
} else {
// an existing config file exists .. so this now becomes tricky
// string replace 'sync_dir' if it exists, in the existing 'config' file, but only if 'sync_dir' (already read in) is different from 'sync_dir_cli'
if ( ( getValueString ( "sync_dir" ) ) ! = ( getValueString ( "sync_dir_cli" ) ) ) {
// values are different
File applicableConfigFilePathFileHandle = File ( applicableConfigFilePath , "r" ) ;
string lineBuffer ;
string [ ] newConfigFileEntries ;
// read applicableConfigFilePath line by line
auto range = applicableConfigFilePathFileHandle . byLine ( ) ;
// for each 'config' file line
foreach ( line ; range ) {
lineBuffer = stripLeft ( line ) . to ! string ;
if ( lineBuffer . length = = 0 | | lineBuffer [ 0 ] = = ';' | | lineBuffer [ 0 ] = = '#' ) {
newConfigFileEntries ~ = [ lineBuffer ] ;
} else {
auto c = lineBuffer . matchFirst ( configRegex ) ;
if ( ! c . empty ) {
c . popFront ( ) ; // skip the whole match
string key = c . front . dup ;
if ( key = = "sync_dir" ) {
// lineBuffer is the line we want to keep
newConfigFileEntries ~ = [ newConfigOptionSyncDirLine ] ;
} else {
newConfigFileEntries ~ = [ lineBuffer ] ;
}
}
}
}
// close original 'config' file if still open
if ( applicableConfigFilePathFileHandle . isOpen ( ) ) {
// close open file
applicableConfigFilePathFileHandle . close ( ) ;
}
// free memory from file open
object . destroy ( applicableConfigFilePathFileHandle ) ;
// Update the existing item in the file line array
if ( ! getValueBool ( "dry_run" ) ) {
// Open the file with write access using 'w' mode to overwrite existing content
File applicableConfigFilePathFileHandleWrite = File ( applicableConfigFilePath , "w" ) ;
// Write each line from the 'newConfigFileEntries' array to the file
foreach ( line ; newConfigFileEntries ) {
applicableConfigFilePathFileHandleWrite . writeln ( line ) ;
}
// Flush and close the file handle to ensure all data is written
if ( applicableConfigFilePathFileHandleWrite . isOpen ( ) ) {
applicableConfigFilePathFileHandleWrite . flush ( ) ;
applicableConfigFilePathFileHandleWrite . close ( ) ;
}
// free memory from file open
object . destroy ( applicableConfigFilePathFileHandleWrite ) ;
}
}
}
// Final - configure sync_dir with the value of sync_dir_cli so that it can be used as part of the application configuration and detect change
setValueString ( "sync_dir" , getValueString ( "sync_dir_cli" ) ) ;
}
// Was --auth-files used?
if ( ! getValueString ( "auth_files" ) . empty ) {
// --auth-files used, need to validate that '~' was not used as a path identifier, and if yes, perform the correct expansion
string [ ] tempAuthFiles = getValueString ( "auth_files" ) . split ( ":" ) ;
string tempAuthUrl = tempAuthFiles [ 0 ] ;
string tempResponseUrl = tempAuthFiles [ 1 ] ;
string newAuthFilesString ;
// shell expansion if required
if ( ! shellEnvironmentSet ) {
// No shell environment is set, no automatic expansion of '~' if present is possible
// Does the 'currently configured' tempAuthUrl include a ~
if ( canFind ( tempAuthUrl , "~" ) ) {
// A ~ was found in auth_files(authURL)
addLogEntry ( "auth_files: A '~' was found in 'auth_files(authURL)', using the calculated 'homePath' to replace '~' as no SHELL or USER environment variable set" , [ "debug" ] ) ;
tempAuthUrl = buildNormalizedPath ( buildPath ( defaultHomePath , strip ( tempAuthUrl , "~" ) ) ) ;
}
// Does the 'currently configured' tempAuthUrl include a ~
if ( canFind ( tempResponseUrl , "~" ) ) {
// A ~ was found in auth_files(authURL)
addLogEntry ( "auth_files: A '~' was found in 'auth_files(tempResponseUrl)', using the calculated 'homePath' to replace '~' as no SHELL or USER environment variable set" , [ "debug" ] ) ;
tempResponseUrl = buildNormalizedPath ( buildPath ( defaultHomePath , strip ( tempResponseUrl , "~" ) ) ) ;
}
} else {
// Shell environment is set, automatic expansion of '~' if present is possible
// Does the 'currently configured' tempAuthUrl include a ~
if ( canFind ( tempAuthUrl , "~" ) ) {
// A ~ was found in auth_files(authURL)
addLogEntry ( "auth_files: A '~' was found in the configured 'auth_files(authURL)', automatically expanding as SHELL and USER environment variable is set" , [ "debug" ] ) ;
tempAuthUrl = expandTilde ( tempAuthUrl ) ;
}
// Does the 'currently configured' tempAuthUrl include a ~
if ( canFind ( tempResponseUrl , "~" ) ) {
// A ~ was found in auth_files(authURL)
addLogEntry ( "auth_files: A '~' was found in the configured 'auth_files(tempResponseUrl)', automatically expanding as SHELL and USER environment variable is set" , [ "debug" ] ) ;
tempResponseUrl = expandTilde ( tempResponseUrl ) ;
}
}
// Build new string
newAuthFilesString = tempAuthUrl ~ ":" ~ tempResponseUrl ;
addLogEntry ( "auth_files - updated value: " ~ newAuthFilesString , [ "debug" ] ) ;
setValueString ( "auth_files" , newAuthFilesString ) ;
}
if ( opt . helpWanted ) {
outputLongHelp ( opt . options ) ;
exit ( EXIT_SUCCESS ) ;
}
} catch ( GetOptException e ) {
// getOpt error - must use writeln() here
writeln ( e . msg ) ;
writeln ( "Try 'onedrive -h' for more information" ) ;
exit ( EXIT_FAILURE ) ;
} catch ( Exception e ) {
// general error - must use writeln() here
writeln ( e . msg ) ;
writeln ( "Try 'onedrive -h' for more information" ) ;
exit ( EXIT_FAILURE ) ;
2020-10-29 22:00:26 +01:00
}
}
2024-01-08 23:13:17 +01:00
// Check the arguments passed in for any that will be depreciated
void checkDepreciatedOptions ( string [ ] cliArgs ) {
bool depreciatedCommandsFound = false ;
foreach ( cliArg ; cliArgs ) {
// Check each CLI arg for items that have been depreciated
// --synchronize depreciated in v2.5.0, will be removed in future version
if ( cliArg = = "--synchronize" ) {
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "DEPRECIATION WARNING: --synchronize has been depreciated in favour of --sync or -s" ) ;
depreciatedCommandsFound = true ;
}
// --get-O365-drive-id depreciated in v2.5.0, will be removed in future version
if ( cliArg = = "--get-O365-drive-id" ) {
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "DEPRECIATION WARNING: --get-O365-drive-id has been depreciated in favour of --get-sharepoint-drive-id" ) ;
depreciatedCommandsFound = true ;
}
}
if ( depreciatedCommandsFound ) {
addLogEntry ( "DEPRECIATION WARNING: Depreciated commands will be removed in a future release." ) ;
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
}
}
// Display the applicable application configuration
void displayApplicationConfiguration ( ) {
if ( getValueBool ( "display_running_config" ) ) {
addLogEntry ( "--------------- Application Runtime Configuration ---------------" ) ;
}
// Display application version
addLogEntry ( "onedrive version = " ~ applicationVersion ) ;
// Display all of the pertinent configuration options
addLogEntry ( "Config path = " ~ configDirName ) ;
// Does a config file exist or are we using application defaults
addLogEntry ( "Config file found in config path = " ~ to ! string ( exists ( applicableConfigFilePath ) ) ) ;
// Is config option drive_id configured?
addLogEntry ( "Config option 'drive_id' = " ~ getValueString ( "drive_id" ) ) ;
// Config Options as per 'config' file
addLogEntry ( "Config option 'sync_dir' = " ~ getValueString ( "sync_dir" ) ) ;
// logging and notifications
addLogEntry ( "Config option 'enable_logging' = " ~ to ! string ( getValueBool ( "enable_logging" ) ) ) ;
addLogEntry ( "Config option 'log_dir' = " ~ getValueString ( "log_dir" ) ) ;
addLogEntry ( "Config option 'disable_notifications' = " ~ to ! string ( getValueBool ( "disable_notifications" ) ) ) ;
// skip files and directory and 'matching' policy
addLogEntry ( "Config option 'skip_dir' = " ~ getValueString ( "skip_dir" ) ) ;
addLogEntry ( "Config option 'skip_dir_strict_match' = " ~ to ! string ( getValueBool ( "skip_dir_strict_match" ) ) ) ;
addLogEntry ( "Config option 'skip_file' = " ~ getValueString ( "skip_file" ) ) ;
addLogEntry ( "Config option 'skip_dotfiles' = " ~ to ! string ( getValueBool ( "skip_dotfiles" ) ) ) ;
addLogEntry ( "Config option 'skip_symlinks' = " ~ to ! string ( getValueBool ( "skip_symlinks" ) ) ) ;
// --monitor sync process options
addLogEntry ( "Config option 'monitor_interval' = " ~ to ! string ( getValueLong ( "monitor_interval" ) ) ) ;
addLogEntry ( "Config option 'monitor_log_frequency' = " ~ to ! string ( getValueLong ( "monitor_log_frequency" ) ) ) ;
addLogEntry ( "Config option 'monitor_fullscan_frequency' = " ~ to ! string ( getValueLong ( "monitor_fullscan_frequency" ) ) ) ;
// sync process and method
addLogEntry ( "Config option 'read_only_auth_scope' = " ~ to ! string ( getValueBool ( "read_only_auth_scope" ) ) ) ;
addLogEntry ( "Config option 'dry_run' = " ~ to ! string ( getValueBool ( "dry_run" ) ) ) ;
addLogEntry ( "Config option 'upload_only' = " ~ to ! string ( getValueBool ( "upload_only" ) ) ) ;
addLogEntry ( "Config option 'download_only' = " ~ to ! string ( getValueBool ( "download_only" ) ) ) ;
addLogEntry ( "Config option 'local_first' = " ~ to ! string ( getValueBool ( "local_first" ) ) ) ;
addLogEntry ( "Config option 'check_nosync' = " ~ to ! string ( getValueBool ( "check_nosync" ) ) ) ;
addLogEntry ( "Config option 'check_nomount' = " ~ to ! string ( getValueBool ( "check_nomount" ) ) ) ;
addLogEntry ( "Config option 'resync' = " ~ to ! string ( getValueBool ( "resync" ) ) ) ;
addLogEntry ( "Config option 'resync_auth' = " ~ to ! string ( getValueBool ( "resync_auth" ) ) ) ;
addLogEntry ( "Config option 'cleanup_local_files' = " ~ to ! string ( getValueBool ( "cleanup_local_files" ) ) ) ;
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// data integrity
addLogEntry ( "Config option 'classify_as_big_delete' = " ~ to ! string ( getValueLong ( "classify_as_big_delete" ) ) ) ;
addLogEntry ( "Config option 'disable_upload_validation' = " ~ to ! string ( getValueBool ( "disable_upload_validation" ) ) ) ;
addLogEntry ( "Config option 'disable_download_validation' = " ~ to ! string ( getValueBool ( "disable_download_validation" ) ) ) ;
addLogEntry ( "Config option 'bypass_data_preservation' = " ~ to ! string ( getValueBool ( "bypass_data_preservation" ) ) ) ;
addLogEntry ( "Config option 'no_remote_delete' = " ~ to ! string ( getValueBool ( "no_remote_delete" ) ) ) ;
addLogEntry ( "Config option 'remove_source_files' = " ~ to ! string ( getValueBool ( "remove_source_files" ) ) ) ;
addLogEntry ( "Config option 'sync_dir_permissions' = " ~ to ! string ( getValueLong ( "sync_dir_permissions" ) ) ) ;
addLogEntry ( "Config option 'sync_file_permissions' = " ~ to ! string ( getValueLong ( "sync_file_permissions" ) ) ) ;
addLogEntry ( "Config option 'space_reservation' = " ~ to ! string ( getValueLong ( "space_reservation" ) ) ) ;
// curl operations
addLogEntry ( "Config option 'application_id' = " ~ getValueString ( "application_id" ) ) ;
addLogEntry ( "Config option 'azure_ad_endpoint' = " ~ getValueString ( "azure_ad_endpoint" ) ) ;
addLogEntry ( "Config option 'azure_tenant_id' = " ~ getValueString ( "azure_tenant_id" ) ) ;
addLogEntry ( "Config option 'user_agent' = " ~ getValueString ( "user_agent" ) ) ;
addLogEntry ( "Config option 'force_http_11' = " ~ to ! string ( getValueBool ( "force_http_11" ) ) ) ;
addLogEntry ( "Config option 'debug_https' = " ~ to ! string ( getValueBool ( "debug_https" ) ) ) ;
addLogEntry ( "Config option 'rate_limit' = " ~ to ! string ( getValueLong ( "rate_limit" ) ) ) ;
addLogEntry ( "Config option 'operation_timeout' = " ~ to ! string ( getValueLong ( "operation_timeout" ) ) ) ;
addLogEntry ( "Config option 'dns_timeout' = " ~ to ! string ( getValueLong ( "dns_timeout" ) ) ) ;
addLogEntry ( "Config option 'connect_timeout' = " ~ to ! string ( getValueLong ( "connect_timeout" ) ) ) ;
addLogEntry ( "Config option 'data_timeout' = " ~ to ! string ( getValueLong ( "data_timeout" ) ) ) ;
addLogEntry ( "Config option 'ip_protocol_version' = " ~ to ! string ( getValueLong ( "ip_protocol_version" ) ) ) ;
2024-01-19 20:15:33 +01:00
addLogEntry ( "Config option 'threads' = " ~ to ! string ( getValueLong ( "threads" ) ) ) ;
2024-01-08 23:13:17 +01:00
// Is sync_list configured ?
if ( exists ( syncListFilePath ) ) {
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "Selective sync 'sync_list' configured = true" ) ;
addLogEntry ( "sync_list config option 'sync_root_files' = " ~ to ! string ( getValueBool ( "sync_root_files" ) ) ) ;
addLogEntry ( "sync_list contents:" ) ;
// Output the sync_list contents
auto syncListFile = File ( syncListFilePath , "r" ) ;
auto range = syncListFile . byLine ( ) ;
foreach ( line ; range )
{
addLogEntry ( to ! string ( line ) ) ;
}
} else {
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "Selective sync 'sync_list' configured = false" ) ;
2020-11-08 22:06:48 +01:00
}
2024-01-08 23:13:17 +01:00
// Is sync_business_shared_items enabled and configured ?
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "Config option 'sync_business_shared_items' = " ~ to ! string ( getValueBool ( "sync_business_shared_items" ) ) ) ;
2024-02-26 04:48:33 +01:00
addLogEntry ( "Config option 'Shared Files Directory' = " ~ configuredBusinessSharedFilesDirectoryName ) ;
2024-01-08 23:13:17 +01:00
// Are webhooks enabled?
addLogEntry ( ) ; // used instead of an empty 'writeln();' to ensure the line break is correct in the buffered console output ordering
addLogEntry ( "Config option 'webhook_enabled' = " ~ to ! string ( getValueBool ( "webhook_enabled" ) ) ) ;
if ( getValueBool ( "webhook_enabled" ) ) {
addLogEntry ( "Config option 'webhook_public_url' = " ~ getValueString ( "webhook_public_url" ) ) ;
addLogEntry ( "Config option 'webhook_listening_host' = " ~ getValueString ( "webhook_listening_host" ) ) ;
addLogEntry ( "Config option 'webhook_listening_port' = " ~ to ! string ( getValueLong ( "webhook_listening_port" ) ) ) ;
addLogEntry ( "Config option 'webhook_expiration_interval' = " ~ to ! string ( getValueLong ( "webhook_expiration_interval" ) ) ) ;
addLogEntry ( "Config option 'webhook_renewal_interval' = " ~ to ! string ( getValueLong ( "webhook_renewal_interval" ) ) ) ;
addLogEntry ( "Config option 'webhook_retry_interval' = " ~ to ! string ( getValueLong ( "webhook_retry_interval" ) ) ) ;
}
if ( getValueBool ( "display_running_config" ) ) {
addLogEntry ( "-----------------------------------------------------------------" ) ;
}
}
// Prompt the user to accept the risk of using --resync
bool displayResyncRiskForAcceptance ( ) {
// what is the user risk acceptance?
bool userRiskAcceptance = false ;
// Did the user use --resync-auth or 'resync_auth' in the config file to negate presenting this message?
if ( ! getValueBool ( "resync_auth" ) ) {
// need to prompt user
char response ;
// --resync warning message
addLogEntry ( "" , [ "consoleOnly" ] ) ; // new line, console only
addLogEntry ( "The usage of --resync will delete your local 'onedrive' client state, thus no record of your current 'sync status' will exist." , [ "consoleOnly" ] ) ;
addLogEntry ( "This has the potential to overwrite local versions of files with perhaps older versions of documents downloaded from OneDrive, resulting in local data loss." , [ "consoleOnly" ] ) ;
addLogEntry ( "If in doubt, backup your local data before using --resync" , [ "consoleOnly" ] ) ;
addLogEntry ( "" , [ "consoleOnly" ] ) ; // new line, console only
addLogEntry ( "Are you sure you wish to proceed with --resync? [Y/N] " , [ "consoleOnlyNoNewLine" ] ) ;
try {
// Attempt to read user response
string input = readln ( ) . strip ;
if ( input . length > 0 ) {
response = std . ascii . toUpper ( input [ 0 ] ) ;
}
} catch ( std . format . FormatException e ) {
userRiskAcceptance = false ;
// Caught an error
return EXIT_FAILURE ;
}
// What did the user enter?
addLogEntry ( "--resync warning User Response Entered: " ~ to ! string ( response ) , [ "debug" ] ) ;
// Evaluate user repsonse
if ( ( to ! string ( response ) = = "y" ) | | ( to ! string ( response ) = = "Y" ) ) {
// User has accepted --resync risk to proceed
userRiskAcceptance = true ;
// Are you sure you wish .. does not use writeln();
write ( "\n" ) ;
}
} else {
// resync_auth is true
userRiskAcceptance = true ;
}
// Return the --resync acceptance or not
return userRiskAcceptance ;
}
// Prompt the user to accept the risk of using --force-sync
bool displayForceSyncRiskForAcceptance ( ) {
// what is the user risk acceptance?
bool userRiskAcceptance = false ;
// need to prompt user
char response ;
// --force-sync warning message
addLogEntry ( "" , [ "consoleOnly" ] ) ; // new line, console only
addLogEntry ( "The use of --force-sync will reconfigure the application to use defaults. This may have untold and unknown future impacts." , [ "consoleOnly" ] ) ;
addLogEntry ( "By proceeding in using this option you accept any impacts including any data loss that may occur as a result of using --force-sync." , [ "consoleOnly" ] ) ;
addLogEntry ( "" , [ "consoleOnly" ] ) ; // new line, console only
addLogEntry ( "Are you sure you wish to proceed with --force-sync [Y/N] " , [ "consoleOnlyNoNewLine" ] ) ;
try {
// Attempt to read user response
string input = readln ( ) . strip ;
if ( input . length > 0 ) {
response = std . ascii . toUpper ( input [ 0 ] ) ;
}
} catch ( std . format . FormatException e ) {
userRiskAcceptance = false ;
// Caught an error
return EXIT_FAILURE ;
}
// What did the user enter?
addLogEntry ( "--force-sync warning User Response Entered: " ~ to ! string ( response ) , [ "debug" ] ) ;
// Evaluate user repsonse
if ( ( to ! string ( response ) = = "y" ) | | ( to ! string ( response ) = = "Y" ) ) {
// User has accepted --force-sync risk to proceed
userRiskAcceptance = true ;
// Are you sure you wish .. does not use writeln();
write ( "\n" ) ;
}
// Return the --resync acceptance or not
return userRiskAcceptance ;
2020-10-29 22:00:26 +01:00
}
2024-01-08 23:13:17 +01:00
// Check the application configuration for any changes that need to trigger a --resync
// This function is only called if --resync is not present
bool applicationChangeWhereResyncRequired ( ) {
// Default is that no resync is required
bool resyncRequired = false ;
2024-01-12 01:21:45 +01:00
// Consolidate the flags for different configuration changes
bool [ 9 ] configOptionsDifferent ;
// Handle multiple entries of skip_file
string backupConfigFileSkipFile ;
2024-01-08 23:13:17 +01:00
2024-01-12 01:21:45 +01:00
// Handle multiple entries of skip_dir
string backupConfigFileSkipDir ;
2024-01-08 23:13:17 +01:00
2024-01-12 01:21:45 +01:00
// Create and read the required initial hash files
2024-01-08 23:13:17 +01:00
createRequiredInitialConfigurationHashFiles ( ) ;
// Read in the existing hash file values
readExistingConfigurationHashFiles ( ) ;
2024-01-12 01:21:45 +01:00
// Helper lambda for logging and setting the difference flag
auto logAndSetDifference = ( string message , size_t index ) {
addLogEntry ( message , [ "debug" ] ) ;
configOptionsDifferent [ index ] = true ;
} ;
// Check for changes in the sync_list and business_shared_items files
if ( currentSyncListHash ! = previousSyncListHash )
logAndSetDifference ( "sync_list file has been updated, --resync needed" , 0 ) ;
// Check for updates in the config file
2024-01-08 23:13:17 +01:00
if ( currentConfigHash ! = previousConfigHash ) {
addLogEntry ( "Application configuration file has been updated, checking if --resync needed" ) ;
addLogEntry ( "Using this configBackupFile: " ~ configBackupFile , [ "debug" ] ) ;
2024-01-12 01:21:45 +01:00
2024-01-08 23:13:17 +01:00
if ( exists ( configBackupFile ) ) {
string [ string ] backupConfigStringValues ;
backupConfigStringValues [ "drive_id" ] = "" ;
backupConfigStringValues [ "sync_dir" ] = "" ;
backupConfigStringValues [ "skip_file" ] = "" ;
backupConfigStringValues [ "skip_dir" ] = "" ;
backupConfigStringValues [ "skip_dotfiles" ] = "" ;
backupConfigStringValues [ "skip_symlinks" ] = "" ;
backupConfigStringValues [ "sync_business_shared_items" ] = "" ;
2024-01-12 01:21:45 +01:00
2024-01-08 23:13:17 +01:00
bool drive_id_present = false ;
bool sync_dir_present = false ;
bool skip_file_present = false ;
bool skip_dir_present = false ;
bool skip_dotfiles_present = false ;
bool skip_symlinks_present = false ;
bool sync_business_shared_items_present = false ;
2024-01-12 01:21:45 +01:00
2024-01-08 23:13:17 +01:00
string configOptionModifiedMessage = " was modified since the last time the application was successfully run, --resync required" ;
auto configBackupFileHandle = File ( configBackupFile , "r" ) ;
2024-01-12 01:21:45 +01:00
scope ( exit ) {
if ( configBackupFileHandle . isOpen ( ) ) {
configBackupFileHandle . close ( ) ;
}
}
2024-01-08 23:13:17 +01:00
string lineBuffer ;
auto range = configBackupFileHandle . byLine ( ) ;
foreach ( line ; range ) {
lineBuffer = stripLeft ( line ) . to ! string ;
if ( lineBuffer . length = = 0 | | lineBuffer [ 0 ] = = ';' | | lineBuffer [ 0 ] = = '#' ) continue ;
auto c = lineBuffer . matchFirst ( configRegex ) ;
if ( ! c . empty ) {
c . popFront ( ) ; // skip the whole match
string key = c . front . dup ;
addLogEntry ( "Backup Config Key: " ~ key , [ "debug" ] ) ;
2024-01-12 01:21:45 +01:00
2024-01-08 23:13:17 +01:00
auto p = key in backupConfigStringValues ;
if ( p ) {
c . popFront ( ) ;
2024-01-12 01:21:45 +01:00
string value = c . front . dup ;
// Compare each key value with current config
2024-01-08 23:13:17 +01:00
if ( key = = "drive_id" ) {
drive_id_present = true ;
2024-01-12 01:21:45 +01:00
if ( value ! = getValueString ( "drive_id" ) ) {
logAndSetDifference ( key ~ configOptionModifiedMessage , 2 ) ;
2024-01-08 23:13:17 +01:00
}
}
if ( key = = "sync_dir" ) {
sync_dir_present = true ;
2024-01-12 01:21:45 +01:00
if ( value ! = getValueString ( "sync_dir" ) ) {
logAndSetDifference ( key ~ configOptionModifiedMessage , 3 ) ;
2024-01-08 23:13:17 +01:00
}
}
2024-01-12 01:21:45 +01:00
// skip_file handling
2024-01-08 23:13:17 +01:00
if ( key = = "skip_file" ) {
skip_file_present = true ;
2024-01-12 01:21:45 +01:00
// Handle multiple entries of skip_file
if ( backupConfigFileSkipFile . empty ) {
2024-01-13 22:05:26 +01:00
// currently no entry exists, include 'defaultSkipFile' entries
backupConfigFileSkipFile = defaultSkipFile ~ "|" ~ to ! string ( c . front . dup ) ;
2024-01-12 01:21:45 +01:00
} else {
// add to existing backupConfigFileSkipFile entry
backupConfigFileSkipFile = backupConfigFileSkipFile ~ "|" ~ to ! string ( c . front . dup ) ;
2024-01-08 23:13:17 +01:00
}
}
2024-01-12 01:21:45 +01:00
// skip_dir handling
2024-01-08 23:13:17 +01:00
if ( key = = "skip_dir" ) {
skip_dir_present = true ;
2024-01-12 01:21:45 +01:00
// Handle multiple entries of skip_dir
if ( backupConfigFileSkipDir . empty ) {
// currently no entry exists
backupConfigFileSkipDir = c . front . dup ;
} else {
// add to existing backupConfigFileSkipDir entry
backupConfigFileSkipDir = backupConfigFileSkipDir ~ "|" ~ to ! string ( c . front . dup ) ;
2024-01-08 23:13:17 +01:00
}
}
if ( key = = "skip_dotfiles" ) {
skip_dotfiles_present = true ;
2024-01-12 01:21:45 +01:00
if ( value ! = to ! string ( getValueBool ( "skip_dotfiles" ) ) ) {
logAndSetDifference ( key ~ configOptionModifiedMessage , 6 ) ;
2024-01-08 23:13:17 +01:00
}
}
if ( key = = "skip_symlinks" ) {
skip_symlinks_present = true ;
2024-01-12 01:21:45 +01:00
if ( value ! = to ! string ( getValueBool ( "skip_symlinks" ) ) ) {
logAndSetDifference ( key ~ configOptionModifiedMessage , 7 ) ;
2024-01-08 23:13:17 +01:00
}
}
if ( key = = "sync_business_shared_items" ) {
sync_business_shared_items_present = true ;
2024-01-12 01:21:45 +01:00
if ( value ! = to ! string ( getValueBool ( "sync_business_shared_items" ) ) ) {
logAndSetDifference ( key ~ configOptionModifiedMessage , 8 ) ;
2024-01-08 23:13:17 +01:00
}
}
}
}
}
2024-01-12 01:21:45 +01:00
// skip_file can be specified multiple times
if ( skip_file_present & & backupConfigFileSkipFile ! = configFileSkipFile ) logAndSetDifference ( "skip_file" ~ configOptionModifiedMessage , 4 ) ;
2024-01-08 23:13:17 +01:00
2024-01-12 01:21:45 +01:00
// skip_dir can be specified multiple times
if ( skip_dir_present & & backupConfigFileSkipDir ! = configFileSkipDir ) logAndSetDifference ( "skip_dir" ~ configOptionModifiedMessage , 5 ) ;
2024-01-08 23:13:17 +01:00
2024-01-12 01:21:45 +01:00
// Check for newly added configuration options
if ( ! drive_id_present & & configFileDriveId ! = "" ) logAndSetDifference ( "drive_id newly added ... --resync needed" , 2 ) ;
if ( ! sync_dir_present & & configFileSyncDir ! = defaultSyncDir ) logAndSetDifference ( "sync_dir newly added ... --resync needed" , 3 ) ;
if ( ! skip_file_present & & configFileSkipFile ! = defaultSkipFile ) logAndSetDifference ( "skip_file newly added ... --resync needed" , 4 ) ;
if ( ! skip_dir_present & & configFileSkipDir ! = "" ) logAndSetDifference ( "skip_dir newly added ... --resync needed" , 5 ) ;
if ( ! skip_dotfiles_present & & configFileSkipDotfiles ) logAndSetDifference ( "skip_dotfiles newly added ... --resync needed" , 6 ) ;
if ( ! skip_symlinks_present & & configFileSkipSymbolicLinks ) logAndSetDifference ( "skip_symlinks newly added ... --resync needed" , 7 ) ;
if ( ! sync_business_shared_items_present & & configFileSyncBusinessSharedItems ) logAndSetDifference ( "sync_business_shared_items newly added ... --resync needed" , 8 ) ;
2024-01-08 23:13:17 +01:00
} else {
addLogEntry ( "WARNING: no backup config file was found, unable to validate if any changes made" ) ;
}
}
2024-01-12 01:21:45 +01:00
// Check CLI options
2024-01-08 23:13:17 +01:00
if ( exists ( applicableConfigFilePath ) ) {
2024-01-12 01:21:45 +01:00
if ( configFileSyncDir ! = "" & & configFileSyncDir ! = getValueString ( "sync_dir" ) ) logAndSetDifference ( "sync_dir: CLI override of config file option, --resync needed" , 3 ) ;
if ( configFileSkipFile ! = "" & & configFileSkipFile ! = getValueString ( "skip_file" ) ) logAndSetDifference ( "skip_file: CLI override of config file option, --resync needed" , 4 ) ;
if ( configFileSkipDir ! = "" & & configFileSkipDir ! = getValueString ( "skip_dir" ) ) logAndSetDifference ( "skip_dir: CLI override of config file option, --resync needed" , 5 ) ;
if ( ! configFileSkipDotfiles & & getValueBool ( "skip_dotfiles" ) ) logAndSetDifference ( "skip_dotfiles: CLI override of config file option, --resync needed" , 6 ) ;
if ( ! configFileSkipSymbolicLinks & & getValueBool ( "skip_symlinks" ) ) logAndSetDifference ( "skip_symlinks: CLI override of config file option, --resync needed" , 7 ) ;
}
2021-11-23 20:54:28 +01:00
2024-01-12 01:21:45 +01:00
// Aggregate the result to determine if a resync is required
foreach ( optionDifferent ; configOptionsDifferent ) {
if ( optionDifferent ) {
resyncRequired = true ;
break ;
2024-01-08 23:13:17 +01:00
}
}
2024-02-26 04:48:33 +01:00
// Final override
// In certain situations, regardless of config 'resync' needed status, ignore this so that the application can display 'non-syncable' information
// Options that should now be looked at are:
// --list-shared-items
if ( getValueBool ( "list_business_shared_items" ) ) resyncRequired = false ;
// Return the calculated boolean
2024-01-08 23:13:17 +01:00
return resyncRequired ;
}
// Cleanup hash files that require to be cleaned up when a --resync is issued
void cleanupHashFilesDueToResync ( ) {
if ( ! getValueBool ( "dry_run" ) ) {
// cleanup hash files
addLogEntry ( "Cleaning up configuration hash files" , [ "debug" ] ) ;
safeRemove ( configHashFile ) ;
safeRemove ( syncListHashFile ) ;
} else {
// --dry-run scenario ... technically we should not be making any local file changes .......
addLogEntry ( "DRY RUN: Not removing hash files as --dry-run has been used" ) ;
}
}
// For each of the config files, update the hash data in the hash files
void updateHashContentsForConfigFiles ( ) {
// Are we in a --dry-run scenario?
if ( ! getValueBool ( "dry_run" ) ) {
// Not a dry-run scenario, update the applicable files
// Update applicable 'config' files
if ( exists ( applicableConfigFilePath ) ) {
// Update the hash of the applicable config file
addLogEntry ( "Updating applicable config file hash" , [ "debug" ] ) ;
std . file . write ( configHashFile , computeQuickXorHash ( applicableConfigFilePath ) ) ;
// Hash file should only be readable by the user who created it - 0600 permissions needed
configHashFile . setAttributes ( convertedPermissionValue ) ;
}
// Update 'sync_list' files
if ( exists ( syncListFilePath ) ) {
// update sync_list hash
addLogEntry ( "Updating sync_list hash" , [ "debug" ] ) ;
std . file . write ( syncListHashFile , computeQuickXorHash ( syncListFilePath ) ) ;
// Hash file should only be readable by the user who created it - 0600 permissions needed
syncListHashFile . setAttributes ( convertedPermissionValue ) ;
}
} else {
// --dry-run scenario ... technically we should not be making any local file changes .......
addLogEntry ( "DRY RUN: Not updating hash files as --dry-run has been used" ) ;
}
}
// Create any required hash files for files that help us determine if the configuration has changed since last run
void createRequiredInitialConfigurationHashFiles ( ) {
// Does a 'config' file exist with a valid hash file
if ( exists ( applicableConfigFilePath ) ) {
if ( ! exists ( configHashFile ) ) {
// no existing hash file exists
std . file . write ( configHashFile , "initial-hash" ) ;
// Hash file should only be readable by the user who created it - 0600 permissions needed
configHashFile . setAttributes ( convertedPermissionValue ) ;
}
// Generate the runtime hash for the 'config' file
currentConfigHash = computeQuickXorHash ( applicableConfigFilePath ) ;
}
// Does a 'sync_list' file exist with a valid hash file
if ( exists ( syncListFilePath ) ) {
if ( ! exists ( syncListHashFile ) ) {
// no existing hash file exists
std . file . write ( syncListHashFile , "initial-hash" ) ;
// Hash file should only be readable by the user who created it - 0600 permissions needed
syncListHashFile . setAttributes ( convertedPermissionValue ) ;
}
// Generate the runtime hash for the 'sync_list' file
currentSyncListHash = computeQuickXorHash ( syncListFilePath ) ;
}
}
// Read in the text values of the previous configurations
int readExistingConfigurationHashFiles ( ) {
if ( exists ( configHashFile ) ) {
try {
previousConfigHash = readText ( configHashFile ) ;
} catch ( std . file . FileException e ) {
// Unable to access required hash file
addLogEntry ( "ERROR: Unable to access " ~ e . msg ) ;
// Use exit scopes to shutdown API
return EXIT_FAILURE ;
}
}
if ( exists ( syncListHashFile ) ) {
try {
previousSyncListHash = readText ( syncListHashFile ) ;
} catch ( std . file . FileException e ) {
// Unable to access required hash file
addLogEntry ( "ERROR: Unable to access " ~ e . msg ) ;
// Use exit scopes to shutdown API
return EXIT_FAILURE ;
}
}
2024-02-26 04:48:33 +01:00
2024-01-08 23:13:17 +01:00
return 0 ;
}
// Check for basic option conflicts - flags that should not be used together and/or flag combinations that conflict with each other
bool checkForBasicOptionConflicts ( ) {
bool operationalConflictDetected = false ;
// What are the permission that have been set for the application?
// These are relevant for:
// - The ~/OneDrive parent folder or 'sync_dir' configured item
// - Any new folder created under ~/OneDrive or 'sync_dir'
// - Any new file created under ~/OneDrive or 'sync_dir'
// valid permissions are 000 -> 777 - anything else is invalid
if ( ( getValueLong ( "sync_dir_permissions" ) < 0 ) | | ( getValueLong ( "sync_file_permissions" ) < 0 ) | | ( getValueLong ( "sync_dir_permissions" ) > 777 ) | | ( getValueLong ( "sync_file_permissions" ) > 777 ) ) {
addLogEntry ( "ERROR: Invalid 'User|Group|Other' permissions set within config file. Please check your configuration" ) ;
operationalConflictDetected = true ;
} else {
// Debug log output what permissions are being set to
addLogEntry ( "Configuring default new folder permissions as: " ~ to ! string ( getValueLong ( "sync_dir_permissions" ) ) , [ "debug" ] ) ;
configureRequiredDirectoryPermisions ( ) ;
addLogEntry ( "Configuring default new file permissions as: " ~ to ! string ( getValueLong ( "sync_file_permissions" ) ) , [ "debug" ] ) ;
2020-11-08 22:06:48 +01:00
configureRequiredFilePermisions ( ) ;
}
2024-01-08 23:13:17 +01:00
// --upload-only and --download-only cannot be used together
if ( ( getValueBool ( "upload_only" ) ) & & ( getValueBool ( "download_only" ) ) ) {
addLogEntry ( "ERROR: --upload-only and --download-only cannot be used together. Use one, not both at the same time" ) ;
operationalConflictDetected = true ;
}
// --sync and --monitor cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( getValueBool ( "monitor" ) ) ) {
addLogEntry ( "ERROR: --sync and --monitor cannot be used together. Only use one of these options, not both at the same time" ) ;
operationalConflictDetected = true ;
}
// --no-remote-delete can ONLY be enabled when --upload-only is used
if ( ( getValueBool ( "no_remote_delete" ) ) & & ( ! getValueBool ( "upload_only" ) ) ) {
addLogEntry ( "ERROR: --no-remote-delete can only be used with --upload-only" ) ;
operationalConflictDetected = true ;
}
// --remove-source-files can ONLY be enabled when --upload-only is used
if ( ( getValueBool ( "remove_source_files" ) ) & & ( ! getValueBool ( "upload_only" ) ) ) {
addLogEntry ( "ERROR: --remove-source-files can only be used with --upload-only" ) ;
operationalConflictDetected = true ;
}
// --cleanup-local-files can ONLY be enabled when --download-only is used
if ( ( getValueBool ( "cleanup_local_files" ) ) & & ( ! getValueBool ( "download_only" ) ) ) {
addLogEntry ( "ERROR: --cleanup-local-files can only be used with --download-only" ) ;
operationalConflictDetected = true ;
}
// --list-shared-folders cannot be used with --resync and/or --resync-auth
if ( ( getValueBool ( "list_business_shared_items" ) ) & & ( ( getValueBool ( "resync" ) ) | | ( getValueBool ( "resync_auth" ) ) ) ) {
2024-02-26 04:48:33 +01:00
addLogEntry ( "ERROR: --list-shared-items cannot be used with --resync or --resync-auth" ) ;
2024-01-08 23:13:17 +01:00
operationalConflictDetected = true ;
}
2024-02-26 04:48:33 +01:00
// --list-shared-folders cannot be used with --sync or --monitor
if ( ( getValueBool ( "list_business_shared_items" ) ) & & ( ( getValueBool ( "synchronize" ) ) | | ( getValueBool ( "monitor" ) ) ) ) {
addLogEntry ( "ERROR: --list-shared-items cannot be used with --sync or --monitor" ) ;
operationalConflictDetected = true ;
}
// --sync-shared-files can ONLY be used with sync_business_shared_items
if ( ( getValueBool ( "sync_business_shared_files" ) ) & & ( ! getValueBool ( "sync_business_shared_items" ) ) ) {
addLogEntry ( "ERROR: The --sync-shared-files option can only be utilised if the 'sync_business_shared_items' configuration setting is enabled." ) ;
operationalConflictDetected = true ;
}
2024-01-08 23:13:17 +01:00
// --display-sync-status cannot be used with --resync and/or --resync-auth
if ( ( getValueBool ( "display_sync_status" ) ) & & ( ( getValueBool ( "resync" ) ) | | ( getValueBool ( "resync_auth" ) ) ) ) {
addLogEntry ( "ERROR: --display-sync-status cannot be used with --resync or --resync-auth" ) ;
operationalConflictDetected = true ;
}
// --modified-by cannot be used with --resync and/or --resync-auth
if ( ( ! getValueString ( "modified_by" ) . empty ) & & ( ( getValueBool ( "resync" ) ) | | ( getValueBool ( "resync_auth" ) ) ) ) {
addLogEntry ( "ERROR: --modified-by cannot be used with --resync or --resync-auth" ) ;
operationalConflictDetected = true ;
}
// --get-file-link cannot be used with --resync and/or --resync-auth
if ( ( ! getValueString ( "get_file_link" ) . empty ) & & ( ( getValueBool ( "resync" ) ) | | ( getValueBool ( "resync_auth" ) ) ) ) {
addLogEntry ( "ERROR: --get-file-link cannot be used with --resync or --resync-auth" ) ;
operationalConflictDetected = true ;
}
// --create-share-link cannot be used with --resync and/or --resync-auth
if ( ( ! getValueString ( "create_share_link" ) . empty ) & & ( ( getValueBool ( "resync" ) ) | | ( getValueBool ( "resync_auth" ) ) ) ) {
addLogEntry ( "ERROR: --create-share-link cannot be used with --resync or --resync-auth" ) ;
operationalConflictDetected = true ;
}
// --get-sharepoint-drive-id cannot be used with --resync and/or --resync-auth
if ( ( ! getValueString ( "sharepoint_library_name" ) . empty ) & & ( ( getValueBool ( "resync" ) ) | | ( getValueBool ( "resync_auth" ) ) ) ) {
addLogEntry ( "ERROR: --get-sharepoint-drive-id cannot be used with --resync or --resync-auth" ) ;
operationalConflictDetected = true ;
}
// --monitor and --display-sync-status cannot be used together
if ( ( getValueBool ( "monitor" ) ) & & ( getValueBool ( "display_sync_status" ) ) ) {
addLogEntry ( "ERROR: --monitor and --display-sync-status cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --sync and and --display-sync-status cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( getValueBool ( "display_sync_status" ) ) ) {
addLogEntry ( "ERROR: --sync and and --display-sync-status cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --monitor and --display-quota cannot be used together
if ( ( getValueBool ( "monitor" ) ) & & ( getValueBool ( "display_quota" ) ) ) {
addLogEntry ( "ERROR: --monitor and --display-quota cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --sync and and --display-quota cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( getValueBool ( "display_quota" ) ) ) {
addLogEntry ( "ERROR: --sync and and --display-quota cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --force-sync can only be used when using --sync --single-directory
if ( getValueBool ( "force_sync" ) ) {
bool conflict = false ;
// Should not be used with --monitor
if ( getValueBool ( "monitor" ) ) conflict = true ;
// single_directory must not be empty
if ( getValueString ( "single_directory" ) . empty ) conflict = true ;
if ( conflict ) {
addLogEntry ( "ERROR: --force-sync can only be used with --sync --single-directory" ) ;
operationalConflictDetected = true ;
}
}
// When using 'azure_ad_endpoint', 'azure_tenant_id' cannot be empty
if ( ( ! getValueString ( "azure_ad_endpoint" ) . empty ) & & ( getValueString ( "azure_tenant_id" ) . empty ) ) {
addLogEntry ( "ERROR: config option 'azure_tenant_id' cannot be empty when 'azure_ad_endpoint' is configured" ) ;
operationalConflictDetected = true ;
}
// When using --enable-logging the 'log_dir' cannot be empty
if ( ( getValueBool ( "enable_logging" ) ) & & ( getValueString ( "log_dir" ) . empty ) ) {
addLogEntry ( "ERROR: config option 'log_dir' cannot be empty when 'enable_logging' is configured" ) ;
operationalConflictDetected = true ;
}
// When using --syncdir, the value cannot be empty.
if ( strip ( getValueString ( "sync_dir" ) ) . empty ) {
addLogEntry ( "ERROR: --syncdir value cannot be empty" ) ;
operationalConflictDetected = true ;
}
// --monitor and --create-directory cannot be used together
if ( ( getValueBool ( "monitor" ) ) & & ( ! getValueString ( "create_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --monitor and --create-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --sync and --create-directory cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( ! getValueString ( "create_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --sync and --create-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --monitor and --remove-directory cannot be used together
if ( ( getValueBool ( "monitor" ) ) & & ( ! getValueString ( "remove_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --monitor and --remove-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --sync and --remove-directory cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( ! getValueString ( "remove_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --sync and --remove-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --monitor and --source-directory cannot be used together
if ( ( getValueBool ( "monitor" ) ) & & ( ! getValueString ( "source_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --monitor and --source-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --sync and --source-directory cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( ! getValueString ( "source_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --sync and --source-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --monitor and --destination-directory cannot be used together
if ( ( getValueBool ( "monitor" ) ) & & ( ! getValueString ( "destination_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --monitor and --destination-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --sync and --destination-directory cannot be used together
if ( ( getValueBool ( "synchronize" ) ) & & ( ! getValueString ( "destination_directory" ) . empty ) ) {
addLogEntry ( "ERROR: --sync and --destination-directory cannot be used together" ) ;
operationalConflictDetected = true ;
}
// --download-only and --local-first cannot be used together
if ( ( getValueBool ( "download_only" ) ) & & ( getValueBool ( "local_first" ) ) ) {
addLogEntry ( "ERROR: --download-only cannot be used with --local-first" ) ;
operationalConflictDetected = true ;
}
// Return bool value indicating if we have an operational conflict
return operationalConflictDetected ;
2020-10-29 22:00:26 +01:00
}
2022-05-16 02:16:50 +02:00
2024-01-08 23:13:17 +01:00
// Reset skip_file and skip_dir to application defaults when --force-sync is used
2022-05-16 02:16:50 +02:00
void resetSkipToDefaults ( ) {
// skip_file
2024-01-08 23:13:17 +01:00
addLogEntry ( "original skip_file: " ~ getValueString ( "skip_file" ) , [ "debug" ] ) ;
addLogEntry ( "resetting skip_file to application defaults" , [ "debug" ] ) ;
2022-05-16 02:16:50 +02:00
setValueString ( "skip_file" , defaultSkipFile ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "reset skip_file: " ~ getValueString ( "skip_file" ) , [ "debug" ] ) ;
2022-05-16 02:16:50 +02:00
// skip_dir
2024-01-08 23:13:17 +01:00
addLogEntry ( "original skip_dir: " ~ getValueString ( "skip_dir" ) , [ "debug" ] ) ;
addLogEntry ( "resetting skip_dir to application defaults" , [ "debug" ] ) ;
2022-05-16 02:16:50 +02:00
setValueString ( "skip_dir" , defaultSkipDir ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "reset skip_dir: " ~ getValueString ( "skip_dir" ) , [ "debug" ] ) ;
}
// Initialise the correct 'sync_dir' expanding any '~' if present
string initialiseRuntimeSyncDirectory ( ) {
string runtimeSyncDirectory ;
addLogEntry ( "sync_dir: Setting runtimeSyncDirectory from config value 'sync_dir'" , [ "debug" ] ) ;
if ( ! shellEnvironmentSet ) {
addLogEntry ( "sync_dir: No SHELL or USER environment variable configuration detected" , [ "debug" ] ) ;
// 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 ( getValueString ( "sync_dir" ) , "~" ) ) {
// A ~ was found in sync_dir
addLogEntry ( "sync_dir: A '~' was found in 'sync_dir', using the calculated 'homePath' to replace '~' as no SHELL or USER environment variable set" , [ "debug" ] ) ;
runtimeSyncDirectory = buildNormalizedPath ( buildPath ( defaultHomePath , strip ( getValueString ( "sync_dir" ) , "~" ) ) ) ;
} else {
// No ~ found in sync_dir, use as is
addLogEntry ( "sync_dir: Using configured 'sync_dir' path as-is as no SHELL or USER environment variable configuration detected" , [ "debug" ] ) ;
runtimeSyncDirectory = getValueString ( "sync_dir" ) ;
}
} else {
// A shell and user environment variable is set, expand any ~ as this will be expanded correctly if present
if ( canFind ( getValueString ( "sync_dir" ) , "~" ) ) {
addLogEntry ( "sync_dir: A '~' was found in the configured 'sync_dir', automatically expanding as SHELL and USER environment variable is set" , [ "debug" ] ) ;
runtimeSyncDirectory = expandTilde ( getValueString ( "sync_dir" ) ) ;
} else {
// No ~ found in sync_dir, does the path begin with a '/' ?
addLogEntry ( "sync_dir: Using configured 'sync_dir' path as-is as however SHELL or USER environment variable configuration detected - should be placed in USER home directory" , [ "debug" ] ) ;
if ( ! startsWith ( getValueString ( "sync_dir" ) , "/" ) ) {
addLogEntry ( "Configured 'sync_dir' does not start with a '/' or '~/' - adjusting configured 'sync_dir' to use User Home Directory as base for 'sync_dir' path" , [ "debug" ] ) ;
string updatedPathWithHome = "~/" ~ getValueString ( "sync_dir" ) ;
runtimeSyncDirectory = expandTilde ( updatedPathWithHome ) ;
} else {
addLogEntry ( "use 'sync_dir' as is - no touch" , [ "debug" ] ) ;
runtimeSyncDirectory = getValueString ( "sync_dir" ) ;
}
}
}
// What will runtimeSyncDirectory be actually set to?
addLogEntry ( "sync_dir: runtimeSyncDirectory set to: " ~ runtimeSyncDirectory , [ "debug" ] ) ;
2024-02-26 04:48:33 +01:00
// Configure configuredBusinessSharedFilesDirectoryName
configuredBusinessSharedFilesDirectoryName = buildNormalizedPath ( buildPath ( runtimeSyncDirectory , defaultBusinessSharedFilesDirectoryName ) ) ;
2024-01-08 23:13:17 +01:00
return runtimeSyncDirectory ;
}
// Initialise the correct 'log_dir' when application logging to a separate file is enabled with 'enable_logging' and expanding any '~' if present
string calculateLogDirectory ( ) {
string configuredLogDirPath ;
addLogEntry ( "log_dir: Setting runtime application log from config value 'log_dir'" , [ "debug" ] ) ;
if ( getValueString ( "log_dir" ) ! = defaultLogFileDir ) {
// User modified 'log_dir' to be used with 'enable_logging'
// if 'log_dir' contains a '~' this needs to be expanded correctly
if ( canFind ( getValueString ( "log_dir" ) , "~" ) ) {
// ~ needs to be expanded correctly
if ( ! shellEnvironmentSet ) {
// No shell or user environment variable set, so expandTilde() will fail - usually headless system running under init.d / systemd or potentially Docker
addLogEntry ( "log_dir: A '~' was found in log_dir, using the calculated 'homePath' to replace '~' as no SHELL or USER environment variable set" , [ "debug" ] ) ;
configuredLogDirPath = buildNormalizedPath ( buildPath ( defaultHomePath , strip ( getValueString ( "log_dir" ) , "~" ) ) ) ;
} else {
// A shell and user environment variable is set, expand any ~ as this will be expanded correctly if present
addLogEntry ( "log_dir: A '~' was found in the configured 'log_dir', automatically expanding as SHELL and USER environment variable is set" , [ "debug" ] ) ;
configuredLogDirPath = expandTilde ( getValueString ( "log_dir" ) ) ;
}
} else {
// '~' not found in log_dir entry, use as is
configuredLogDirPath = getValueString ( "log_dir" ) ;
}
} else {
// Default 'log_dir' to be used with 'enable_logging'
configuredLogDirPath = defaultLogFileDir ;
}
// Attempt to create 'configuredLogDirPath' otherwise we need to fall back to the users home directory
if ( ! exists ( configuredLogDirPath ) ) {
// 'configuredLogDirPath' path does not exist - try and create it
try {
mkdirRecurse ( configuredLogDirPath ) ;
} catch ( std . file . FileException e ) {
// We got an error when attempting to create the directory ..
addLogEntry ( ) ;
addLogEntry ( "ERROR: Unable to create " ~ configuredLogDirPath ) ;
addLogEntry ( "ERROR: Please manually create '" ~ configuredLogDirPath ~ "' and set appropriate permissions to allow write access for your user to this location." ) ;
addLogEntry ( "ERROR: The requested client activity log will instead be located in your users home directory" ) ;
addLogEntry ( ) ;
// Reconfigure 'configuredLogDirPath' to use environment.get("HOME") value, which we have already calculated
configuredLogDirPath = defaultHomePath ;
}
}
// Return the initialised application log path
return configuredLogDirPath ;
}
void setConfigLoggingLevels ( bool verboseLoggingInput , bool debugLoggingInput , long verbosityCountInput ) {
// set the appConfig logging values
verboseLogging = verboseLoggingInput ;
debugLogging = debugLoggingInput ;
verbosityCount = verbosityCountInput ;
2022-05-16 02:16:50 +02:00
}
2024-01-12 21:46:47 +01:00
// What IP protocol is going to be used to access Microsoft OneDrive
void displayIPProtocol ( ) {
if ( getValueLong ( "ip_protocol_version" ) = = 0 ) addLogEntry ( "Using IPv4 and IPv6 (if configured) for all network operations" ) ;
if ( getValueLong ( "ip_protocol_version" ) = = 1 ) addLogEntry ( "Forcing client to use IPv4 connections only" ) ;
if ( getValueLong ( "ip_protocol_version" ) = = 2 ) addLogEntry ( "Forcing client to use IPv6 connections only" ) ;
}
2015-09-01 20:45:34 +02:00
}
2024-01-08 23:13:17 +01:00
// Output the full application help when --help is passed in
void outputLongHelp ( Option [ ] opt ) {
auto argsNeedingOptions = [
"--auth-files" ,
"--auth-response" ,
"--confdir" ,
"--create-directory" ,
"--classify-as-big-delete" ,
"--create-share-link" ,
"--destination-directory" ,
"--get-file-link" ,
"--get-O365-drive-id" ,
"--log-dir" ,
"--min-notify-changes" ,
"--modified-by" ,
"--monitor-interval" ,
"--monitor-log-frequency" ,
"--monitor-fullscan-frequency" ,
"--remove-directory" ,
"--single-directory" ,
"--skip-dir" ,
"--skip-file" ,
"--skip-size" ,
"--source-directory" ,
"--space-reservation" ,
"--syncdir" ,
"--user-agent" ] ;
writeln ( ` onedrive - A client for the Microsoft OneDrive Cloud Service
2019-04-11 04:26:20 +02:00
2024-01-08 23:13:17 +01:00
Usage :
onedrive [ options ] - - sync
2019-04-11 04:26:20 +02:00
Do a one time synchronization
2024-01-08 23:13:17 +01:00
onedrive [ options ] - - monitor
2019-04-11 04:26:20 +02:00
Monitor filesystem and sync regularly
2024-01-08 23:13:17 +01:00
onedrive [ options ] - - display - config
2019-04-11 04:26:20 +02:00
Display the currently used configuration
2024-01-08 23:13:17 +01:00
onedrive [ options ] - - display - sync - status
2019-04-11 04:26:20 +02:00
Query OneDrive service and report on pending changes
2024-01-08 23:13:17 +01:00
onedrive - h | - - help
2019-04-11 04:26:20 +02:00
Show this help screen
2024-01-08 23:13:17 +01:00
onedrive - - version
2019-04-11 04:26:20 +02:00
Show version
2024-01-08 23:13:17 +01:00
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 ) ;
}
}