2024-01-08 23:13:17 +01:00
// What is this module called?
module main ;
// What does this module require to function?
2018-12-28 03:19:20 +01:00
import core.stdc.stdlib : EXIT_SUCCESS , EXIT_FAILURE , exit ;
import core.stdc.signal ;
2024-01-08 23:13:17 +01:00
import core.memory ;
import core.time ;
import core.thread ;
import std.stdio ;
import std.getopt ;
import std.string ;
import std.file ;
import std.process ;
import std.algorithm ;
import std.path ;
import std.concurrency ;
import std.parallelism ;
import std.conv ;
import std.traits ;
import std.net.curl : CurlException ;
2022-10-12 04:34:42 +02:00
import std.datetime ;
2018-12-28 03:19:20 +01:00
2024-01-08 23:13:17 +01:00
// What other modules that we have created do we need to import?
import config ;
import log ;
import curlEngine ;
import util ;
import onedrive ;
2024-02-15 19:10:29 +01:00
import progress ;
2024-01-08 23:13:17 +01:00
import syncEngine ;
import itemdb ;
import clientSideFiltering ;
import monitor ;
2024-02-20 19:13:26 +01:00
import webhook ;
2024-01-08 23:13:17 +01:00
2024-02-15 19:10:29 +01:00
2024-01-08 23:13:17 +01:00
// What other constant variables do we require?
2022-08-17 05:19:51 +02:00
const int EXIT_RESYNC_REQUIRED = 126 ;
2019-07-06 00:01:04 +02:00
2024-01-08 23:13:17 +01:00
// Class objects
ApplicationConfig appConfig ;
2024-02-20 19:13:26 +01:00
OneDriveWebhook oneDriveWebhook ;
2024-01-08 23:13:17 +01:00
SyncEngine syncEngineInstance ;
ItemDatabase itemDB ;
ClientSideFiltering selectiveSync ;
Monitor filesystemMonitor ;
int main ( string [ ] cliArgs ) {
// Application Start Time - used during monitor loop to detail how long it has been running for
auto applicationStartTime = Clock . currTime ( ) ;
// Disable buffering on stdout - this is needed so that when we are using plain write() it will go to the terminal without flushing
2018-12-23 01:15:10 +01:00
stdout . setvbuf ( 0 , _IONBF ) ;
2024-01-08 23:13:17 +01:00
// Required main function variables
string genericHelpMessage = "Please use 'onedrive --help' for further assistance in regards to running this application." ;
// If the user passes in --confdir we need to store this as a variable
string confdirOption = "" ;
// running as what user?
string runtimeUserName = "" ;
// Are we online?
2020-05-20 03:37:11 +02:00
bool online = false ;
2024-01-08 23:13:17 +01:00
// Does the operating environment have shell environment variables set
bool shellEnvSet = false ;
// What is the runtime syncronisation directory that will be used
// Typically this will be '~/OneDrive' .. however tilde expansion is unreliable
string runtimeSyncDirectory = "" ;
// Configure the runtime database file path. Typically this will be the default, but in a --dry-run scenario, we use a separate database file
string runtimeDatabaseFile = "" ;
// Verbosity Logging Count - this defines if verbose or debug logging is being used
long verbosityCount = 0 ;
// Application Logging Level
bool verboseLogging = false ;
bool debugLogging = false ;
// Monitor loop failures
bool monitorFailures = false ;
2024-01-13 20:54:15 +01:00
// Help requested
bool helpRequested = false ;
2024-01-08 23:13:17 +01:00
// DEVELOPER OPTIONS OUTPUT VARIABLES
2020-05-20 03:37:11 +02:00
bool displayMemoryUsage = false ;
2020-05-25 03:30:04 +02:00
bool displaySyncOptions = false ;
2022-11-19 01:06:00 +01: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" ) ) ;
2022-05-19 08:12:53 +02:00
2024-01-08 23:13:17 +01:00
// Define 'exit' and 'failure' scopes
2020-05-20 03:37:11 +02:00
scope ( exit ) {
2024-01-08 23:13:17 +01:00
// Detail what scope was called
addLogEntry ( "Exit scope was called" , [ "debug" ] ) ;
// Perform exit tasks
performStandardExitProcess ( "exitScope" ) ;
2020-05-20 03:37:11 +02:00
}
2024-01-08 23:13:17 +01:00
2020-05-20 03:37:11 +02:00
scope ( failure ) {
2024-01-08 23:13:17 +01:00
// Detail what scope was called
addLogEntry ( "Failure scope was called" , [ "debug" ] ) ;
// Perform exit tasks
performStandardExitProcess ( "failureScope" ) ;
2020-05-20 03:37:11 +02:00
}
2024-01-08 23:13:17 +01:00
// Read in application options as passed in
2019-01-26 01:03:00 +01:00
try {
2019-04-11 04:26:20 +02:00
bool printVersion = false ;
2024-01-08 23:13:17 +01:00
auto cliOptions = getopt (
cliArgs ,
2019-04-11 04:26:20 +02:00
std . getopt . config . passThrough ,
2019-01-26 01:03:00 +01:00
std . getopt . config . bundling ,
std . getopt . config . caseSensitive ,
2019-04-11 04:26:20 +02:00
"confdir" , "Set the directory used to store the configuration files" , & confdirOption ,
2024-01-08 23:13:17 +01:00
"verbose|v+" , "Print more details, useful for debugging (repeat for extra debugging)" , & verbosityCount ,
2019-01-26 01:03:00 +01:00
"version" , "Print the version and exit" , & printVersion
) ;
2022-01-27 22:58:38 +01:00
2024-01-08 23:13:17 +01:00
// Print help and exit
if ( cliOptions . helpWanted ) {
cliArgs ~ = "--help" ;
2024-01-13 20:54:15 +01:00
helpRequested = true ;
2019-04-11 04:26:20 +02:00
}
2024-01-08 23:13:17 +01:00
// Print the version and exit
2019-04-11 04:26:20 +02:00
if ( printVersion ) {
2024-01-08 23:13:17 +01:00
writeln ( applicationVersion ) ;
exit ( EXIT_SUCCESS ) ;
2019-01-26 01:03:00 +01:00
}
} catch ( GetOptException e ) {
2024-01-08 23:13:17 +01:00
// Option errors
writeln ( e . msg ) ;
writeln ( genericHelpMessage ) ;
2019-01-26 01:03:00 +01:00
return EXIT_FAILURE ;
} catch ( Exception e ) {
2024-01-08 23:13:17 +01:00
// Generic error
writeln ( e . msg ) ;
writeln ( genericHelpMessage ) ;
2019-01-26 01:03:00 +01:00
return EXIT_FAILURE ;
}
2024-01-08 23:13:17 +01:00
// Determine the application logging verbosity
if ( verbosityCount = = 1 ) { verboseLogging = true ; }
if ( verbosityCount > = 2 ) { debugLogging = true ; }
2024-02-15 19:10:29 +01:00
// Initialize the application progress manager class
initialiseProgressManager ( verboseLogging , debugLogging ) ;
2024-01-08 23:13:17 +01:00
// Initialize the application logging class, as we know the application verbosity level
// If we need to enable logging to a file, we can only do this once we know the application configuration which is done slightly later on
initialiseLogging ( verboseLogging , debugLogging ) ;
2024-02-15 19:10:29 +01:00
2024-01-08 23:13:17 +01:00
/ * *
// most used
addLogEntry ( "Basic 'info' message" , [ "info" ] ) ; . . . . or just use addLogEntry ( "Basic 'info' message" ) ;
addLogEntry ( "Basic 'verbose' message" , [ "verbose" ] ) ;
addLogEntry ( "Basic 'debug' message" , [ "debug" ] ) ;
// GUI notify only
addLogEntry ( "Basic 'notify' ONLY message and displayed in GUI if notifications are enabled" , [ "notify" ] ) ;
// info and notify
addLogEntry ( "Basic 'info and notify' message and displayed in GUI if notifications are enabled" , [ "info" , "notify" ] ) ;
// log file only
addLogEntry ( "Information sent to the log file only, and only if logging to a file is enabled" , [ "logFileOnly" ] ) ;
// Console only (session based upload|download)
addLogEntry ( "Basic 'Console only with new line' message" , [ "consoleOnly" ] ) ;
// Console only with no new line
addLogEntry ( "Basic 'Console only with no new line' message" , [ "consoleOnlyNoNewLine" ] ) ;
* * /
// Log application start time
addLogEntry ( "Application started" , [ "debug" ] ) ;
// Who are we running as? This will print the ProcessID, UID, GID and username the application is running as
runtimeUserName = getUserName ( ) ;
// Print in debug the application version as soon as possible
addLogEntry ( "Application Version: " ~ applicationVersion , [ "debug" ] ) ;
// How was this application started - what options were passed in
addLogEntry ( "Passed in 'cliArgs': " ~ to ! string ( cliArgs ) , [ "debug" ] ) ;
addLogEntry ( "Note: --confdir and --verbose are not listed in 'cliArgs' array" , [ "debug" ] ) ;
addLogEntry ( "Passed in --confdir if present: " ~ confdirOption , [ "debug" ] ) ;
addLogEntry ( "Passed in --verbose count if present: " ~ to ! string ( verbosityCount ) , [ "debug" ] ) ;
// Create a new AppConfig object with default values,
appConfig = new ApplicationConfig ( ) ;
// Update the default application configuration with the logging level so these can be used as a config option throughout the application
appConfig . setConfigLoggingLevels ( verboseLogging , debugLogging , verbosityCount ) ;
// Initialise the application configuration, utilising --confdir if it was passed in
// Otherwise application defaults will be used to configure the application
2024-01-13 20:54:15 +01:00
if ( ! appConfig . initialise ( confdirOption , helpRequested ) ) {
2024-01-08 23:13:17 +01:00
// There was an error loading the user specified application configuration
2019-04-11 04:26:20 +02:00
// Error message already printed
return EXIT_FAILURE ;
2019-04-01 19:51:25 +02:00
}
2022-01-27 22:58:38 +01:00
2024-01-08 23:13:17 +01:00
// Update the current runtime application configuration (default or 'config' fileread-in options) from any passed in command line arguments
appConfig . updateFromArgs ( cliArgs ) ;
2022-05-03 07:09:08 +02:00
2024-01-08 23:13:17 +01:00
// As early as possible, now re-configure the logging class, given that we have read in any applicable 'config' file and updated the application running config from CLI input:
// - Enable logging to a file if this is required
// - Disable GUI notifications if this has been configured
2022-01-27 22:58:38 +01:00
2024-01-08 23:13:17 +01:00
// Configure application logging to a log file only if this has been enabled
// This is the earliest point that this can be done, as the client configuration has been read in, and any CLI arguments have been processed.
// Either of those ('confif' file, CPU arguments) could be enabling logging, thus this is the earliest point at which this can be validated and enabled.
// The buffered logging also ensures that all 'output' to this point is also captured and written out to the log file
if ( appConfig . getValueBool ( "enable_logging" ) ) {
// Calculate the application logging directory
string calculatedLogDirPath = appConfig . calculateLogDirectory ( ) ;
string calculatedLogFilePath ;
// Initialise using the configured logging directory
addLogEntry ( "Using the following path to store the runtime application log: " ~ calculatedLogDirPath , [ "verbose" ] ) ;
// Calculate the logfile name
if ( calculatedLogDirPath ! = appConfig . defaultHomePath ) {
// Log file is not going to the home directory
string logfileName = runtimeUserName ~ ".onedrive.log" ;
calculatedLogFilePath = buildNormalizedPath ( buildPath ( calculatedLogDirPath , logfileName ) ) ;
2022-01-27 22:58:38 +01:00
} else {
2024-01-08 23:13:17 +01:00
// Log file is going to the users home directory
calculatedLogFilePath = buildNormalizedPath ( buildPath ( calculatedLogDirPath , "onedrive.log" ) ) ;
2022-01-27 22:58:38 +01:00
}
2024-01-08 23:13:17 +01:00
// Update the logging class to use 'calculatedLogFilePath' for the application log file now that this has been determined
enableLogFileOutput ( calculatedLogFilePath ) ;
2022-01-27 22:58:38 +01:00
}
2022-12-05 20:02:41 +01:00
2024-01-08 23:13:17 +01:00
// Disable GUI Notifications if configured to do so
// - This option is reverse action. If 'disable_notifications' is 'true', we need to send 'false'
if ( appConfig . getValueBool ( "disable_notifications" ) ) {
// disable_notifications is true, ensure GUI notifications is initialised with false so that NO GUI notification is sent
disableGUINotifications ( false ) ;
addLogEntry ( "Disabling GUI notifications as per user configuration" ) ;
2022-05-19 08:12:53 +02:00
}
2024-01-08 23:13:17 +01:00
// Perform a depreciated options check now that the config file (if present) and CLI options have all been parsed to advise the user that their option usage might change
appConfig . checkDepreciatedOptions ( cliArgs ) ;
// Configure Client Side Filtering (selective sync) by parsing and getting a usable regex for skip_file, skip_dir and sync_list config components
selectiveSync = new ClientSideFiltering ( appConfig ) ;
if ( ! selectiveSync . initialise ( ) ) {
// exit here as something triggered a selective sync configuration failure
return EXIT_FAILURE ;
2020-06-27 11:10:37 +02:00
}
2024-01-08 23:13:17 +01:00
// Set runtimeDatabaseFile, this will get updated if we are using --dry-run
runtimeDatabaseFile = appConfig . databaseFilePath ;
// Read in 'sync_dir' from appConfig with '~' if present expanded
runtimeSyncDirectory = appConfig . initialiseRuntimeSyncDirectory ( ) ;
// DEVELOPER OPTIONS OUTPUT
// Set to display memory details as early as possible
displayMemoryUsage = appConfig . getValueBool ( "display_memory" ) ;
// set to display sync options
displaySyncOptions = appConfig . getValueBool ( "display_sync_options" ) ;
// Display the current application configuration (based on all defaults, 'config' file parsing and/or options passed in via the CLI) and exit if --display-config has been used
if ( ( appConfig . getValueBool ( "display_config" ) ) | | ( appConfig . getValueBool ( "display_running_config" ) ) ) {
// Display the application configuration
appConfig . displayApplicationConfiguration ( ) ;
// Do we exit? We exit only if '--display-config' has been used
if ( appConfig . getValueBool ( "display_config" ) ) {
return EXIT_SUCCESS ;
2019-08-24 09:18:58 +02:00
}
}
2024-01-08 23:13:17 +01:00
// Check for basic application option conflicts - flags that should not be used together and/or flag combinations that conflict with each other, values that should be present and are not
if ( appConfig . checkForBasicOptionConflicts ) {
// Any error will have been printed by the function itself, but we need a small delay here to allow the buffered logging to output any error
return EXIT_FAILURE ;
2019-08-24 09:18:58 +02:00
}
2024-01-08 23:13:17 +01:00
// Check for --dry-run operation
// If this has been requested, we need to ensure that all actions are performed against the dry-run database copy, and,
// no actual action takes place - such as deleting files if deleted online, moving files if moved online or local, downloading new & changed files, uploading new & changed files
if ( appConfig . getValueBool ( "dry_run" ) ) {
// this is a --dry-run operation
addLogEntry ( "DRY-RUN Configured. Output below shows what 'would' have occurred." ) ;
2022-08-26 01:11:10 +02:00
2024-01-08 23:13:17 +01:00
// Cleanup any existing dry-run elements ... these should never be left hanging around
cleanupDryRunDatabaseFiles ( appConfig . databaseFilePathDryRun ) ;
2022-08-05 01:16:54 +02:00
2019-03-11 07:57:47 +01:00
// Make a copy of the original items.sqlite3 for use as the dry run copy if it exists
2024-01-08 23:13:17 +01:00
if ( exists ( appConfig . databaseFilePath ) ) {
// In a --dry-run --resync scenario, we should not copy the existing database file
if ( ! appConfig . getValueBool ( "resync" ) ) {
// Copy the existing DB file to the dry-run copy
addLogEntry ( "DRY-RUN: Copying items.sqlite3 to items-dryrun.sqlite3 to use for dry run operations" ) ;
copy ( appConfig . databaseFilePath , appConfig . databaseFilePathDryRun ) ;
2021-02-06 10:46:56 +01:00
} else {
2024-01-08 23:13:17 +01:00
// No database copy due to --resync
addLogEntry ( "DRY-RUN: No database copy created for --dry-run due to --resync also being used" ) ;
2021-02-06 10:46:56 +01:00
}
2019-03-11 07:57:47 +01:00
}
2024-01-08 23:13:17 +01:00
// update runtimeDatabaseFile now that we are using the dry run path
runtimeDatabaseFile = appConfig . databaseFilePathDryRun ;
2016-12-25 19:23:33 +01:00
}
2024-01-08 23:13:17 +01:00
2022-03-07 19:35:00 +01:00
// Handle --logout as separate item, do not 'resync' on a --logout
2024-01-08 23:13:17 +01:00
if ( appConfig . getValueBool ( "logout" ) ) {
addLogEntry ( "--logout requested" , [ "debug" ] ) ;
addLogEntry ( "Deleting the saved authentication status ..." ) ;
if ( ! appConfig . getValueBool ( "dry_run" ) ) {
safeRemove ( appConfig . refreshTokenFilePath ) ;
} else {
// --dry-run scenario ... technically we should not be making any local file changes .......
addLogEntry ( "DRY RUN: Not removing the saved authentication status" ) ;
2021-11-16 20:05:11 +01:00
}
// Exit
return EXIT_SUCCESS ;
}
2022-03-07 19:35:00 +01:00
// Handle --reauth to re-authenticate the client
2024-01-08 23:13:17 +01:00
if ( appConfig . getValueBool ( "reauth" ) ) {
addLogEntry ( "--reauth requested" , [ "debug" ] ) ;
addLogEntry ( "Deleting the saved authentication status ... re-authentication requested" ) ;
if ( ! appConfig . getValueBool ( "dry_run" ) ) {
safeRemove ( appConfig . refreshTokenFilePath ) ;
} else {
// --dry-run scenario ... technically we should not be making any local file changes .......
addLogEntry ( "DRY RUN: Not removing the saved authentication status" ) ;
2022-03-07 19:35:00 +01:00
}
}
2024-01-08 23:13:17 +01:00
// --resync should be considered a 'last resort item' or if the application configuration has changed, where a resync is needed .. the user needs to 'accept' this warning to proceed
// If --resync has not been used (bool value is false), check the application configuration for 'changes' that require a --resync to ensure that the data locally reflects the users requested configuration
if ( appConfig . getValueBool ( "resync" ) ) {
// what is the risk acceptance for --resync?
bool resyncRiskAcceptance = appConfig . displayResyncRiskForAcceptance ( ) ;
2024-01-16 23:04:42 +01:00
addLogEntry ( "Returned --resync risk acceptance: " ~ to ! string ( resyncRiskAcceptance ) , [ "debug" ] ) ;
2022-05-10 05:43:32 +02:00
2024-01-08 23:13:17 +01:00
// Action based on user response
if ( ! resyncRiskAcceptance ) {
// --resync risk not accepted
return EXIT_FAILURE ;
2018-12-19 19:42:28 +01:00
} else {
2024-01-08 23:13:17 +01:00
addLogEntry ( "--resync issued and risk accepted" , [ "debug" ] ) ;
// --resync risk accepted, perform a cleanup of items that require a cleanup
appConfig . cleanupHashFilesDueToResync ( ) ;
// Make a backup of the applicable configuration file
appConfig . createBackupConfigFile ( ) ;
// Update hash files and generate a new config backup
appConfig . updateHashContentsForConfigFiles ( ) ;
// Remove the items database
processResyncDatabaseRemoval ( runtimeDatabaseFile ) ;
2018-12-19 19:42:28 +01:00
}
2024-01-08 23:13:17 +01:00
} else {
2024-03-08 21:15:11 +01:00
// Is the application currently authenticated? If not, it is pointless checking if a --resync is required until the application is authenticated
if ( exists ( appConfig . refreshTokenFilePath ) ) {
// Has any of our application configuration that would require a --resync been changed?
if ( appConfig . applicationChangeWhereResyncRequired ( ) ) {
// Application configuration has changed however --resync not issued, fail fast
addLogEntry ( ) ;
addLogEntry ( "An application configuration change has been detected where a --resync is required" ) ;
addLogEntry ( ) ;
return EXIT_RESYNC_REQUIRED ;
} else {
// No configuration change that requires a --resync to be issued
// Special cases need to be checked - if these options were enabled, it creates a false 'Resync Required' flag, so do not create a backup
if ( ( ! appConfig . getValueBool ( "list_business_shared_items" ) ) ) {
// Make a backup of the applicable configuration file
appConfig . createBackupConfigFile ( ) ;
// Update hash files and generate a new config backup
appConfig . updateHashContentsForConfigFiles ( ) ;
}
}
2022-08-05 22:25:58 +02:00
}
2018-12-19 19:42:28 +01:00
}
2024-01-08 23:13:17 +01:00
// Implement https://github.com/abraunegg/onedrive/issues/1129
// Force a synchronization of a specific folder, only when using --synchronize --single-directory and ignoring all non-default skip_dir and skip_file rules
if ( appConfig . getValueBool ( "force_sync" ) ) {
// appConfig.checkForBasicOptionConflicts() has already checked for the basic requirements for --force-sync
addLogEntry ( ) ;
addLogEntry ( "WARNING: Overriding application configuration to use application defaults for skip_dir and skip_file due to --sync --single-directory --force-sync being used" ) ;
addLogEntry ( ) ;
bool forceSyncRiskAcceptance = appConfig . displayForceSyncRiskForAcceptance ( ) ;
addLogEntry ( "Returned --force-sync risk acceptance: " ~ forceSyncRiskAcceptance , [ "debug" ] ) ;
2022-09-26 03:49:48 +02:00
2024-01-08 23:13:17 +01:00
// Action based on user response
if ( ! forceSyncRiskAcceptance ) {
// --force-sync risk not accepted
2022-09-26 03:49:48 +02:00
return EXIT_FAILURE ;
2024-01-08 23:13:17 +01:00
} else {
// --force-sync risk accepted
// reset set config using function to use application defaults
appConfig . resetSkipToDefaults ( ) ;
// update sync engine regex with reset defaults
selectiveSync . setDirMask ( appConfig . getValueString ( "skip_dir" ) ) ;
selectiveSync . setFileMask ( appConfig . getValueString ( "skip_file" ) ) ;
2022-05-05 20:48:50 +02:00
}
}
2024-01-12 21:46:47 +01:00
// What IP Protocol are we going to use to access the network with
appConfig . displayIPProtocol ( ) ;
2020-12-17 20:17:52 +01:00
// Test if OneDrive service can be reached, exit if it cant be reached
2024-01-08 23:13:17 +01:00
addLogEntry ( "Testing network to ensure network connectivity to Microsoft OneDrive Service" , [ "debug" ] ) ;
online = testInternetReachability ( appConfig ) ;
// If we are not 'online' - how do we handle this situation?
2020-12-17 20:17:52 +01:00
if ( ! online ) {
2024-01-08 23:13:17 +01:00
// We are unable to initialise the OneDrive API as we are not online
if ( ! appConfig . getValueBool ( "monitor" ) ) {
2020-12-17 20:17:52 +01:00
// Running as --synchronize
2024-01-08 23:13:17 +01:00
addLogEntry ( ) ;
addLogEntry ( "ERROR: Unable to reach Microsoft OneDrive API service, unable to initialise application" ) ;
addLogEntry ( ) ;
2018-12-04 01:15:44 +01:00
return EXIT_FAILURE ;
2020-12-17 20:17:52 +01:00
} else {
// Running as --monitor
2024-01-08 23:13:17 +01:00
addLogEntry ( ) ;
addLogEntry ( "Unable to reach the Microsoft OneDrive API service at this point in time, re-trying network tests based on applicable intervals" ) ;
addLogEntry ( ) ;
if ( ! retryInternetConnectivtyTest ( appConfig ) ) {
2020-12-17 20:17:52 +01:00
return EXIT_FAILURE ;
}
2018-12-04 01:15:44 +01:00
}
2022-03-07 04:02:50 +01:00
}
2024-01-08 23:13:17 +01:00
// This needs to be a separate 'if' statement, as, if this was an 'if-else' from above, if we were originally offline and using --monitor, we would never get to this point
2022-03-07 04:02:50 +01:00
if ( online ) {
2022-03-07 00:57:20 +01:00
// Check Application Version
2024-01-08 23:13:17 +01:00
addLogEntry ( "Checking Application Version ..." , [ "verbose" ] ) ;
2022-03-07 00:57:20 +01:00
checkApplicationVersion ( ) ;
2024-01-08 23:13:17 +01:00
// Initialise the OneDrive API
addLogEntry ( "Attempting to initialise the OneDrive API ..." , [ "verbose" ] ) ;
2024-02-20 19:13:26 +01:00
OneDriveApi oneDriveApiInstance = new OneDriveApi ( appConfig ) ;
2024-01-08 23:13:17 +01:00
appConfig . apiWasInitialised = oneDriveApiInstance . initialise ( ) ;
if ( appConfig . apiWasInitialised ) {
addLogEntry ( "The OneDrive API was initialised successfully" , [ "verbose" ] ) ;
// Flag that we were able to initalise the API in the application config
oneDriveApiInstance . debugOutputConfiguredAPIItems ( ) ;
2024-02-20 19:13:26 +01:00
oneDriveApiInstance . shutdown ( ) ;
object . destroy ( oneDriveApiInstance ) ;
2024-01-08 23:13:17 +01:00
// Need to configure the itemDB and syncEngineInstance for 'sync' and 'non-sync' operations
addLogEntry ( "Opening the item database ..." , [ "verbose" ] ) ;
// Configure the Item Database
itemDB = new ItemDatabase ( runtimeDatabaseFile ) ;
// Was the database successfully initialised?
if ( ! itemDB . isDatabaseInitialised ( ) ) {
// no .. destroy class
itemDB = null ;
// exit application
2020-11-20 20:46:05 +01:00
return EXIT_FAILURE ;
}
2024-01-08 23:13:17 +01:00
// Initialise the syncEngine
syncEngineInstance = new SyncEngine ( appConfig , itemDB , selectiveSync ) ;
appConfig . syncEngineWasInitialised = syncEngineInstance . initialise ( ) ;
// Are we not doing a --sync or a --monitor operation? Both of these will be false if they are not set
if ( ( ! appConfig . getValueBool ( "synchronize" ) ) & & ( ! appConfig . getValueBool ( "monitor" ) ) ) {
// Are we performing some sort of 'no-sync' task?
// - Are we obtaining the Office 365 Drive ID for a given Office 365 SharePoint Shared Library?
// - Are we displaying the sync satus?
2024-03-08 21:15:11 +01:00
// - Are we getting the URL for a file online?
// - Are we listing who modified a file last online?
// - Are we listing OneDrive Business Shared Items?
2024-01-08 23:13:17 +01:00
// - Are we createing a shareable link for an existing file on OneDrive?
// - Are we just creating a directory online, without any sync being performed?
// - Are we just deleting a directory online, without any sync being performed?
// - Are we renaming or moving a directory?
// - Are we displaying the quota information?
// - Did we just authorise the client?
// --get-sharepoint-drive-id - Get the SharePoint Library drive_id
if ( appConfig . getValueString ( "sharepoint_library_name" ) ! = "" ) {
// Get the SharePoint Library drive_id
syncEngineInstance . querySiteCollectionForDriveID ( appConfig . getValueString ( "sharepoint_library_name" ) ) ;
// Exit application
// Use exit scopes to shutdown API and cleanup data
return EXIT_SUCCESS ;
}
// --display-sync-status - Query the sync status
if ( appConfig . getValueBool ( "display_sync_status" ) ) {
// path to query variable
string pathToQueryStatusOn ;
// What path do we query?
if ( ! appConfig . getValueString ( "single_directory" ) . empty ) {
pathToQueryStatusOn = "/" ~ appConfig . getValueString ( "single_directory" ) ;
} else {
pathToQueryStatusOn = "/" ;
}
// Query the sync status
syncEngineInstance . queryOneDriveForSyncStatus ( pathToQueryStatusOn ) ;
// Exit application
// Use exit scopes to shutdown API and cleanup data
return EXIT_SUCCESS ;
}
// --get-file-link - Get the URL path for a synced file?
if ( appConfig . getValueString ( "get_file_link" ) ! = "" ) {
// Query the OneDrive API for the file link
syncEngineInstance . queryOneDriveForFileDetails ( appConfig . getValueString ( "get_file_link" ) , runtimeSyncDirectory , "URL" ) ;
// Exit application
// Use exit scopes to shutdown API and cleanup data
return EXIT_SUCCESS ;
}
// --modified-by - Are we listing the modified-by details of a provided path?
if ( appConfig . getValueString ( "modified_by" ) ! = "" ) {
// Query the OneDrive API for the last modified by details
syncEngineInstance . queryOneDriveForFileDetails ( appConfig . getValueString ( "modified_by" ) , runtimeSyncDirectory , "ModifiedBy" ) ;
// Exit application
// Use exit scopes to shutdown API and cleanup data
return EXIT_SUCCESS ;
}
2024-03-08 21:15:11 +01:00
// --list-shared-items - Are we listing OneDrive Business Shared Items
if ( appConfig . getValueBool ( "list_business_shared_items" ) ) {
// Is this a business account type?
if ( appConfig . accountType = = "business" ) {
// List OneDrive Business Shared Items
syncEngineInstance . listBusinessSharedObjects ( ) ;
} else {
addLogEntry ( "ERROR: Unsupported account type for listing OneDrive Business Shared Items" ) ;
}
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
}
2024-01-08 23:13:17 +01:00
// --create-share-link - Are we createing a shareable link for an existing file on OneDrive?
if ( appConfig . getValueString ( "create_share_link" ) ! = "" ) {
// Query OneDrive for the file, and if valid, create a shareable link for the file
// By default, the shareable link will be read-only.
// If the user adds:
// --with-editing-perms
// this will create a writeable link
syncEngineInstance . queryOneDriveForFileDetails ( appConfig . getValueString ( "create_share_link" ) , runtimeSyncDirectory , "ShareableLink" ) ;
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
}
// --create-directory - Are we just creating a directory online, without any sync being performed?
if ( ( appConfig . getValueString ( "create_directory" ) ! = "" ) ) {
// Handle the remote path creation and updating of the local database without performing a sync
syncEngineInstance . createDirectoryOnline ( appConfig . getValueString ( "create_directory" ) ) ;
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
}
// --remove-directory - Are we just deleting a directory online, without any sync being performed?
if ( ( appConfig . getValueString ( "remove_directory" ) ! = "" ) ) {
// Handle the remote path deletion without performing a sync
syncEngineInstance . deleteByPath ( appConfig . getValueString ( "remove_directory" ) ) ;
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
}
// Are we renaming or moving a directory?
// onedrive --source-directory 'path/as/source/' --destination-directory 'path/as/destination'
if ( ( appConfig . getValueString ( "source_directory" ) ! = "" ) & & ( appConfig . getValueString ( "destination_directory" ) ! = "" ) ) {
// We are renaming or moving a directory
syncEngineInstance . uploadMoveItem ( appConfig . getValueString ( "source_directory" ) , appConfig . getValueString ( "destination_directory" ) ) ;
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
}
// Are we displaying the quota information?
if ( appConfig . getValueBool ( "display_quota" ) ) {
// Query and respond with the quota details
syncEngineInstance . queryOneDriveForQuotaDetails ( ) ;
// Exit application
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
}
// If we get to this point, we have not performed a 'no-sync' task ..
// Did we just authorise the client?
if ( appConfig . applicationAuthorizeResponseUri ) {
// Authorisation activity
if ( exists ( appConfig . refreshTokenFilePath ) ) {
// OneDrive refresh token exists
addLogEntry ( ) ;
addLogEntry ( "The application has been successfully authorised, but no extra command options have been specified." ) ;
addLogEntry ( ) ;
addLogEntry ( genericHelpMessage ) ;
addLogEntry ( ) ;
// Use exit scopes to shutdown API
return EXIT_SUCCESS ;
} else {
// We just authorised, but refresh_token does not exist .. probably an auth error?
addLogEntry ( ) ;
addLogEntry ( "Your application's authorisation was unsuccessful. Please review your URI response entry, then attempt authorisation again with a new URI response." ) ;
addLogEntry ( ) ;
// Use exit scopes to shutdown API
return EXIT_FAILURE ;
}
} else {
// No authorisation activity
addLogEntry ( ) ;
addLogEntry ( "Your command line input is missing either the '--sync' or '--monitor' switches. Please include one (but not both) of these switches in your command line, or refer to 'onedrive --help' for additional guidance." ) ;
addLogEntry ( ) ;
addLogEntry ( "It is important to note that you must include one of these two arguments in your command line for the application to perform a synchronisation with Microsoft OneDrive" ) ;
addLogEntry ( ) ;
// Use exit scopes to shutdown API
// invalidSyncExit = true;
return EXIT_FAILURE ;
}
}
2020-03-14 20:29:44 +01:00
} else {
2024-01-08 23:13:17 +01:00
// API could not be initialised
addLogEntry ( "The OneDrive API could not be initialised" ) ;
2020-03-14 20:29:44 +01:00
return EXIT_FAILURE ;
}
2018-04-24 04:14:36 +02:00
}
2022-08-30 11:09:35 +02:00
2024-01-08 23:13:17 +01:00
// Configure the sync direcory based on the runtimeSyncDirectory configured directory
addLogEntry ( "All application operations will be performed in the configured local 'sync_dir' directory: " ~ runtimeSyncDirectory , [ "verbose" ] ) ;
2022-11-18 22:53:49 +01:00
try {
2024-01-08 23:13:17 +01:00
if ( ! exists ( runtimeSyncDirectory ) ) {
addLogEntry ( "runtimeSyncDirectory: Configured 'sync_dir' is missing locally. Creating: " ~ runtimeSyncDirectory , [ "debug" ] ) ;
2022-11-18 22:53:49 +01:00
try {
// Attempt to create the sync dir we have been configured with
2024-01-08 23:13:17 +01:00
mkdirRecurse ( runtimeSyncDirectory ) ;
2022-11-18 22:53:49 +01:00
// Configure the applicable permissions for the folder
2024-01-08 23:13:17 +01:00
addLogEntry ( "Setting directory permissions for: " ~ runtimeSyncDirectory , [ "debug" ] ) ;
runtimeSyncDirectory . setAttributes ( appConfig . returnRequiredDirectoryPermisions ( ) ) ;
2022-11-18 22:53:49 +01:00
} catch ( std . file . FileException e ) {
// Creating the sync directory failed
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: Unable to create the configured local 'sync_dir' directory: " ~ e . msg ) ;
2022-11-18 22:53:49 +01:00
// Use exit scopes to shutdown API
return EXIT_FAILURE ;
}
2019-07-30 23:20:26 +02:00
}
2022-11-18 22:53:49 +01:00
} catch ( std . file . FileException e ) {
// Creating the sync directory failed
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: Unable to test for the existence of the configured local 'sync_dir' directory: " ~ e . msg ) ;
2022-11-18 22:53:49 +01:00
// Use exit scopes to shutdown API
return EXIT_FAILURE ;
2018-12-23 01:15:10 +01:00
}
2022-08-30 22:41:52 +02:00
2024-01-08 23:13:17 +01:00
// Change the working directory to the 'sync_dir' as configured
chdir ( runtimeSyncDirectory ) ;
2022-10-12 04:34:42 +02:00
2024-01-08 23:13:17 +01:00
// Do we need to validate the runtimeSyncDirectory to check for the presence of a '.nosync' file
checkForNoMountScenario ( ) ;
2023-03-17 23:53:58 +01:00
2024-01-19 20:15:33 +01:00
// Set the default thread pool value
defaultPoolThreads ( to ! int ( appConfig . getValueLong ( "threads" ) ) ) ;
2024-01-08 23:13:17 +01:00
// Is the sync engine initiallised correctly?
if ( appConfig . syncEngineWasInitialised ) {
// Configure some initial variables
string singleDirectoryPath ;
string localPath = "." ;
2018-12-28 02:26:03 +01:00
string remotePath = "/" ;
2024-01-08 23:13:17 +01:00
2024-02-11 07:46:07 +01:00
if ( ! appConfig . getValueBool ( "resync" ) ) {
// Check if there are interrupted upload session(s)
if ( syncEngineInstance . checkForInterruptedSessionUploads ) {
// Need to re-process the session upload files to resume the failed session uploads
addLogEntry ( "There are interrupted session uploads that need to be resumed ..." ) ;
// Process the session upload files
syncEngineInstance . processForInterruptedSessionUploads ( ) ;
}
} else {
// Clean up any upload session files due to --resync being used
syncEngineInstance . clearInterruptedSessionUploads ( ) ;
2024-01-08 23:13:17 +01:00
}
// Are we doing a single directory operation (--single-directory) ?
if ( ! appConfig . getValueString ( "single_directory" ) . empty ) {
// Set singleDirectoryPath
singleDirectoryPath = appConfig . getValueString ( "single_directory" ) ;
// Ensure that this is a normalised relative path to runtimeSyncDirectory
string normalisedRelativePath = replace ( buildNormalizedPath ( absolutePath ( singleDirectoryPath ) ) , buildNormalizedPath ( absolutePath ( runtimeSyncDirectory ) ) , "." ) ;
// The user provided a directory to sync within the configured 'sync_dir' path
// This also validates if the path being used exists online and/or does not have a 'case-insensitive match'
syncEngineInstance . setSingleDirectoryScope ( normalisedRelativePath ) ;
// Does the directory we want to sync actually exist locally?
if ( ! exists ( singleDirectoryPath ) ) {
// The requested path to use with --single-directory does not exist locally within the configured 'sync_dir'
addLogEntry ( "WARNING: The requested path for --single-directory does not exist locally. Creating requested path within " ~ runtimeSyncDirectory , [ "info" , "notify" ] ) ;
// Make the required --single-directory path locally
mkdirRecurse ( singleDirectoryPath ) ;
// Configure the applicable permissions for the folder
addLogEntry ( "Setting directory permissions for: " ~ singleDirectoryPath , [ "debug" ] ) ;
singleDirectoryPath . setAttributes ( appConfig . returnRequiredDirectoryPermisions ( ) ) ;
}
// Update the paths that we use to perform the sync actions
localPath = singleDirectoryPath ;
remotePath = singleDirectoryPath ;
// Display that we are syncing from a specific path due to --single-directory
addLogEntry ( "Syncing changes from this selected path: " ~ singleDirectoryPath , [ "verbose" ] ) ;
2018-12-28 02:26:03 +01:00
}
2015-09-01 20:45:34 +02:00
2024-01-08 23:13:17 +01:00
// Are we doing a --sync operation? This includes doing any --single-directory operations
if ( appConfig . getValueBool ( "synchronize" ) ) {
// Did the user specify --upload-only?
if ( appConfig . getValueBool ( "upload_only" ) ) {
// Perform the --upload-only sync process
performUploadOnlySyncProcess ( localPath ) ;
}
// Did the user specify --download-only?
if ( appConfig . getValueBool ( "download_only" ) ) {
// Only download data from OneDrive
syncEngineInstance . syncOneDriveAccountToLocalDisk ( ) ;
// Perform the DB consistency check
// This will also delete any out-of-sync flagged items if configured to do so
syncEngineInstance . performDatabaseConsistencyAndIntegrityCheck ( ) ;
// Do we cleanup local files?
// - Deletes of data from online will already have been performed, but what we are now doing is searching the local filesystem
// for any new data locally, that usually would be uploaded to OneDrive, but instead, because of the options being
// used, will need to be deleted from the local filesystem
if ( appConfig . getValueBool ( "cleanup_local_files" ) ) {
// Perform the filesystem walk
syncEngineInstance . scanLocalFilesystemPathForNewData ( localPath ) ;
2018-03-14 05:43:40 +01:00
}
2015-09-20 21:21:51 +02:00
}
2024-01-08 23:13:17 +01:00
// If no use of --upload-only or --download-only
if ( ( ! appConfig . getValueBool ( "upload_only" ) ) & & ( ! appConfig . getValueBool ( "download_only" ) ) ) {
// Perform the standard sync process
performStandardSyncProcess ( localPath ) ;
}
// Detail the outcome of the sync process
displaySyncOutcome ( ) ;
2024-02-15 19:10:29 +01:00
progressManager . clearAllJobs ( ) ;
2018-03-14 05:43:40 +01:00
}
2024-01-08 23:13:17 +01:00
// Are we doing a --monitor operation?
if ( appConfig . getValueBool ( "monitor" ) ) {
// What are the current values for the platform we are running on
// Max number of open files /proc/sys/fs/file-max
string maxOpenFiles = strip ( readText ( "/proc/sys/fs/file-max" ) ) ;
// What is the currently configured maximum inotify watches that can be used
// /proc/sys/fs/inotify/max_user_watches
string maxInotifyWatches = strip ( readText ( "/proc/sys/fs/inotify/max_user_watches" ) ) ;
// Start the monitor process
addLogEntry ( "OneDrive synchronisation interval (seconds): " ~ to ! string ( appConfig . getValueLong ( "monitor_interval" ) ) ) ;
// If we are in a --download-only method of operation, the output of these is not required
if ( ! appConfig . getValueBool ( "download_only" ) ) {
addLogEntry ( "Maximum allowed open files: " ~ maxOpenFiles , [ "verbose" ] ) ;
addLogEntry ( "Maximum allowed inotify user watches: " ~ maxInotifyWatches , [ "verbose" ] ) ;
}
// Configure the monitor class
filesystemMonitor = new Monitor ( appConfig , selectiveSync ) ;
// Delegated function for when inotify detects a new local directory has been created
filesystemMonitor . onDirCreated = delegate ( string path ) {
2020-04-06 12:05:06 +02:00
// Handle .folder creation if skip_dotfiles is enabled
2024-01-08 23:13:17 +01:00
if ( ( appConfig . getValueBool ( "skip_dotfiles" ) ) & & ( isDotFile ( path ) ) ) {
addLogEntry ( "[M] Skipping watching local path - .folder found & --skip-dot-files enabled: " ~ path , [ "verbose" ] ) ;
2020-04-06 12:05:06 +02:00
} else {
2024-01-08 23:13:17 +01:00
addLogEntry ( "[M] Local directory created: " ~ path , [ "verbose" ] ) ;
2020-04-06 12:05:06 +02:00
try {
2024-01-08 23:13:17 +01:00
syncEngineInstance . scanLocalFilesystemPathForNewData ( path ) ;
2020-04-06 12:05:06 +02:00
} catch ( CurlException e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Offline, cannot create remote dir: " ~ path , [ "verbose" ] ) ;
2020-04-06 12:05:06 +02:00
} catch ( Exception e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Cannot create remote directory: " ~ e . msg , [ "info" , "notify" ] ) ;
2020-04-06 12:05:06 +02:00
}
2018-03-14 05:43:40 +01:00
}
} ;
2024-01-08 23:13:17 +01:00
// Delegated function for when inotify detects a local file has been changed
2024-02-12 07:12:20 +01:00
filesystemMonitor . onFileChanged = delegate ( string [ ] changedLocalFilesToUploadToOneDrive ) {
2024-01-08 23:13:17 +01:00
// Handle a potentially locally changed file
// Logging for this event moved to handleLocalFileTrigger() due to threading and false triggers from scanLocalFilesystemPathForNewData() above
2024-02-12 07:12:20 +01:00
addLogEntry ( "[M] Total number of local file changed: " ~ to ! string ( changedLocalFilesToUploadToOneDrive . length ) ) ;
syncEngineInstance . handleLocalFileTrigger ( changedLocalFilesToUploadToOneDrive ) ;
2018-03-14 05:43:40 +01:00
} ;
2024-01-08 23:13:17 +01:00
// Delegated function for when inotify detects a delete event
filesystemMonitor . onDelete = delegate ( string path ) {
addLogEntry ( "[M] Local item deleted: " ~ path , [ "verbose" ] ) ;
2018-03-14 05:43:40 +01:00
try {
2024-01-08 23:13:17 +01:00
addLogEntry ( "The operating system sent a deletion notification. Trying to delete the item as requested" ) ;
syncEngineInstance . deleteByPath ( path ) ;
2019-02-21 09:51:15 +01:00
} catch ( CurlException e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Offline, cannot delete item: " ~ path , [ "verbose" ] ) ;
2018-12-06 00:50:46 +01:00
} catch ( SyncException e ) {
if ( e . msg = = "The item to delete is not in the local database" ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Item cannot be deleted from Microsoft OneDrive because it was not found in the local database" , [ "verbose" ] ) ;
2018-12-06 00:50:46 +01:00
} else {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Cannot delete remote item: " ~ e . msg , [ "info" , "notify" ] ) ;
2018-12-06 00:50:46 +01:00
}
2018-03-14 05:43:40 +01:00
} catch ( Exception e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Cannot delete remote item: " ~ e . msg , [ "info" , "notify" ] ) ;
2018-03-14 05:43:40 +01:00
}
} ;
2024-01-08 23:13:17 +01:00
// Delegated function for when inotify detects a move event
filesystemMonitor . onMove = delegate ( string from , string to ) {
addLogEntry ( "[M] Local item moved: " ~ from ~ " -> " ~ to , [ "verbose" ] ) ;
2018-03-14 05:43:40 +01:00
try {
2020-04-06 12:05:06 +02:00
// Handle .folder -> folder if skip_dotfiles is enabled
2024-01-08 23:13:17 +01:00
if ( ( appConfig . getValueBool ( "skip_dotfiles" ) ) & & ( isDotFile ( from ) ) ) {
2020-04-06 12:05:06 +02:00
// .folder -> folder handling - has to be handled as a new folder
2024-01-08 23:13:17 +01:00
syncEngineInstance . scanLocalFilesystemPathForNewData ( to ) ;
2020-04-06 12:05:06 +02:00
} else {
2024-01-08 23:13:17 +01:00
syncEngineInstance . uploadMoveItem ( from , to ) ;
2020-04-06 12:05:06 +02:00
}
2019-02-21 09:51:15 +01:00
} catch ( CurlException e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Offline, cannot move item !" , [ "verbose" ] ) ;
2018-03-14 05:43:40 +01:00
} catch ( Exception e ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Cannot move item: " ~ e . msg , [ "info" , "notify" ] ) ;
2018-03-14 05:43:40 +01:00
}
} ;
2024-01-08 23:13:17 +01:00
// Handle SIGINT and SIGTERM
2018-12-28 03:19:20 +01:00
signal ( SIGINT , & exitHandler ) ;
signal ( SIGTERM , & exitHandler ) ;
2024-01-08 23:13:17 +01:00
// Initialise the local filesystem monitor class using inotify to monitor for local filesystem changes
// If we are in a --download-only method of operation, we do not enable local filesystem monitoring
if ( ! appConfig . getValueBool ( "download_only" ) ) {
// Not using --download-only
2019-10-31 04:59:44 +01:00
try {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Initialising filesystem inotify monitoring ..." ) ;
filesystemMonitor . initialise ( ) ;
addLogEntry ( "Performing initial syncronisation to ensure consistent local state ..." ) ;
} catch ( MonitorException e ) {
// monitor class initialisation failed
addLogEntry ( "ERROR: " ~ e . msg ) ;
2022-08-30 22:41:52 +02:00
return EXIT_FAILURE ;
2019-10-31 04:59:44 +01:00
}
}
2024-01-08 23:13:17 +01:00
// Filesystem monitor loop variables
// Immutables
immutable auto checkOnlineInterval = dur ! "seconds" ( appConfig . getValueLong ( "monitor_interval" ) ) ;
immutable auto githubCheckInterval = dur ! "seconds" ( 86400 ) ;
immutable ulong fullScanFrequency = appConfig . getValueLong ( "monitor_fullscan_frequency" ) ;
immutable ulong logOutputSupressionInterval = appConfig . getValueLong ( "monitor_log_frequency" ) ;
immutable bool webhookEnabled = appConfig . getValueBool ( "webhook_enabled" ) ;
immutable string loopStartOutputMessage = "################################################## NEW LOOP ##################################################" ;
immutable string loopStopOutputMessage = "################################################ LOOP COMPLETE ###############################################" ;
// Changables
2020-05-20 03:37:11 +02:00
bool performMonitor = true ;
ulong monitorLoopFullCount = 0 ;
2024-01-08 23:13:17 +01:00
ulong fullScanFrequencyLoopCount = 0 ;
ulong monitorLogOutputLoopCount = 0 ;
2020-05-20 03:37:11 +02:00
MonoTime lastCheckTime = MonoTime . currTime ( ) ;
2022-03-07 04:02:50 +01:00
MonoTime lastGitHubCheckTime = MonoTime . currTime ( ) ;
2024-01-08 23:13:17 +01:00
// Webhook Notification Handling
bool notificationReceived = false ;
2020-05-20 03:37:11 +02:00
while ( performMonitor ) {
2024-01-08 23:13:17 +01:00
// Do we need to validate the runtimeSyncDirectory to check for the presence of a '.nosync' file - the disk may have been ejected ..
checkForNoMountScenario ( ) ;
// If we are in a --download-only method of operation, there is no filesystem monitoring, so no inotify events to check
if ( ! appConfig . getValueBool ( "download_only" ) ) {
2020-05-05 23:20:13 +02:00
try {
2024-01-08 23:13:17 +01:00
// Process any inotify events
filesystemMonitor . update ( true ) ;
2020-05-05 23:20:13 +02:00
} catch ( MonitorException e ) {
// Catch any exceptions thrown by inotify / monitor engine
2024-01-08 23:13:17 +01:00
addLogEntry ( "ERROR: The following inotify error was generated: " ~ e . msg ) ;
2020-05-05 23:20:13 +02:00
}
}
2024-01-08 23:13:17 +01:00
2021-11-23 20:54:28 +01:00
// Check for notifications pushed from Microsoft to the webhook
if ( webhookEnabled ) {
// Create a subscription on the first run, or renew the subscription
// on subsequent runs when it is about to expire.
2024-02-20 19:13:26 +01:00
if ( oneDriveWebhook is null ) {
oneDriveWebhook = new OneDriveWebhook ( thisTid , appConfig ) ;
oneDriveWebhook . serve ( ) ;
} else
oneDriveWebhook . createOrRenewSubscription ( ) ;
2024-01-08 23:13:17 +01:00
}
// Get the current time this loop is starting
auto currentTime = MonoTime . currTime ( ) ;
// Do we perform a sync with OneDrive?
if ( ( currentTime - lastCheckTime > = checkOnlineInterval ) | | ( monitorLoopFullCount = = 0 ) ) {
// Increment relevant counters
monitorLoopFullCount + + ;
fullScanFrequencyLoopCount + + ;
monitorLogOutputLoopCount + + ;
// If full scan at a specific frequency enabled?
if ( fullScanFrequency > 0 ) {
// Full Scan set for some 'frequency' - do we flag to perform a full scan of the online data?
if ( fullScanFrequencyLoopCount > fullScanFrequency ) {
// set full scan trigger for true up
addLogEntry ( "Enabling Full Scan True Up (fullScanFrequencyLoopCount > fullScanFrequency), resetting fullScanFrequencyLoopCount = 1" , [ "debug" ] ) ;
fullScanFrequencyLoopCount = 1 ;
appConfig . fullScanTrueUpRequired = true ;
2021-11-23 20:54:28 +01:00
} else {
2024-01-08 23:13:17 +01:00
// unset full scan trigger for true up
addLogEntry ( "Disabling Full Scan True Up" , [ "debug" ] ) ;
appConfig . fullScanTrueUpRequired = false ;
2021-11-23 20:54:28 +01:00
}
2024-01-08 23:13:17 +01:00
} else {
// No it is disabled - ensure this is false
appConfig . fullScanTrueUpRequired = false ;
2021-11-23 20:54:28 +01:00
}
2022-03-07 04:02:50 +01:00
2024-01-08 23:13:17 +01:00
// Loop Start
addLogEntry ( loopStartOutputMessage , [ "debug" ] ) ;
addLogEntry ( "Total Run-Time Loop Number: " ~ to ! string ( monitorLoopFullCount ) , [ "debug" ] ) ;
addLogEntry ( "Full Scan Freqency Loop Number: " ~ to ! string ( fullScanFrequencyLoopCount ) , [ "debug" ] ) ;
SysTime startFunctionProcessingTime = Clock . currTime ( ) ;
addLogEntry ( "Start Monitor Loop Time: " ~ to ! string ( startFunctionProcessingTime ) , [ "debug" ] ) ;
// Do we perform any monitor console logging output surpression?
// 'monitor_log_frequency' controls how often, in a non-verbose application output mode, how often
// the full output of what is occuring is done. This is done to lessen the 'verbosity' of non-verbose
// logging, but only when running in --monitor
if ( monitorLogOutputLoopCount > logOutputSupressionInterval ) {
// unsurpress the logging output
monitorLogOutputLoopCount = 1 ;
addLogEntry ( "Unsuppressing initial sync log output" , [ "debug" ] ) ;
appConfig . surpressLoggingOutput = false ;
2020-05-25 03:30:04 +02:00
} else {
2024-01-08 23:13:17 +01:00
// do we surpress the logging output to absolute minimal
if ( monitorLoopFullCount = = 1 ) {
// application startup with --monitor
addLogEntry ( "Unsuppressing initial sync log output" , [ "debug" ] ) ;
appConfig . surpressLoggingOutput = false ;
} else {
// only surpress if we are not doing --verbose or higher
if ( appConfig . verbosityCount = = 0 ) {
addLogEntry ( "Suppressing --monitor log output" , [ "debug" ] ) ;
appConfig . surpressLoggingOutput = true ;
} else {
addLogEntry ( "Unsuppressing log output" , [ "debug" ] ) ;
appConfig . surpressLoggingOutput = false ;
}
2019-10-31 04:59:44 +01:00
}
}
2024-01-08 23:13:17 +01:00
// How long has the application been running for?
auto elapsedTime = Clock . currTime ( ) - applicationStartTime ;
addLogEntry ( "Application run-time thus far: " ~ to ! string ( elapsedTime ) , [ "debug" ] ) ;
// Need to re-validate that the client is still online for this loop
if ( testInternetReachability ( appConfig ) ) {
// Starting a sync
addLogEntry ( "Starting a sync with Microsoft OneDrive" ) ;
// Attempt to reset syncFailures
syncEngineInstance . resetSyncFailures ( ) ;
2024-02-02 19:49:07 +01:00
// Update cached quota details from online as this may have changed online in the background outside of this application
syncEngineInstance . freshenCachedDriveQuotaDetails ( ) ;
2024-01-08 23:13:17 +01:00
// Did the user specify --upload-only?
if ( appConfig . getValueBool ( "upload_only" ) ) {
// Perform the --upload-only sync process
performUploadOnlySyncProcess ( localPath , filesystemMonitor ) ;
} else {
// Perform the standard sync process
performStandardSyncProcess ( localPath , filesystemMonitor ) ;
2018-12-04 01:15:44 +01:00
}
2024-01-08 23:13:17 +01:00
// Handle any new inotify events
filesystemMonitor . update ( true ) ;
// Detail the outcome of the sync process
displaySyncOutcome ( ) ;
if ( appConfig . fullScanTrueUpRequired ) {
// Write WAL and SHM data to file for this loop
addLogEntry ( "Merge contents of WAL and SHM files into main database file" , [ "debug" ] ) ;
itemDB . performVacuum ( ) ;
2019-10-29 20:32:17 +01:00
}
2024-01-08 23:13:17 +01:00
} else {
// Not online
addLogEntry ( "Microsoft OneDrive service is not reachable at this time. Will re-try on next sync attempt." ) ;
2019-10-31 04:59:44 +01:00
}
2024-01-08 23:13:17 +01:00
// Output end of loop processing times
SysTime endFunctionProcessingTime = Clock . currTime ( ) ;
addLogEntry ( "End Monitor Loop Time: " ~ to ! string ( endFunctionProcessingTime ) , [ "debug" ] ) ;
addLogEntry ( "Elapsed Monitor Loop Processing Time: " ~ to ! string ( ( endFunctionProcessingTime - startFunctionProcessingTime ) ) , [ "debug" ] ) ;
2022-06-15 01:16:06 +02:00
2020-05-20 03:37:11 +02:00
// Display memory details before cleanup
2024-01-08 23:13:17 +01:00
if ( displayMemoryUsage ) displayMemoryUsagePreGC ( ) ;
2020-05-20 03:37:11 +02:00
// Perform Garbage Cleanup
2019-10-31 04:59:44 +01:00
GC . collect ( ) ;
2024-01-08 23:13:17 +01:00
// Return free memory to the OS
GC . minimize ( ) ;
2020-05-20 03:37:11 +02:00
// Display memory details after cleanup
2024-01-08 23:13:17 +01:00
if ( displayMemoryUsage ) displayMemoryUsagePostGC ( ) ;
2022-06-15 01:16:06 +02:00
2024-01-08 23:13:17 +01:00
// Log that this loop is complete
addLogEntry ( loopStopOutputMessage , [ "debug" ] ) ;
2022-06-15 01:16:06 +02:00
2024-01-08 23:13:17 +01:00
// performSync complete, set lastCheckTime to current time
lastCheckTime = MonoTime . currTime ( ) ;
2022-06-15 01:16:06 +02:00
2020-05-20 03:37:11 +02:00
// Developer break via config option
2024-01-08 23:13:17 +01:00
if ( appConfig . getValueLong ( "monitor_max_loop" ) > 0 ) {
2020-05-20 03:37:11 +02:00
// developer set option to limit --monitor loops
2024-01-08 23:13:17 +01:00
if ( monitorLoopFullCount = = ( appConfig . getValueLong ( "monitor_max_loop" ) ) ) {
performMonitor = false ;
addLogEntry ( "Exiting after " ~ to ! string ( monitorLoopFullCount ) ~ " loops due to developer set option" ) ;
}
}
}
2024-02-15 19:10:29 +01:00
progressManager . clearAllJobs ( ) ;
2024-01-08 23:13:17 +01:00
if ( performMonitor ) {
auto nextCheckTime = lastCheckTime + checkOnlineInterval ;
currentTime = MonoTime . currTime ( ) ;
auto sleepTime = nextCheckTime - currentTime ;
addLogEntry ( "Sleep for " ~ to ! string ( sleepTime ) , [ "debug" ] ) ;
if ( filesystemMonitor . initialised | | webhookEnabled ) {
if ( filesystemMonitor . initialised ) {
2024-02-20 19:13:26 +01:00
// If local monitor is on and is waiting (previous event was not from webhook)
2024-01-08 23:13:17 +01:00
// start the worker and wait for event
2024-02-20 19:13:26 +01:00
if ( ! notificationReceived )
filesystemMonitor . send ( true ) ;
2024-01-08 23:13:17 +01:00
}
if ( webhookEnabled ) {
// if onedrive webhook is enabled
// update sleep time based on renew interval
2024-02-20 19:13:26 +01:00
Duration nextWebhookCheckDuration = oneDriveWebhook . getNextExpirationCheckDuration ( ) ;
2024-01-08 23:13:17 +01:00
if ( nextWebhookCheckDuration < sleepTime ) {
sleepTime = nextWebhookCheckDuration ;
addLogEntry ( "Update sleeping time to " ~ to ! string ( sleepTime ) , [ "debug" ] ) ;
}
2024-02-20 19:13:26 +01:00
// Webhook Notification reset to false for this loop
2024-01-08 23:13:17 +01:00
notificationReceived = false ;
}
int res = 1 ;
// Process incoming notifications if any.
auto signalExists = receiveTimeout ( sleepTime ,
( int msg ) {
res = msg ;
} ,
( ulong _ ) {
notificationReceived = true ;
}
) ;
// Debug values
addLogEntry ( "signalExists = " ~ to ! string ( signalExists ) , [ "debug" ] ) ;
addLogEntry ( "worker status = " ~ to ! string ( res ) , [ "debug" ] ) ;
addLogEntry ( "notificationReceived = " ~ to ! string ( notificationReceived ) , [ "debug" ] ) ;
// Empirical evidence shows that Microsoft often sends multiple
// notifications for one single change, so we need a loop to exhaust
// all signals that were queued up by the webhook. The notifications
// do not contain any actual changes, and we will always rely do the
// delta endpoint to sync to latest. Therefore, only one sync run is
// good enough to catch up for multiple notifications.
2024-02-20 19:13:26 +01:00
if ( notificationReceived ) {
int signalCount = 1 ;
while ( true ) {
signalExists = receiveTimeout ( dur ! "seconds" ( - 1 ) , ( ulong _ ) { } ) ;
if ( signalExists ) {
signalCount + + ;
} else {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Received " ~ to ! string ( signalCount ) ~ " refresh signals from the webhook" ) ;
oneDriveWebhookCallback ( ) ;
2024-02-20 19:13:26 +01:00
break ;
2024-01-08 23:13:17 +01:00
}
}
}
if ( res = = - 1 ) {
addLogEntry ( "ERROR: Monitor worker failed." ) ;
monitorFailures = true ;
2020-05-20 03:37:11 +02:00
performMonitor = false ;
}
2024-01-08 23:13:17 +01:00
} else {
// no hooks available, nothing to check
Thread . sleep ( sleepTime ) ;
2020-05-20 03:37:11 +02:00
}
}
2024-02-15 19:10:29 +01:00
progressManager . clearAllJobs ( ) ;
2015-09-17 00:16:23 +02:00
}
}
2024-01-08 23:13:17 +01:00
} else {
// Exit application as the sync engine could not be initialised
addLogEntry ( "Application Sync Engine could not be initialised correctly" ) ;
// Use exit scope
return EXIT_FAILURE ;
}
// Exit application using exit scope
if ( ! syncEngineInstance . syncFailures & & ! monitorFailures ) {
return EXIT_SUCCESS ;
} else {
return EXIT_FAILURE ;
2015-09-14 19:21:06 +02:00
}
2015-09-01 20:45:34 +02:00
}
2015-09-20 21:21:51 +02:00
2024-01-08 23:13:17 +01:00
void performStandardExitProcess ( string scopeCaller = null ) {
// Who called this function
if ( ! scopeCaller . empty ) {
addLogEntry ( "Running performStandardExitProcess due to: " ~ scopeCaller , [ "debug" ] ) ;
2022-08-05 01:16:54 +02:00
}
2024-01-08 23:13:17 +01:00
2024-02-20 19:13:26 +01:00
// Shutdown the OneDrive Webhook instance
if ( oneDriveWebhook ! is null ) {
oneDriveWebhook . stop ( ) ;
object . destroy ( oneDriveWebhook ) ;
2022-08-05 01:16:54 +02:00
}
2024-01-08 23:13:17 +01:00
// Shutdown the sync engine
if ( syncEngineInstance ! is null ) {
addLogEntry ( "Shutdown Sync Engine instance" , [ "debug" ] ) ;
object . destroy ( syncEngineInstance ) ;
2022-08-05 01:16:54 +02:00
}
2024-01-08 23:13:17 +01:00
// Shutdown the client side filtering objects
if ( selectiveSync ! is null ) {
addLogEntry ( "Shutdown Client Side Filtering instance" , [ "debug" ] ) ;
selectiveSync . shutdown ( ) ;
object . destroy ( selectiveSync ) ;
}
// Shutdown the application configuration objects
if ( appConfig ! is null ) {
addLogEntry ( "Shutdown Application Configuration instance" , [ "debug" ] ) ;
// Cleanup any existing dry-run elements ... these should never be left hanging around
cleanupDryRunDatabaseFiles ( appConfig . databaseFilePathDryRun ) ;
object . destroy ( appConfig ) ;
}
// Shutdown any local filesystem monitoring
if ( filesystemMonitor ! is null ) {
addLogEntry ( "Shutdown Filesystem Monitoring instance" , [ "debug" ] ) ;
filesystemMonitor . shutdown ( ) ;
object . destroy ( filesystemMonitor ) ;
}
// Shutdown the database
if ( itemDB ! is null ) {
addLogEntry ( "Shutdown Database instance" , [ "debug" ] ) ;
// Make sure the .wal file is incorporated into the main db before we exit
if ( itemDB . isDatabaseInitialised ( ) ) {
itemDB . performVacuum ( ) ;
2018-12-04 01:15:44 +01:00
}
2024-01-08 23:13:17 +01:00
object . destroy ( itemDB ) ;
}
2024-01-26 23:55:11 +01:00
// Shutdown cached sockets
CurlEngine . releaseAll ( ) ;
2024-01-08 23:13:17 +01:00
// Set all objects to null
if ( scopeCaller = = "failureScope" ) {
// Set these to be null due to failure scope - prevent 'ERROR: Unable to perform a database vacuum: out of memory' when the exit scope is then called
addLogEntry ( "Setting ALL Class Objects to null due to failure scope" , [ "debug" ] ) ;
itemDB = null ;
appConfig = null ;
2024-02-20 19:13:26 +01:00
oneDriveWebhook = null ;
2024-01-08 23:13:17 +01:00
selectiveSync = null ;
syncEngineInstance = null ;
} else {
2024-02-13 19:02:48 +01:00
addLogEntry ( "Waiting for all internal threads to complete before exiting application" , [ "verbose" ] ) ;
thread_joinAll ( ) ;
2024-01-08 23:13:17 +01:00
addLogEntry ( "Application exit" , [ "debug" ] ) ;
addLogEntry ( "#######################################################################################################################################" , [ "logFileOnly" ] ) ;
// Destroy the shared logging buffer
2024-02-12 06:33:44 +01:00
( cast ( ) logBuffer ) . shutdown ( ) ;
2024-01-08 23:13:17 +01:00
object . destroy ( logBuffer ) ;
2018-12-04 01:15:44 +01:00
}
}
2024-01-08 23:13:17 +01:00
void oneDriveWebhookCallback ( ) {
// If we are in a --download-only method of operation, there is no filesystem monitoring, so no inotify events to check
if ( ! appConfig . getValueBool ( "download_only" ) ) {
try {
// Process any inotify events
filesystemMonitor . update ( true ) ;
} catch ( MonitorException e ) {
// Catch any exceptions thrown by inotify / monitor engine
addLogEntry ( "ERROR: The following inotify error was generated: " ~ e . msg ) ;
}
}
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// Download data from OneDrive last
syncEngineInstance . syncOneDriveAccountToLocalDisk ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any new inotify events
filesystemMonitor . update ( true ) ;
}
}
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
void performUploadOnlySyncProcess ( string localPath , Monitor filesystemMonitor = null ) {
// Perform the local database consistency check, picking up locally modified data and uploading this to OneDrive
syncEngineInstance . performDatabaseConsistencyAndIntegrityCheck ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any inotify events whilst the DB was being scanned
filesystemMonitor . update ( true ) ;
}
// Scan the configured 'sync_dir' for new data to upload
syncEngineInstance . scanLocalFilesystemPathForNewData ( localPath ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any new inotify events whilst the local filesystem was being scanned
filesystemMonitor . update ( true ) ;
2018-03-14 05:43:40 +01:00
}
2024-01-08 23:13:17 +01:00
}
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
void performStandardSyncProcess ( string localPath , Monitor filesystemMonitor = null ) {
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// If we are performing log supression, output this message so the user knows what is happening
if ( appConfig . surpressLoggingOutput ) {
addLogEntry ( "Syncing changes from Microsoft OneDrive ..." ) ;
}
// Zero out these arrays
syncEngineInstance . fileDownloadFailures = [ ] ;
syncEngineInstance . fileUploadFailures = [ ] ;
// Which way do we sync first?
// OneDrive first then local changes (normal operational process that uses OneDrive as the source of truth)
// Local First then OneDrive changes (alternate operation process to use local files as source of truth)
if ( appConfig . getValueBool ( "local_first" ) ) {
// Local data first
// Perform the local database consistency check, picking up locally modified data and uploading this to OneDrive
syncEngineInstance . performDatabaseConsistencyAndIntegrityCheck ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any inotify events whilst the DB was being scanned
filesystemMonitor . update ( true ) ;
}
// Scan the configured 'sync_dir' for new data to upload to OneDrive
syncEngineInstance . scanLocalFilesystemPathForNewData ( localPath ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any new inotify events whilst the local filesystem was being scanned
filesystemMonitor . update ( true ) ;
}
// Download data from OneDrive last
syncEngineInstance . syncOneDriveAccountToLocalDisk ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Cancel out any inotify events from downloading data
filesystemMonitor . update ( false ) ;
}
} else {
// Normal sync
// Download data from OneDrive first
syncEngineInstance . syncOneDriveAccountToLocalDisk ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Cancel out any inotify events from downloading data
filesystemMonitor . update ( false ) ;
}
// Perform the local database consistency check, picking up locally modified data and uploading this to OneDrive
syncEngineInstance . performDatabaseConsistencyAndIntegrityCheck ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any inotify events whilst the DB was being scanned
filesystemMonitor . update ( true ) ;
}
// Is --download-only NOT configured?
if ( ! appConfig . getValueBool ( "download_only" ) ) {
// Scan the configured 'sync_dir' for new data to upload to OneDrive
syncEngineInstance . scanLocalFilesystemPathForNewData ( localPath ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Handle any new inotify events whilst the local filesystem was being scanned
filesystemMonitor . update ( true ) ;
2020-05-25 03:30:04 +02:00
}
2024-01-08 23:13:17 +01:00
// Make sure we sync any DB data to this point, but only if not in --monitor mode
// In --monitor mode, this is handled within the 'loop', based on when the full scan true up is being performed
if ( ! appConfig . getValueBool ( "monitor" ) ) {
itemDB . performVacuum ( ) ;
}
// Perform the final true up scan to ensure we have correctly replicated the current online state locally
if ( ! appConfig . surpressLoggingOutput ) {
addLogEntry ( "Performing a last examination of the most recent online data within Microsoft OneDrive to complete the reconciliation process" ) ;
}
// We pass in the 'appConfig.fullScanTrueUpRequired' value which then flags do we use the configured 'deltaLink'
// If 'appConfig.fullScanTrueUpRequired' is true, we do not use the 'deltaLink' if we are in --monitor mode, thus forcing a full scan true up
syncEngineInstance . syncOneDriveAccountToLocalDisk ( ) ;
if ( appConfig . getValueBool ( "monitor" ) ) {
// Cancel out any inotify events from downloading data
filesystemMonitor . update ( false ) ;
}
}
}
}
2022-08-30 22:41:52 +02:00
2024-01-08 23:13:17 +01:00
void displaySyncOutcome ( ) {
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// Detail any download or upload transfer failures
syncEngineInstance . displaySyncFailures ( ) ;
// Sync is either complete or partially complete
if ( ! syncEngineInstance . syncFailures ) {
// No download or upload issues
if ( ! appConfig . getValueBool ( "monitor" ) ) addLogEntry ( ) ; // Add an additional line break so that this is clear when using --sync
addLogEntry ( "Sync with Microsoft OneDrive is complete" ) ;
} else {
addLogEntry ( ) ;
addLogEntry ( "Sync with Microsoft OneDrive has completed, however there are items that failed to sync." ) ;
// Due to how the OneDrive API works 'changes' such as add new files online, rename files online, delete files online are only sent once when using the /delta API call.
// That we failed to download it, we need to track that, and then issue a --resync to download any of these failed files .. unfortunate, but there is no easy way here
if ( ! syncEngineInstance . fileDownloadFailures . empty ) {
addLogEntry ( "To fix any download failures you may need to perform a --resync to ensure this system is correctly synced with your Microsoft OneDrive Account" ) ;
}
if ( ! syncEngineInstance . fileUploadFailures . empty ) {
addLogEntry ( "To fix any upload failures you may need to perform a --resync to ensure this system is correctly synced with your Microsoft OneDrive Account" ) ;
}
// So that from a logging perspective these messages are clear, add a line break in
addLogEntry ( ) ;
}
}
2022-08-30 22:41:52 +02:00
2024-01-08 23:13:17 +01:00
void processResyncDatabaseRemoval ( string databaseFilePathToRemove ) {
addLogEntry ( "Testing if we have exclusive access to local database file" , [ "debug" ] ) ;
// Are we the only running instance? Test that we can open the database file path
itemDB = new ItemDatabase ( databaseFilePathToRemove ) ;
// did we successfully initialise the database class?
if ( ! itemDB . isDatabaseInitialised ( ) ) {
// no .. destroy class
itemDB = null ;
// exit application - void function, force exit this way
exit ( - 1 ) ;
}
// If we have exclusive access we will not have exited
// destroy access test
destroy ( itemDB ) ;
// delete application sync state
addLogEntry ( "Deleting the saved application sync status ..." ) ;
if ( ! appConfig . getValueBool ( "dry_run" ) ) {
safeRemove ( databaseFilePathToRemove ) ;
} else {
// --dry-run scenario ... technically we should not be making any local file changes .......
addLogEntry ( "DRY RUN: Not removing the saved application sync status" ) ;
}
}
2022-08-30 22:41:52 +02:00
2024-01-08 23:13:17 +01:00
void cleanupDryRunDatabaseFiles ( string dryRunDatabaseFile ) {
// Temp variables
string dryRunShmFile = dryRunDatabaseFile ~ "-shm" ;
string dryRunWalFile = dryRunDatabaseFile ~ "-wal" ;
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
// If the dry run database exists, clean this up
if ( exists ( dryRunDatabaseFile ) ) {
// remove the existing file
addLogEntry ( "DRY-RUN: Removing items-dryrun.sqlite3 as it still exists for some reason" , [ "debug" ] ) ;
safeRemove ( dryRunDatabaseFile ) ;
}
// silent cleanup of shm files if it exists
if ( exists ( dryRunShmFile ) ) {
// remove items-dryrun.sqlite3-shm
addLogEntry ( "DRY-RUN: Removing items-dryrun.sqlite3-shm as it still exists for some reason" , [ "debug" ] ) ;
safeRemove ( dryRunShmFile ) ;
}
// silent cleanup of wal files if it exists
if ( exists ( dryRunWalFile ) ) {
// remove items-dryrun.sqlite3-wal
addLogEntry ( "DRY-RUN: Removing items-dryrun.sqlite3-wal as it still exists for some reason" , [ "debug" ] ) ;
safeRemove ( dryRunWalFile ) ;
}
}
2021-11-23 20:54:28 +01:00
2024-01-08 23:13:17 +01:00
void checkForNoMountScenario ( ) {
// If this is a 'mounted' folder, the 'mount point' should have this file to help the application stop any action to preserve data because the drive to mount is not currently mounted
if ( appConfig . getValueBool ( "check_nomount" ) ) {
// we were asked to check the mount point for the presence of a '.nosync' file
if ( exists ( ".nosync" ) ) {
addLogEntry ( "ERROR: .nosync file found in directory mount point. Aborting application startup process to safeguard data." , [ "info" , "notify" ] ) ;
Thread . sleep ( dur ! ( "msecs" ) ( 500 ) ) ;
exit ( EXIT_FAILURE ) ;
2015-09-20 21:21:51 +02:00
}
2024-01-08 23:13:17 +01:00
}
2015-09-20 21:21:51 +02:00
}
2018-12-05 20:19:00 +01:00
2024-01-08 23:13:17 +01:00
// Getting around the @nogc problem
2018-12-28 03:19:20 +01:00
// https://p0nce.github.io/d-idioms/#Bypassing-@nogc
2024-01-08 23:13:17 +01:00
auto assumeNoGC ( T ) ( T t ) if ( isFunctionPointer ! T | | isDelegate ! T ) {
2018-12-28 03:19:20 +01:00
enum attrs = functionAttributes ! T | FunctionAttribute . nogc ;
return cast ( SetFunctionAttributes ! ( T , functionLinkage ! T , attrs ) ) t ;
}
2024-01-08 23:13:17 +01:00
// Catch CTRL-C
2018-12-28 03:19:20 +01:00
extern ( C ) nothrow @nogc @system void exitHandler ( int value ) {
try {
assumeNoGC ( ( ) {
2024-01-08 23:13:17 +01:00
addLogEntry ( "Got termination signal, performing clean up" ) ;
// Wait for all parallel jobs that depend on the database to complete
addLogEntry ( "Waiting for any existing upload|download process to complete" ) ;
taskPool . finish ( true ) ;
// Was itemDb initialised?
if ( itemDB . isDatabaseInitialised ( ) ) {
2020-11-06 00:28:15 +01:00
// Make sure the .wal file is incorporated into the main db before we exit
2024-01-08 23:13:17 +01:00
addLogEntry ( "Shutting down DB connection and merging temporary data" ) ;
itemDB . performVacuum ( ) ;
object . destroy ( itemDB ) ;
2020-11-06 00:28:15 +01:00
}
2024-01-08 23:13:17 +01:00
performStandardExitProcess ( ) ;
2018-12-28 03:19:20 +01:00
} ) ( ) ;
} catch ( Exception e ) { }
exit ( 0 ) ;
2024-01-08 23:13:17 +01:00
}