Skip to content

Commit

Permalink
Improve safeBackup function and documentation (#2966)
Browse files Browse the repository at this point in the history
* Update safeBackup to use a new filename format for easier identification

<filename>-<hostname>-safeBackup-<number>.file_extention

* Update client-architecture.md to articulate backup file formatting and how to disable preserving local files to prevent local data loss
  • Loading branch information
abraunegg authored Nov 13, 2024
1 parent 6024d30 commit 8f9927f
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 25 deletions.
36 changes: 34 additions & 2 deletions docs/client-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
49 changes: 26 additions & 23 deletions src/util.d
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit 8f9927f

Please sign in to comment.