Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve safeBackup function and documentation #2966

Merged
merged 6 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading