From 9717450b30b1ee41bb08a28916a4d22bc0bfca6e Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 4 May 2026 10:17:54 +0200 Subject: [PATCH 1/2] Add `quote-disable` to synctool This PR adds support for using the `quota-disable` option to diable the quota check performed before executing the remote sync operation. This is needed if the quota information returned is unreliable. --- .../Library/Main/Backend/LightWeightBackendManager.cs | 6 +++--- .../RemoteSynchronization/RemoteSynchronizationRunner.cs | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs b/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs index da243d0af..27048958c 100644 --- a/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs +++ b/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs @@ -40,7 +40,7 @@ namespace Duplicati.Library.Main.Backend private readonly bool _autoCreateFolders; private readonly string _backendUrl; private readonly int _maxRetries; - private readonly Dictionary _options; + private readonly Dictionary _options; private readonly int _retryDelay; private readonly bool _retryWithExponentialBackoff; private int _currentRetryDelay; @@ -48,11 +48,11 @@ namespace Duplicati.Library.Main.Backend private readonly IOperationProgressUpdater? _progressUpdater; private readonly IBackendProgressUpdater? _backendProgressUpdater; - public LightWeightBackendManager(string backendUrl, Dictionary options, int maxRetries = 3, int retryDelay = 1000, bool autoCreateFolders = false, bool retryWithExponentialBackoff = false) + public LightWeightBackendManager(string backendUrl, Dictionary options, int maxRetries = 3, int retryDelay = 1000, bool autoCreateFolders = false, bool retryWithExponentialBackoff = false) : this(backendUrl, options, maxRetries, retryDelay, autoCreateFolders, retryWithExponentialBackoff, null, null) { } - internal LightWeightBackendManager(string backendUrl, Dictionary options, int maxRetries = 3, int retryDelay = 1000, bool autoCreateFolders = false, bool retryWithExponentialBackoff = false, IOperationProgressUpdater? progressUpdater = null, IBackendProgressUpdater? backendProgressUpdater = null) + internal LightWeightBackendManager(string backendUrl, Dictionary options, int maxRetries = 3, int retryDelay = 1000, bool autoCreateFolders = false, bool retryWithExponentialBackoff = false, IOperationProgressUpdater? progressUpdater = null, IBackendProgressUpdater? backendProgressUpdater = null) { _backendUrl = backendUrl; _maxRetries = maxRetries; diff --git a/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs b/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs index 731557c01..9b3eeedbe 100644 --- a/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs +++ b/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs @@ -197,9 +197,10 @@ public static class RemoteSynchronizationRunner // Prepare the operations var (to_copy, to_delete, to_verify) = await PrepareFileLists(b1m, b2m, config, token).ConfigureAwait(false); + var disableQuota = Library.Utility.Utility.ParseBoolOption(dst_opts, "quota-disable"); // Check if we have enough free space in the destination to perform the synchronization. - var dst_quota = await b2m.GetQuotaInfoAsync(token).ConfigureAwait(false); + var dst_quota = disableQuota ? null : await b2m.GetQuotaInfoAsync(token).ConfigureAwait(false); if (dst_quota is not null) { var total_delete_size = to_delete.Sum(x => Math.Max(x.Size, 0)); @@ -652,11 +653,11 @@ public static class RemoteSynchronizationRunner /// The list of string options to parse /// A dictionary with the parsed options, where the key is the option name and the value is the option value. /// If an option was not parsed correctly. - private static Dictionary ParseOptions(IEnumerable options) + private static Dictionary ParseOptions(IEnumerable options) { var result = options .Select(x => x.Split('=')) - .ToDictionary(x => x[0], x => string.Join("=", x.Skip(1))); + .ToDictionary(x => x[0], x => (string?)string.Join("=", x.Skip(1))); // Double check that the options are valid by reconstructing them from the dictionary foreach (var opt in result.Select(x => $"{x.Key}={x.Value}")) From fc675638c4112df2a0f2e8d1dfa816f4da9cb52a Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 4 May 2026 10:19:54 +0200 Subject: [PATCH 2/2] Throwing a UserInformationException instead of the generic exception --- .../RemoteSynchronization/RemoteSynchronizationRunner.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs b/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs index 9b3eeedbe..12b1c5546 100644 --- a/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs +++ b/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs @@ -211,7 +211,7 @@ public static class RemoteSynchronizationRunner "Not enough free space in destination to perform the synchronization. Required: {0}, Available: {1}. Aborting.", Duplicati.Library.Utility.Utility.FormatSizeString(total_copy_size - total_delete_size), Duplicati.Library.Utility.Utility.FormatSizeString(dst_quota.FreeQuotaSpace)); - throw new Exception("Not enough free space in destination to perform the synchronization."); + throw new UserInformationException("Not enough free space in destination to perform the synchronization.", "NotEnoughDestinationSpace"); } }