diff --git a/Tools/RemoteSynchronization/LightWeightBackendManager.cs b/Tools/RemoteSynchronization/LightWeightBackendManager.cs index f3fffe94e..617b7c142 100644 --- a/Tools/RemoteSynchronization/LightWeightBackendManager.cs +++ b/Tools/RemoteSynchronization/LightWeightBackendManager.cs @@ -9,6 +9,21 @@ using Duplicati.Library.Utility; namespace RemoteSynchronization { + /// + /// A lightweight backend manager that handles remote synchronization operations. + /// This class is designed to manage backend operations such as Get, Put, Delete, Rename, and List asynchronously. + /// It supports retrying operations with a specified delay and can automatically create folders if they do not exist. + /// The class implements IDisposable to ensure proper resource management. + /// It uses a streaming backend for efficient file operations and handles exceptions gracefully to allow for retries and recovery. + /// A retry incurs a new instantiation of the backend. + /// It is designed to mimic the behavior of Duplicati.Library.Main.Backend.BackendManager. + /// + /// The backend URL string. + /// A dictionary of options to pass to the backend. + /// The maximum number of retries for failed operations. + /// The delay between retries, in milliseconds. + /// Whether to automatically create folders if they do not exist. + /// Whether to use exponential backoff for retries. public class LightWeightBackendManager(string backendUrl, Dictionary options, int maxRetries = 3, int retryDelay = 1000, bool autoCreateFolders = false, bool retryWithExponentialBackoff = false) : IDisposable { private static readonly string LOGTAG = Duplicati.Library.Logging.Log.LogTagFromType(); @@ -24,9 +39,12 @@ namespace RemoteSynchronization private int _retryDelay = retryDelay; private IStreamingBackend? _streamingBackend = null; - //TODO Du skal have lavet docstrings - //TODO Du skal tjekke at alle parametrene er korrekte i det originale program - + /// + /// Deletes a file from the remote backend. + /// + /// The name of the remote file to delete. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous delete operation. public Task DeleteAsync(string remotename, CancellationToken token) { return RetryWithDelay( @@ -38,6 +56,11 @@ namespace RemoteSynchronization ); } + /// + /// Gets the display name of the backend. + /// This property initializes the backend if it has not been instantiated yet. + /// + /// The display name of the backend. public string DisplayName { get @@ -48,6 +71,9 @@ namespace RemoteSynchronization } } + /// + /// Disposes of the backend and streaming backend resources. + /// public void Dispose() { try @@ -63,6 +89,14 @@ namespace RemoteSynchronization } } + /// + /// Gets a file from the remote backend and writes it to the specified stream. + /// This method retries the operation with a delay if it fails. + /// + /// The name of the remote file to get. + /// The stream to write the file to. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous get operation. public Task GetAsync(string remotename, Stream stream, CancellationToken token) { return RetryWithDelay( @@ -78,6 +112,13 @@ namespace RemoteSynchronization ); } + /// + /// Instantiates the backend if it has not been instantiated yet. + /// If the backend is already instantiated, it simply returns. + /// If the maximum number of retries has been reached, it throws an InvalidOperationException. + /// This method is called internally to ensure that the backend is ready for operations. + /// + /// If the maximum number of instantiations has been reached. private void Instantiate() { if (_backend != null) @@ -98,6 +139,11 @@ namespace RemoteSynchronization } } + /// + /// Lists the files in the remote backend asynchronously. + /// + /// A cancellation token to cancel the operation. + /// An asynchronous enumerable of file entries. public IAsyncEnumerable ListAsync(CancellationToken token) { while (true) @@ -119,6 +165,13 @@ namespace RemoteSynchronization } + /// + /// Puts a file to the remote backend from the specified stream. + /// + /// The name of the remote file to put. + /// The stream containing the file data to put. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous put operation. public Task PutAsync(string remotename, Stream stream, CancellationToken token) { return RetryWithDelay( @@ -134,6 +187,15 @@ namespace RemoteSynchronization ); } + /// + /// Renames a file in the remote backend. + /// If the backend supports renaming, it uses the RenameAsync method. + /// If the backend does not support renaming, it downloads the file, renames it, and deletes the old one. + /// + /// The current name of the remote file. + /// The new name for the remote file. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous rename operation. public Task RenameAsync(string oldname, string newname, CancellationToken token) { Instantiate(); @@ -174,6 +236,20 @@ namespace RemoteSynchronization }; } + /// + /// Retries an operation with a delay if it fails. + /// This method will instantiate the backend if it has not been instantiated yet. + /// If the operation fails, it will log the error, dispose of the current backend and streaming backend, + /// reset the stream if specified, and attempt to recover from the exception. + /// If recovery is not possible, it will wait for the specified retry delay before retrying the operation. + /// The retry delay can be increased exponentially if specified. + /// + /// The name of the operation being retried, used for logging. + /// The action to perform. + /// The stream to use for the operation. Used when resetting the stream during recovery. + /// Whether to reset the stream if the operation fails. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous operation. private async Task RetryWithDelay(string operationName, Func action, Stream? stream, bool resetStream, CancellationToken token) { while (true) @@ -203,6 +279,11 @@ namespace RemoteSynchronization } } + /// + /// Attempts to create a folder in the remote backend. + /// + /// A cancellation token to cancel the operation. + /// A task that represents the asynchronous operation. The task result is true if the folder was created successfully, false otherwise. private async Task TryCreateFolder(CancellationToken token) { try @@ -224,6 +305,17 @@ namespace RemoteSynchronization } } + /// + /// Attempts to recover from an exception that occurred during a backend operation. + /// This method checks for specific types of exceptions, such as DNS resolution failures or folder missing exceptions. + /// If a DNS failure is detected, it attempts to refresh the DNS name by re-instantiating the backend and resolving DNS names. + /// If the exception is a folder missing exception and auto-creation of folders is enabled, it attempts to create the folder. + /// If recovery is not possible, it waits for the specified retry delay. + /// The retry delay will be doubled if exponential backoff is enabled. + /// + /// The exception that occurred during the operation. + /// A cancellation token to cancel the operation. + /// A task representing the asynchronous recovery operation. private async Task TryRecoverFromException(Exception ex, CancellationToken token) { // Copied from Duplicati.Library.Main.Backend.BackendManager.Handler. diff --git a/Tools/RemoteSynchronization/Program.cs b/Tools/RemoteSynchronization/Program.cs index 907fb8e9b..9af910aca 100644 --- a/Tools/RemoteSynchronization/Program.cs +++ b/Tools/RemoteSynchronization/Program.cs @@ -552,8 +552,8 @@ destination will be verified before being overwritten (if they seemingly match). /// The files to delete are the files that are found in the destination but not found in the source. /// The files to verify are the files that are found in both the source and the destination, and that have the same size and modification date. /// - /// The source backend. - /// The destination backend. + /// The source lightweight backend manager. + /// The destination lightweight backend manager. /// The parsed configuration for the tool. /// A tuple of Lists each holding the files to copy, delete and verify. private static async Task<(IEnumerable, IEnumerable, IEnumerable)> PrepareFileLists(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, Config config, CancellationToken cancelToken) @@ -638,7 +638,7 @@ destination will be verified before being overwritten (if they seemingly match). /// Renames the files in a backend. /// The renaming is done by deleting the file and re-uploading it with a new name. /// - /// The backend to rename the files in. + /// The lightweight backend manager to issue rename operations to. /// The files to rename. /// The parsed configuration for the tool. /// The number of successful renames. @@ -704,8 +704,8 @@ destination will be verified before being overwritten (if they seemingly match). /// Verifies the files in the destination backend. /// The verification is done by downloading the files from the destination backend and comparing them to the source files. /// - /// The source backend. - /// The destination backend. + /// The source lightweight backend manager. + /// The destination lightweight backend manager. /// The files to verify. /// The parsed configuration for the tool. /// A list of the files that failed verification.