abraunegg-onedrive/src/config.d

902 lines
35 KiB
D

import core.stdc.stdlib: EXIT_SUCCESS, EXIT_FAILURE, exit;
import std.file, std.string, std.regex, std.stdio, std.process, std.algorithm.searching, std.getopt, std.conv, std.path;
import std.algorithm.sorting: sort;
import selective;
static import log;
final class Config
{
// application defaults
public string defaultSyncDir = "~/OneDrive";
public string defaultSkipFile = "~*|.~*|*.tmp";
public string defaultSkipDir = "";
public string defaultLogFileDir = "/var/log/onedrive/";
// application set items
public string refreshTokenFilePath = "";
public string deltaLinkFilePath = "";
public string databaseFilePath = "";
public string databaseFilePathDryRun = "";
public string uploadStateFilePath = "";
public string syncListFilePath = "";
public string homePath = "";
public string configDirName = "";
public string systemConfigDirName = "";
public string configFileSyncDir = "";
public string configFileSkipFile = "";
public string configFileSkipDir = "";
public string businessSharedFolderFilePath = "";
private string userConfigFilePath = "";
private string systemConfigFilePath = "";
// was the application just authorised - paste of response uri
public bool applicationAuthorizeResponseUri = false;
// hashmap for the values found in the user config file
// ARGGGG D is stupid and cannot make hashmap initializations!!!
// private string[string] foobar = [ "aa": "bb" ] does NOT work!!!
private string[string] stringValues;
private bool[string] boolValues;
private long[string] longValues;
// Compile time regex - this does not change
public auto configRegex = ctRegex!(`^(\w+)\s*=\s*"(.*)"\s*$`);
// Default directory permission mode
public long defaultDirectoryPermissionMode = 700;
public int configuredDirectoryPermissionMode;
// Default file permission mode
public long defaultFilePermissionMode = 600;
public int configuredFilePermissionMode;
// Bring in v2.5.0 config items
// 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
immutable int defaultConnectTimeout = 10;
// With the following settings we force
// - if there is no data flow for 10min, abort
// - if the download time for one item exceeds 1h, abort
//
// Timeout for activity on connection
// this translates into Curl's CURLOPT_LOW_SPEED_TIME
// which says:
// It contains the time in number seconds that the
// transfer speed should be below the CURLOPT_LOW_SPEED_LIMIT
// for the library to consider it too slow and abort.
immutable int defaultDataTimeout = 600;
// Maximum time any operation is allowed to take
// This includes dns resolution, connecting, data transfer, etc.
immutable int defaultOperationTimeout = 3600;
// Specify how many redirects should be allowed
immutable int defaultMaxRedirects = 5;
// 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
this(string confdirOption)
{
// default configuration - entries in config file ~/.config/onedrive/config
// 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()
stringValues["sync_dir"] = defaultSyncDir;
stringValues["skip_file"] = defaultSkipFile;
stringValues["skip_dir"] = defaultSkipDir;
stringValues["log_dir"] = defaultLogFileDir;
stringValues["drive_id"] = "";
stringValues["user_agent"] = "";
boolValues["upload_only"] = false;
boolValues["check_nomount"] = false;
boolValues["check_nosync"] = false;
boolValues["download_only"] = false;
boolValues["disable_notifications"] = false;
boolValues["disable_download_validation"] = false;
boolValues["disable_upload_validation"] = false;
boolValues["enable_logging"] = false;
boolValues["force_http_11"] = false;
boolValues["local_first"] = false;
boolValues["no_remote_delete"] = false;
boolValues["skip_symlinks"] = false;
boolValues["debug_https"] = false;
boolValues["skip_dotfiles"] = false;
boolValues["dry_run"] = false;
boolValues["sync_root_files"] = false;
longValues["verbose"] = log.verbose; // might be initialized by the first getopt call!
// The amount of time (seconds) between monitor sync loops
longValues["monitor_interval"] = 300;
longValues["skip_size"] = 0;
longValues["min_notify_changes"] = 5;
longValues["monitor_log_frequency"] = 6;
// 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;
// Delete source after successful transfer
boolValues["remove_source_files"] = false;
// Strict matching for skip_dir
boolValues["skip_dir_strict_match"] = false;
// Allow for a custom Client ID / Application ID to be used to replace the inbuilt default
// This is a config file option ONLY
stringValues["application_id"] = "";
// allow for resync to be set via config file
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;
// 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"] = "";
// Support single-tenant applications that are not able to use the "common" multiplexer
stringValues["azure_tenant_id"] = "common";
// Allow enable / disable of the syncing of OneDrive Business Shared Folders via configuration file
boolValues["sync_business_shared_folders"] = false;
// Configure the default folder permission attributes for newly created folders
longValues["sync_dir_permissions"] = defaultDirectoryPermissionMode;
// Configure the default file permission attributes for newly created file
longValues["sync_file_permissions"] = defaultFilePermissionMode;
// Configure download / upload rate limits
longValues["rate_limit"] = 0;
// To ensure we do not fill up the load disk, how much disk space should be reserved by default
longValues["space_reservation"] = 50 * 2^^20; // 50 MB as Bytes
// Webhook options
boolValues["webhook_enabled"] = false;
stringValues["webhook_public_url"] = "";
stringValues["webhook_listening_host"] = "";
longValues["webhook_listening_port"] = 8888;
longValues["webhook_expiration_interval"] = 3600 * 24;
longValues["webhook_renewal_interval"] = 3600 * 12;
// 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;
// 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
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;
// HTTPS & CURL Operation Settings
// - Maximum time an operation is allowed to take
// This includes dns resolution, connecting, data transfer, etc.
longValues["operation_timeout"] = defaultOperationTimeout;
// libcurl dns_cache_timeout timeout
longValues["dns_timeout"] = defaultDnsTimeout;
// Timeout for HTTPS connections
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
// EXPAND USERS HOME DIRECTORY
// Determine the users home directory.
// Need to avoid using ~ here as expandTilde() below does not interpret correctly when running under init.d or systemd scripts
// Check for HOME environment variable
if (environment.get("HOME") != ""){
// Use HOME environment variable
log.vdebug("homePath: HOME environment variable set");
homePath = environment.get("HOME");
} else {
if ((environment.get("SHELL") == "") && (environment.get("USER") == "")){
// No shell is set or username - observed case when running as systemd service under CentOS 7.x
log.vdebug("homePath: WARNING - no HOME environment variable set");
log.vdebug("homePath: WARNING - no SHELL environment variable set");
log.vdebug("homePath: WARNING - no USER environment variable set");
homePath = "/root";
} else {
// A shell & valid user is set, but no HOME is set, use ~ which can be expanded
log.vdebug("homePath: WARNING - no HOME environment variable set");
homePath = "~";
}
}
// Output homePath calculation
log.vdebug("homePath: ", homePath);
// Determine the correct configuration directory to use
string configDirBase;
string systemConfigDirBase;
if (confdirOption != "") {
// A CLI 'confdir' was passed in
// Clean up any stray " .. these should not be there ...
confdirOption = strip(confdirOption,"\"");
log.vdebug("configDirName: CLI override to set configDirName to: ", confdirOption);
if (canFind(confdirOption,"~")) {
// A ~ was found
log.vdebug("configDirName: A '~' was found in configDirName, using the calculated 'homePath' to replace '~'");
configDirName = homePath ~ strip(confdirOption,"~","~");
} else {
configDirName = confdirOption;
}
} else {
// Determine the base directory relative to which user specific configuration files should be stored.
if (environment.get("XDG_CONFIG_HOME") != ""){
log.vdebug("configDirBase: XDG_CONFIG_HOME environment variable set");
configDirBase = environment.get("XDG_CONFIG_HOME");
} else {
// XDG_CONFIG_HOME does not exist on systems where X11 is not present - ie - headless systems / servers
log.vdebug("configDirBase: WARNING - no XDG_CONFIG_HOME environment variable set");
configDirBase = homePath ~ "/.config";
// Also set up a path to pre-shipped shared configs (which can be overridden by supplying a config file in userspace)
systemConfigDirBase = "/etc";
}
// Output configDirBase calculation
log.vdebug("configDirBase: ", configDirBase);
// Set the default application configuration directory
log.vdebug("configDirName: Configuring application to use default config path");
// configDirBase contains the correct path so we do not need to check for presence of '~'
configDirName = configDirBase ~ "/onedrive";
// systemConfigDirBase contains the correct path so we do not need to check for presence of '~'
systemConfigDirName = systemConfigDirBase ~ "/onedrive";
}
// Config directory options all determined
if (!exists(configDirName)) {
// create the directory
mkdirRecurse(configDirName);
// Configure the applicable permissions for the folder
configDirName.setAttributes(returnRequiredDirectoryPermisions());
} else {
// The config path exists
// The path that exists must be a directory, not a file
if (!isDir(configDirName)) {
if (!confdirOption.empty) {
// the configuration path was passed in by the user .. user error
writeln("ERROR: --confdir entered value is an existing file instead of an existing directory");
} else {
// other error
writeln("ERROR: ~/.config/onedrive is a file rather than a directory");
}
// Must exit
exit(EXIT_FAILURE);
}
}
// configDirName has a trailing /
if (!configDirName.empty) log.vlog("Using 'user' Config Dir: ", configDirName);
if (!systemConfigDirName.empty) log.vlog("Using 'system' Config Dir: ", systemConfigDirName);
// Update application set variables based on configDirName
refreshTokenFilePath = buildNormalizedPath(configDirName ~ "/refresh_token");
deltaLinkFilePath = buildNormalizedPath(configDirName ~ "/delta_link");
databaseFilePath = buildNormalizedPath(configDirName ~ "/items.sqlite3");
databaseFilePathDryRun = buildNormalizedPath(configDirName ~ "/items-dryrun.sqlite3");
uploadStateFilePath = buildNormalizedPath(configDirName ~ "/resume_upload");
userConfigFilePath = buildNormalizedPath(configDirName ~ "/config");
syncListFilePath = buildNormalizedPath(configDirName ~ "/sync_list");
systemConfigFilePath = buildNormalizedPath(systemConfigDirName ~ "/config");
businessSharedFolderFilePath = buildNormalizedPath(configDirName ~ "/business_shared_folders");
// Debug Output for application set variables based on configDirName
log.vdebug("refreshTokenFilePath = ", refreshTokenFilePath);
log.vdebug("deltaLinkFilePath = ", deltaLinkFilePath);
log.vdebug("databaseFilePath = ", databaseFilePath);
log.vdebug("databaseFilePathDryRun = ", databaseFilePathDryRun);
log.vdebug("uploadStateFilePath = ", uploadStateFilePath);
log.vdebug("userConfigFilePath = ", userConfigFilePath);
log.vdebug("syncListFilePath = ", syncListFilePath);
log.vdebug("systemConfigFilePath = ", systemConfigFilePath);
log.vdebug("businessSharedFolderFilePath = ", businessSharedFolderFilePath);
}
bool initialize()
{
// Initialise the application
if (!exists(userConfigFilePath)) {
// 'user' configuration file does not exist
// Is there a system configuration file?
if (!exists(systemConfigFilePath)) {
// 'system' configuration file does not exist
log.vlog("No user or system config file found, using application defaults");
return true;
} else {
// 'system' configuration file exists
// can we load the configuration file without error?
if (load(systemConfigFilePath)) {
// configuration file loaded without error
log.log("System configuration file successfully loaded");
return true;
} else {
// there was a problem loading the configuration file
log.log("System configuration file has errors - please check your configuration");
return false;
}
}
} else {
// 'user' configuration file exists
// can we load the configuration file without error?
if (load(userConfigFilePath)) {
// configuration file loaded without error
log.log("Configuration file successfully loaded");
return true;
} else {
// there was a problem loading the configuration file
log.log("Configuration file has errors - please check your configuration");
return false;
}
}
}
void update_from_args(string[] args)
{
// Add additional options that are NOT configurable via config file
stringValues["create_directory"] = "";
stringValues["create_share_link"] = "";
stringValues["destination_directory"] = "";
stringValues["get_file_link"] = "";
stringValues["modified_by"] = "";
stringValues["get_o365_drive_id"] = "";
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["print_token"] = false;
boolValues["logout"] = false;
boolValues["reauth"] = false;
boolValues["monitor"] = false;
boolValues["synchronize"] = false;
boolValues["force"] = false;
boolValues["list_business_shared_folders"] = false;
boolValues["force_sync"] = false;
boolValues["with_editing_perms"] = false;
// Application Startup option validation
try {
string tmpStr;
bool tmpBol;
long tmpVerb;
// duplicated from main.d to get full help output!
auto opt = getopt(
args,
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"],
"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 --synchronize --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-O365-drive-id",
"Query and return the Office 365 Drive ID for a given Office 365 SharePoint Shared Library",
&stringValues["get_o365_drive_id"],
"local-first",
"Synchronize from the local directory source first, before downloading changes from OneDrive.",
&boolValues["local_first"],
"log-dir",
"Directory where logging output is saved to, needs to end with a slash.",
&stringValues["log_dir"],
"logout",
"Logout the current user",
&boolValues["logout"],
"min-notify-changes",
"Minimum number of pending incoming changes necessary to trigger a desktop notification",
&longValues["min_notify_changes"],
"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-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 synchronization to OneDrive",
&stringValues["sync_dir"],
"synchronize",
"Perform a synchronization",
&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"],
"user-agent",
"Specify a User Agent string to the http client",
&stringValues["user_agent"],
"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,
"list-shared-folders",
"List OneDrive Business Shared Folders",
&boolValues["list_business_shared_folders"],
"sync-shared-folders",
"Sync OneDrive Business Shared Folders",
&boolValues["sync_business_shared_folders"],
"with-editing-perms",
"Create a read-write shareable link for an existing file on OneDrive when used with --create-share-link <file>",
&boolValues["with_editing_perms"]
);
if (opt.helpWanted) {
outputLongHelp(opt.options);
exit(EXIT_SUCCESS);
}
} catch (GetOptException e) {
log.error(e.msg);
log.error("Try 'onedrive -h' for more information");
exit(EXIT_FAILURE);
} catch (Exception e) {
// error
log.error(e.msg);
log.error("Try 'onedrive -h' for more information");
exit(EXIT_FAILURE);
}
}
string getValueString(string key)
{
auto p = key in stringValues;
if (p) {
return *p;
} else {
throw new Exception("Missing config value: " ~ key);
}
}
long getValueLong(string key)
{
auto p = key in longValues;
if (p) {
return *p;
} else {
throw new Exception("Missing config value: " ~ key);
}
}
bool getValueBool(string key)
{
auto p = key in boolValues;
if (p) {
return *p;
} else {
throw new Exception("Missing config value: " ~ key);
}
}
void setValueBool(string key, bool value)
{
boolValues[key] = value;
}
void setValueString(string key, string value)
{
stringValues[key] = value;
}
void setValueLong(string key, long value)
{
longValues[key] = value;
}
// load a configuration file
private bool load(string filename)
{
// configure function variables
try {
readText(filename);
} catch (std.file.FileException e) {
// Unable to access required file
log.error("ERROR: Unable to access ", e.msg);
// Use exit scopes to shutdown API
return false;
}
// We were able to readText the config file - so, we should be able to open and read it
auto file = File(filename, "r");
string lineBuffer;
// configure scopes
// - failure
scope(failure) {
// close file if open
if (file.isOpen()){
// close open file
file.close();
}
}
// - exit
scope(exit) {
// close file if open
if (file.isOpen()){
// close open file
file.close();
}
}
// read file line by line
auto range = file.byLine();
foreach (line; range) {
lineBuffer = stripLeft(line).to!string;
if (lineBuffer.length == 0 || lineBuffer[0] == ';' || lineBuffer[0] == '#') continue;
auto c = lineBuffer.matchFirst(configRegex);
if (!c.empty) {
c.popFront(); // skip the whole match
string key = c.front.dup;
auto p = key in boolValues;
if (p) {
c.popFront();
// only accept "true" as true value. TODO Should we support other formats?
setValueBool(key, c.front.dup == "true" ? true : false);
} else {
auto pp = key in stringValues;
if (pp) {
c.popFront();
setValueString(key, c.front.dup);
// detect need for --resync for these:
// --syncdir ARG
// --skip-file ARG
// --skip-dir ARG
if (key == "sync_dir") configFileSyncDir = c.front.dup;
if (key == "skip_file") {
// Handle multiple entries of skip_file
if (configFileSkipFile.empty) {
// currently no entry exists
configFileSkipFile = c.front.dup;
} else {
// add to existing entry
configFileSkipFile = configFileSkipFile ~ "|" ~ to!string(c.front.dup);
setValueString("skip_file", configFileSkipFile);
}
}
if (key == "skip_dir") {
// Handle multiple entries of skip_dir
if (configFileSkipDir.empty) {
// currently no entry exists
configFileSkipDir = c.front.dup;
} else {
// add to existing entry
configFileSkipDir = configFileSkipDir ~ "|" ~ to!string(c.front.dup);
setValueString("skip_dir", configFileSkipDir);
}
}
// --single-directory Strip quotation marks from path
// This is an issue when using ONEDRIVE_SINGLE_DIRECTORY with Docker
if (key == "single_directory") {
// Strip quotation marks from provided path
string configSingleDirectory = strip(to!string(c.front.dup), "\"");
setValueString("single_directory", configSingleDirectory);
}
// Azure AD Configuration
if (key == "azure_ad_endpoint") {
string azureConfigValue = c.front.dup;
switch(azureConfigValue) {
case "":
log.log("Using config option for Global Azure AD Endpoints");
break;
case "USL4":
log.log("Using config option for Azure AD for US Government Endpoints");
break;
case "USL5":
log.log("Using config option for Azure AD for US Government Endpoints (DOD)");
break;
case "DE":
log.log("Using config option for Azure AD Germany");
break;
case "CN":
log.log("Using config option for Azure AD China operated by 21Vianet");
break;
// Default - all other entries
default:
log.log("Unknown Azure AD Endpoint - using Global Azure AD Endpoints");
}
}
} else {
auto ppp = key in longValues;
if (ppp) {
c.popFront();
setValueLong(key, to!long(c.front.dup));
// if key is space_reservation we have to calculate MB -> bytes
if (key == "space_reservation") {
// temp value
ulong tempValue = to!long(c.front.dup);
// a value of 0 needs to be made at least 1MB ..
if (tempValue == 0) {
tempValue = 1;
}
setValueLong("space_reservation", to!long(tempValue * 2^^20));
}
} else {
log.log("Unknown key in config file: ", key);
return false;
}
}
}
} else {
log.log("Malformed config line: ", lineBuffer);
return false;
}
}
return true;
}
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);
} else {
// return default as octal integer
string valueToConvert = to!string(defaultDirectoryPermissionMode);
auto convertedValue = parse!long(valueToConvert, 8);
configuredDirectoryPermissionMode = to!int(convertedValue);
}
}
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);
} else {
// return default as octal integer
string valueToConvert = to!string(defaultFilePermissionMode);
auto convertedValue = parse!long(valueToConvert, 8);
configuredFilePermissionMode = to!int(convertedValue);
}
}
int returnRequiredDirectoryPermisions() {
// read the configuredDirectoryPermissionMode and return
if (configuredDirectoryPermissionMode == 0) {
// the configured value is zero, this means that directories would get
// values of d---------
configureRequiredDirectoryPermisions();
}
return configuredDirectoryPermissionMode;
}
int returnRequiredFilePermisions() {
// read the configuredFilePermissionMode and return
if (configuredFilePermissionMode == 0) {
// the configured value is zero
configureRequiredFilePermisions();
}
return configuredFilePermissionMode;
}
void resetSkipToDefaults() {
// reset skip_file and skip_dir to application defaults
// skip_file
log.vdebug("original skip_file: ", getValueString("skip_file"));
log.vdebug("resetting skip_file");
setValueString("skip_file", defaultSkipFile);
log.vdebug("reset skip_file: ", getValueString("skip_file"));
// skip_dir
log.vdebug("original skip_dir: ", getValueString("skip_dir"));
log.vdebug("resetting skip_dir");
setValueString("skip_dir", defaultSkipDir);
log.vdebug("reset skip_dir: ", getValueString("skip_dir"));
}
}
void outputLongHelp(Option[] opt)
{
auto argsNeedingOptions = [
"--auth-files",
"--auth-response",
"--confdir",
"--create-directory",
"--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",
"--operation-timeout",
"--remove-directory",
"--single-directory",
"--skip-dir",
"--skip-file",
"--skip-size",
"--source-directory",
"--space-reservation",
"--syncdir",
"--user-agent" ];
writeln(`OneDrive - a client for OneDrive Cloud Services
Usage:
onedrive [options] --synchronize
Do a one time synchronization
onedrive [options] --monitor
Monitor filesystem and sync regularly
onedrive [options] --display-config
Display the currently used configuration
onedrive [options] --display-sync-status
Query OneDrive service and report on pending changes
onedrive -h | --help
Show this help screen
onedrive --version
Show version
Options:
`);
foreach (it; opt.sort!("a.optLong < b.optLong")) {
writefln(" %s%s%s%s\n %s",
it.optLong,
it.optShort == "" ? "" : " " ~ it.optShort,
argsNeedingOptions.canFind(it.optLong) ? " ARG" : "",
it.required ? " (required)" : "", it.help);
}
}
unittest
{
auto cfg = new Config("");
cfg.load("config");
assert(cfg.getValueString("sync_dir") == "~/OneDrive");
}