diff --git a/docs/client-architecture.md b/docs/client-architecture.md index f9fc4afad..d464dac6a 100644 --- a/docs/client-architecture.md +++ b/docs/client-architecture.md @@ -97,9 +97,41 @@ This exclusion process can be illustrated by the following activity diagram. A ' When using the default operational modes (`--sync` or `--monitor`) the client application is conforming to how the Microsoft Windows OneDrive client operates in terms of resolving conflicts for files. -Additionally, when using `--resync` this conflict resolution can differ slightly, as, when using `--resync` you are *deleting* the known application state, thus, the application has zero reference as to what was previously in sync with the local file system. +When using `--resync` this conflict resolution can differ slightly, as, when using `--resync` you are *deleting* the known application state, thus, the application has zero reference as to what was previously in sync with the local file system. -Due to this factor, when using `--resync` the online source is always going to be considered accurate and the source-of-truth, regardless of the local file state, file timestamp or file hash. +Due to this factor, when using `--resync` the online source is always going to be considered accurate and the source-of-truth, regardless of the local file state, local file timestamp or local file hash. When a difference in local file hash is detected, the file will be renamed to prevent local data loss. + +> [!IMPORTANT] +> In v2.5.3 and above, when a local file is renamed due to conflict handling, this will be in the following format pattern to allow easier identification: +> +> **filename-hostname-safeBackup-number.file_extension** +> +> For example: +> ``` +> -rw-------. 1 alex alex 53402 Sep 21 08:25 file5.data +> -rw-------. 1 alex alex 53423 Nov 13 18:18 file5-onedrive-client-dev-safeBackup-0001.data +> -rw-------. 1 alex alex 53422 Nov 13 18:19 file5-onedrive-client-dev-safeBackup-0002.data +> ``` +> +> In client versions v2.5.2 and below, the renamed file have the following naming convention: +> +> **filename-hostname-number.file_extension** +> +> resulting in backup filenames of the following format: +> ``` +> -rw-------. 1 alex alex 53402 Sep 21 08:25 file5.data +> -rw-------. 1 alex alex 53432 Nov 14 05:22 file5-onedrive-client-dev-2.data +> -rw-------. 1 alex alex 53435 Nov 14 05:24 file5-onedrive-client-dev-3.data +> -rw-------. 1 alex alex 53419 Nov 14 05:22 file5-onedrive-client-dev.data +> ``` +> + +> [!CAUTION] +> The creation of backup files when there is a conflict to avoid local data loss can be disabled. +> +> To do this, utilise the configuration option **bypass_data_preservation** +> +> If this is enabled, you will experience data loss on your local data as the local file will be over-written with data from OneDrive online. Use with care and caution. ### Default Operational Modes - Conflict Handling diff --git a/src/util.d b/src/util.d index 47f8a3b3c..322e25d79 100644 --- a/src/util.d +++ b/src/util.d @@ -56,30 +56,31 @@ shared static this() { // Creates a safe backup of the given item, and only performs the function if not in a --dry-run scenario void safeBackup(const(char)[] path, bool dryRun, out string renamedPath) { - auto ext = extension(path); - auto newPath = path.chomp(ext) ~ "-" ~ deviceName; - int n = 2; - + auto ext = extension(path); + auto newPath = path.chomp(ext) ~ "-" ~ deviceName ~ "-safeBackup-"; + int n = 1; + // Limit to 1000 iterations .. 1000 file backups - while (exists(newPath ~ ext) && n < 1000) { - newPath = newPath.chomp("-" ~ (n - 1).to!string) ~ "-" ~ n.to!string; - n++; - } - + while (exists(newPath ~ format("%04d", n) ~ ext) && n < 1000) { + n++; + } + // Check if unique file name was found - if (exists(newPath ~ ext)) { + if (exists(newPath ~ format("%04d", n) ~ ext)) { // On the 1000th backup of this file, this should be triggered addLogEntry("Failed to backup " ~ to!string(path) ~ ": Unique file name could not be found after 1000 attempts", ["error"]); return; // Exit function as a unique file name could not be found } - - // Configure the new name - newPath ~= ext; - // Log that we are perform the backup by renaming the file - if (verboseLogging) {addLogEntry("The local item is out-of-sync with OneDrive, renaming to preserve existing file and prevent local data loss: " ~ to!string(path) ~ " -> " ~ to!string(newPath) , ["verbose"]);} + // Configure the new name with zero-padded counter + newPath ~= format("%04d", n) ~ ext; + + // Log that we are performing the backup by renaming the file + if (verboseLogging) { + addLogEntry("The local item is out-of-sync with OneDrive, renaming to preserve existing file and prevent local data loss: " ~ to!string(path) ~ " -> " ~ to!string(newPath), ["verbose"]); + } - if (!dryRun) { + if (!dryRun) { // Not a --dry-run scenario - do the file rename // // There are 2 options to rename a file @@ -96,13 +97,15 @@ void safeBackup(const(char)[] path, bool dryRun, out string renamedPath) { try { rename(path, newPath); renamedPath = to!string(newPath); - } catch (Exception e) { - // Handle exceptions, e.g., log error - addLogEntry("Renaming of local file failed for " ~ to!string(path) ~ ": " ~ e.msg, ["error"]); - } - } else { - if (debugLogging) {addLogEntry("DRY-RUN: Skipping renaming local file to preserve existing file and prevent data loss: " ~ to!string(path) ~ " -> " ~ to!string(newPath), ["debug"]);} - } + } catch (Exception e) { + // Handle exceptions, e.g., log error + addLogEntry("Renaming of local file failed for " ~ to!string(path) ~ ": " ~ e.msg, ["error"]); + } + } else { + if (debugLogging) { + addLogEntry("DRY-RUN: Skipping renaming local file to preserve existing file and prevent data loss: " ~ to!string(path) ~ " -> " ~ to!string(newPath), ["debug"]); + } + } } // Rename the given item, and only performs the function if not in a --dry-run scenario