diff --git a/Duplicati/Agent/Program.cs b/Duplicati/Agent/Program.cs index 08b41f513..df7ccdca4 100644 --- a/Duplicati/Agent/Program.cs +++ b/Duplicati/Agent/Program.cs @@ -102,7 +102,7 @@ public static class Program } [STAThread] - public static Task Main(string[] args) + public static Task MainAsync(string[] args) { Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.Agent); @@ -131,13 +131,13 @@ public static class Program new Option($"--{DataFolderManager.PORTABLE_MODE_OPTION}", description: "Use portable mode for locating the database and storing configuration", getDefaultValue: () => DataFolderManager.PORTABLE_MODE), new Option($"--{DataFolderManager.SERVER_DATAFOLDER_OPTION}", description: "The datafolder to use for locating the database and storing configuration", getDefaultValue: () => new DirectoryInfo(DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly))), }; - runcmd.Handler = CommandHandler.Create(RunAgent); + runcmd.Handler = CommandHandler.Create(RunAgentAsync); var clearCommand = new Command("clear", "Clears the agent settings") { new Option("--agent-settings-file", description: "The file to use for the agent settings", getDefaultValue: () => new FileInfo(Settings.DefaultSettingsFile)), }; - clearCommand.Handler = CommandHandler.Create(ClearAgentSettings); + clearCommand.Handler = CommandHandler.Create(ClearAgentSettingsAsync); var registerCommand = new Command("register", "Registers the agent and exits") { @@ -147,7 +147,7 @@ public static class Program new Option("--secret-provider", description: "The secret provider to use", getDefaultValue: () => null), new Option("--secret-provider-pattern", description: "The secret provider pattern", getDefaultValue: () => SecretProviderHelper.DEFAULT_PATTERN), }; - registerCommand.Handler = CommandHandler.Create(RegisterAgent); + registerCommand.Handler = CommandHandler.Create(RegisterAgentAsync); var rootcmd = new RootCommand("Duplicati Agent") { @@ -183,7 +183,7 @@ public static class Program /// /// The file to clear /// The exit code - private static Task ClearAgentSettings(FileInfo agentSettingsFile) + private static Task ClearAgentSettingsAsync(FileInfo agentSettingsFile) { if (File.Exists(agentSettingsFile.FullName)) File.Delete(agentSettingsFile.FullName); @@ -199,8 +199,8 @@ public static class Program /// The secret provider to use /// The secret provider pattern /// - private static Task RegisterAgent(string agentRegistrationUrl, FileInfo agentSettingsFile, string? agentSettingsFilePassphrase, string? secretProvider, string secretProviderPattern) - => RunAgent(new CommandLineArguments( + private static Task RegisterAgentAsync(string agentRegistrationUrl, FileInfo agentSettingsFile, string? agentSettingsFilePassphrase, string? secretProvider, string secretProviderPattern) + => RunAgentAsync(new CommandLineArguments( AgentRegistrationUrl: string.IsNullOrWhiteSpace(agentRegistrationUrl) ? GetDefaultRegistrationUrl() : agentRegistrationUrl, @@ -230,7 +230,7 @@ public static class Program /// /// The configuration from the commandline /// The exit code - private static async Task RunAgent(CommandLineArguments agentConfig) + private static async Task RunAgentAsync(CommandLineArguments agentConfig) { // Read the environment variable for the encryption key if (string.IsNullOrWhiteSpace(agentConfig.SettingsEncryptionKey)) @@ -304,7 +304,7 @@ public static class Program Log.WriteMessage(LogMessageType.Information, LogTag, "AgentStarting", "Starting agent"); - var settings = await Register(agentConfig.AgentRegistrationUrl, agentConfig.AgentSettingsFile.FullName, agentConfig.AgentSettingsFilePassphrase, cts.Token); + var settings = await RegisterAsync(agentConfig.AgentRegistrationUrl, agentConfig.AgentSettingsFile.FullName, agentConfig.AgentSettingsFilePassphrase, cts.Token); if (agentConfig.AgentRegisterOnly) { @@ -313,8 +313,8 @@ public static class Program } var t = await Task.WhenAny( - Task.Run(() => StartLocalServer(agentConfig, applicationSettings, settings, cts.Token)), - KeepRemoteConnection.Start( + Task.Run(() => StartLocalServerAsync(agentConfig, applicationSettings, settings, cts.Token)), + KeepRemoteConnection.StartAsync( settings.ServerUrl, settings.JWT, settings.CertificateUrl, @@ -322,10 +322,10 @@ public static class Program refreshSettingsBy: null, forceConnect: true, cts.Token, - OnConnect, - m => ReKey(applicationSettings, m, agentConfig), - OnControl, - OnMessage + OnConnectAsync, + m => ReKeyAsync(applicationSettings, m, agentConfig), + OnControlAsync, + OnMessageAsync ) ); @@ -335,17 +335,17 @@ public static class Program } } - private static Task> OnConnect(Dictionary metadata) - => Server.Program.DuplicatiWebserver.Provider.GetRequiredService().OnConnect(metadata); + private static Task> OnConnectAsync(Dictionary metadata) + => Server.Program.DuplicatiWebserver.Provider.GetRequiredService().OnConnectAsync(metadata); - private static Task OnControl(KeepRemoteConnection.ControlMessage message) - => Server.Program.DuplicatiWebserver.Provider.GetRequiredService().OnControl(message); + private static Task OnControlAsync(KeepRemoteConnection.ControlMessage message) + => Server.Program.DuplicatiWebserver.Provider.GetRequiredService().OnControlAsync(message); - private static Task OnMessage(KeepRemoteConnection.CommandMessage message) - => Server.Program.DuplicatiWebserver.Provider.GetRequiredService().OnMessage(message); + private static Task OnMessageAsync(KeepRemoteConnection.CommandMessage message) + => Server.Program.DuplicatiWebserver.Provider.GetRequiredService().OnMessageAsync(message); - private static Task ReKey(IApplicationSettings applicationSettings, ClaimedClientData keydata, CommandLineArguments agentConfig) + private static Task ReKeyAsync(IApplicationSettings applicationSettings, ClaimedClientData keydata, CommandLineArguments agentConfig) { // ReKey is handled here because we store the config outside of the database Log.WriteMessage(LogMessageType.Verbose, LogTag, "ReKey", "Rekeying the settings"); @@ -378,7 +378,7 @@ public static class Program /// The commandline arguments passed to the server /// The cancellation token to use for the process /// An awaitable task - private static async Task RunServer(IApplicationSettings applicationSettings, string[] args, CancellationToken cancellationToken) + private static async Task RunServerAsync(IApplicationSettings applicationSettings, string[] args, CancellationToken cancellationToken) { cancellationToken.Register(() => { @@ -417,7 +417,7 @@ public static class Program /// The passphrase for the settings file /// The cancellation token to use for the process /// The settings for the machine - private static async Task Register(string registrationUrl, string settingsFile, string? settingsPasshrase, CancellationToken cancellationToken) + private static async Task RegisterAsync(string registrationUrl, string settingsFile, string? settingsPasshrase, CancellationToken cancellationToken) { var settings = Settings.Load(settingsFile, settingsPasshrase); if (string.IsNullOrWhiteSpace(settings.JWT)) @@ -425,7 +425,7 @@ public static class Program Log.WriteMessage(LogMessageType.Information, LogTag, "ClientNoJWT", "No JWT found in settings, starting in registration mode"); using (var registration = new RegisterForRemote(registrationUrl, null, cancellationToken)) { - var registerClientData = await registration.Register(); + var registerClientData = await registration.RegisterAsync(); if (registerClientData.RegistrationData != null) { Log.WriteMessage(LogMessageType.Information, LogTag, "ClientRegistered", $"Machine registered, claim it by visiting: {registerClientData.RegistrationData.ClaimLink}"); @@ -439,7 +439,7 @@ public static class Program Log.WriteMessage(LogMessageType.Warning, LogTag, "ClientClaimLink", ex, "Failed to open the claim link in system browser"); } } - var claimedClientData = await registration.Claim(); + var claimedClientData = await registration.ClaimAsync(); Log.WriteMessage(LogMessageType.Information, LogTag, "ClientClaimed", "Machine claimed, saving JWT"); settings = settings with @@ -455,7 +455,7 @@ public static class Program // If settings are present in the claim data, apply them via the control handler if (claimedClientData.Settings != null && claimedClientData.Settings.Count > 0) - await OnControl(KeepRemoteConnection.ControlMessage.CreateSettingsControlMessage(claimedClientData.Settings)); + await OnControlAsync(KeepRemoteConnection.ControlMessage.CreateSettingsControlMessage(claimedClientData.Settings)); } } @@ -470,7 +470,7 @@ public static class Program /// The settings for the agent /// The cancellation token that stops the server /// An awaitable task - private static async Task StartLocalServer(CommandLineArguments agentConfig, IApplicationSettings applicationSettings, Settings settings, CancellationToken cancellationToken) + private static async Task StartLocalServerAsync(CommandLineArguments agentConfig, IApplicationSettings applicationSettings, Settings settings, CancellationToken cancellationToken) { // TODO: Look into pipes for Kestrel to prevent network access @@ -517,7 +517,7 @@ public static class Program applicationSettings.Origin = "Agent"; // Start the server - await RunServer(applicationSettings, args, cancellationToken); + await RunServerAsync(applicationSettings, args, cancellationToken); } } diff --git a/Duplicati/CommandLine/CLI/Commands.cs b/Duplicati/CommandLine/CLI/Commands.cs index 06b6fc8a5..96f5e5f1a 100644 --- a/Duplicati/CommandLine/CLI/Commands.cs +++ b/Duplicati/CommandLine/CLI/Commands.cs @@ -26,6 +26,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Common.IO; +using Duplicati.Library.Utility; namespace Duplicati.CommandLine { @@ -58,7 +59,14 @@ namespace Duplicati.CommandLine public bool Join(TimeSpan wait) { if (m_task != null) - return this.m_task.Wait(wait); + try + { + this.m_task.WaitAsync(wait).Await(); + } + catch + { + return false; + } return true; } @@ -74,13 +82,15 @@ namespace Duplicati.CommandLine while (!cancellationToken.IsCancellationRequested) { - float progress; - long filesprocessed; - long filesizeprocessed; - long filecount; - long filesize; - bool counting; - m_output.OperationProgress.UpdateOverall(out _, out progress, out filesprocessed, out filesizeprocessed, out filecount, out filesize, out counting); + m_output.OperationProgress.UpdateOverall( + out _, + out var progress, + out var filesprocessed, + out var filesizeprocessed, + out var filecount, + out var filesize, + out var counting); + var files = Math.Max(0, filecount - filesprocessed); var size = Math.Max(0, filesize - filesizeprocessed); @@ -119,7 +129,7 @@ namespace Duplicati.CommandLine if (m_task != null) { m_cancellationTokenSource.Cancel(); - m_task.Wait(500); + m_task.WaitAsync(TimeSpan.FromMilliseconds(500)).Await(); } } finally @@ -179,7 +189,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(backend, options, console)) { setup(i); - i.ListAffected(args, res => + i.ListAffectedAsync(args, res => { if (res.Filesets != null && res.Filesets.Any()) { @@ -233,7 +243,7 @@ namespace Duplicati.CommandLine outwriter.WriteLine(); } - }); + }).Await(); @@ -375,7 +385,7 @@ namespace Duplicati.CommandLine return 200; } - var res = i.ListFilesets(); + var res = i.ListFilesetsAsync().Await(); outwriter.WriteLine("Listing filesets:"); foreach (var e in res.Filesets) { @@ -406,7 +416,7 @@ namespace Duplicati.CommandLine return 200; } - var res = i.ListFolder(args.ToArray(), 0, 0, false); + var res = i.ListFolderAsync(args.ToArray(), 0, 0, false).Await(); outwriter.WriteLine("Folder contents:"); foreach (var e in res.Entries.Items) { @@ -433,7 +443,7 @@ namespace Duplicati.CommandLine HandleDbAccessWithoutPassphrase(backend, options); - var res = i.ListFileVersions(args.ToArray(), 0, 0); + var res = i.ListFileVersionsAsync(args.ToArray(), 0, 0).Await(); outwriter.WriteLine("File versions:"); var prevFile = string.Empty; foreach (var e in res.FileVersions.Items) @@ -466,7 +476,7 @@ namespace Duplicati.CommandLine HandleDbAccessWithoutPassphrase(backend, options); - var res = i.SearchEntries(args.ToArray(), filter, 0, 0, false); + var res = i.SearchEntriesAsync(args.ToArray(), filter, 0, 0, false).Await(); outwriter.WriteLine("File versions:"); var prevFile = string.Empty; foreach (var e in res.FileVersions.Items) @@ -535,7 +545,7 @@ namespace Duplicati.CommandLine bool controlFiles = Library.Utility.Utility.ParseBoolOption(options, "control-files"); options.Remove("control-files"); - var res = controlFiles ? i.ListControlFiles(args, filter) : i.List(args, filter); + var res = controlFiles ? i.ListControlFilesAsync(args, filter).Await() : i.ListAsync(args, filter).Await(); //If there are no files matching, and we are looking for one or more files, // try again with all-versions set @@ -551,7 +561,7 @@ namespace Duplicati.CommandLine options["all-versions"] = "true"; options.Remove("time"); options.Remove("version"); - res = i.List(args, filter); + res = i.ListAsync(args, filter).Await(); } if (res.Filesets.Any() && (res.Files == null || !res.Files.Any()) && compareFilter.Empty) @@ -621,7 +631,7 @@ namespace Duplicati.CommandLine { setup(i); args.RemoveAt(0); - var res = i.Delete(); + var res = i.DeleteAsync().Await(); if (!res.DeletedSets.Any()) { @@ -652,7 +662,7 @@ namespace Duplicati.CommandLine using (var i = new Duplicati.Library.Main.Controller(args[0], options, console)) { setup(i); - i.Repair(filter); + i.RepairAsync(filter).Await(); } return 0; @@ -678,7 +688,7 @@ namespace Duplicati.CommandLine setup(i); if (controlFiles) { - var res = i.RestoreControlFiles(args.ToArray(), filter); + var res = i.RestoreControlFilesAsync(args.ToArray(), filter).Await(); output.MessageEvent("Restore control files completed:"); foreach (var s in res.Files) outwriter.WriteLine(s); @@ -722,7 +732,7 @@ namespace Duplicati.CommandLine output.MessageEvent(string.Format(" {0} files need to be restored ({1})", files, Library.Utility.Utility.FormatSizeString(size))); }; - var res = i.Restore(args.ToArray(), filter); + var res = i.RestoreAsync(args.ToArray(), filter).Await(); string restorePath; options.TryGetValue("restore-path", out restorePath); @@ -804,7 +814,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(backend, options, output)) { setup(i); - result = i.Backup(dirs, filter); + result = i.BackupAsync(dirs, filter).Await(); } } @@ -877,7 +887,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(args[0], options, console)) { setup(i); - i.Compact(); + i.CompactAsync().Await(); } return 0; @@ -903,7 +913,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(args[0], options, console)) { setup(i); - result = i.Test(tests); + result = i.TestAsync(tests).Await(); } var totalFiles = result.Verifications.Count(); @@ -995,7 +1005,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(args[0], options, console)) { setup(i); - i.CreateLogDatabase(args[1]); + i.CreateLogDatabaseAsync(args[1]).Await(); } outwriter.WriteLine("Completed!"); @@ -1140,9 +1150,9 @@ namespace Duplicati.CommandLine { setup(i); if (args.Count == 2) - i.ListChanges(null, args[1], null, filter, handler); + i.ListChangesAsync(null, args[1], null, filter, handler).Await(); else - i.ListChanges(args.Count > 1 ? args[1] : null, args.Count > 2 ? args[2] : null, null, filter, handler); + i.ListChangesAsync(args.Count > 1 ? args[1] : null, args.Count > 2 ? args[2] : null, null, filter, handler).Await(); } return 0; @@ -1160,7 +1170,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller("dummy://", options, console)) { setup(i); - var result = i.TestFilter(args.ToArray(), filter); + var result = i.TestFilterAsync(args.ToArray(), filter).Await(); outwriter.WriteLine("Matched {0} files ({1})", result.FileCount, Duplicati.Library.Utility.Utility.FormatSizeString(result.FileSize)); } @@ -1180,7 +1190,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller("dummy://", options, console)) { setup(i); - foreach (var line in i.SystemInfo().Lines) + foreach (var line in i.SystemInfoAsync().Await().Lines) outwriter.WriteLine(line); } @@ -1219,7 +1229,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(backend, options, console)) { setup(i); - i.PurgeFiles(filter); + i.PurgeFilesAsync(filter).Await(); } return 0; @@ -1238,7 +1248,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(args[0], options, con)) { setup(i); - i.ListBrokenFiles(filter, (id, time, count, path, size) => + i.ListBrokenFilesAsync(filter, (id, time, count, path, size) => { if (previd != id) { @@ -1257,7 +1267,7 @@ namespace Duplicati.CommandLine return true; - }); + }).Await(); } return 0; @@ -1272,7 +1282,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller(args[0], options, console)) { setup(i); - i.PurgeBrokenFiles(filter); + i.PurgeBrokenFilesAsync(filter).Await(); } return 0; @@ -1290,7 +1300,7 @@ namespace Duplicati.CommandLine using (var i = new Library.Main.Controller("dummy://", options, console)) { setup(i); - foreach (var l in i.SendMail().Lines) + foreach (var l in i.SendMailAsync().Await().Lines) outwriter.WriteLine(l); } @@ -1311,7 +1321,7 @@ namespace Duplicati.CommandLine using (var controller = new Library.Main.Controller(args[0], options, console)) { setup(controller); - var res = controller.SetLocks(); + var res = controller.SetLocksAsync().Await(); if (console.FullResults) { @@ -1337,7 +1347,7 @@ namespace Duplicati.CommandLine using (var controller = new Library.Main.Controller(args[0], options, console)) { setup(controller); - var res = controller.ReadLockInfo(); + var res = controller.ReadLockInfoAsync().Await(); if (console.FullResults) { @@ -1362,7 +1372,7 @@ namespace Duplicati.CommandLine using (var controller = new Library.Main.Controller(args[0], options, console)) { setup(controller); - controller.Vacuum(); + controller.VacuumAsync().Await(); } return 0; } diff --git a/Duplicati/CommandLine/CLI/Help.cs b/Duplicati/CommandLine/CLI/Help.cs index 79404249c..9a339e5bc 100644 --- a/Duplicati/CommandLine/CLI/Help.cs +++ b/Duplicati/CommandLine/CLI/Help.cs @@ -28,6 +28,7 @@ using System.Text; using System.Threading; using Duplicati.Library.AutoUpdater; using Duplicati.Library.DynamicLoader; +using Duplicati.Library.Utility; using FilterGroup = Duplicati.Library.Utility.FilterGroup; namespace Duplicati.CommandLine @@ -266,7 +267,7 @@ namespace Duplicati.CommandLine var lines = new List(); foreach (var module in SecretProviderLoader.Keys) { - var metadata = SecretProviderLoader.GetProviderMetadata(module, CancellationToken.None).Result; + var metadata = SecretProviderLoader.GetProviderMetadata(module, CancellationToken.None).Await(); lines.Add($"- {module}: {metadata.DisplayName}{(metadata.IsSupported ? "" : " (not supported)")}"); } diff --git a/Duplicati/CommandLine/DatabaseTool/Commands/Downgrade.cs b/Duplicati/CommandLine/DatabaseTool/Commands/Downgrade.cs index f2a42791b..cb60a3eed 100644 --- a/Duplicati/CommandLine/DatabaseTool/Commands/Downgrade.cs +++ b/Duplicati/CommandLine/DatabaseTool/Commands/Downgrade.cs @@ -51,7 +51,7 @@ public static class Downgrade } .WithHandler(CommandHandler.Create((databases, serverdatafolder, serverversion, localversion, nobackups, includeuntrackeddatabases) => { - databases = Helper.FindAllDatabases(databases, serverdatafolder.FullName, includeuntrackeddatabases).Await(); + databases = Helper.FindAllDatabasesAsync(databases, serverdatafolder.FullName, includeuntrackeddatabases).Await(); if (databases.Length == 0) { Console.WriteLine("No databases found to downgrade"); @@ -101,7 +101,7 @@ public static class Downgrade bool isserverdb; try { - (version, isserverdb) = Helper.ExamineDatabase(db).Await(); + (version, isserverdb) = Helper.ExamineDatabaseAsync(db).Await(); } catch (Exception ex) { @@ -110,7 +110,7 @@ public static class Downgrade } Console.WriteLine($"Database {db} is version {version} and is a {(isserverdb ? "server" : "local")} database"); - ApplyDowngrade(db, version, + ApplyDowngradeAsync(db, version, isserverdb ? serverversion : localversion, isserverdb ? serverVersions : localVersions, nobackups) @@ -135,7 +135,7 @@ public static class Downgrade /// The downgrade scripts /// Flag to disable backups /// A task that completes when the downgrade is done. - private static async Task ApplyDowngrade(string db, int dbversion, int targetversion, IEnumerable scripts, bool nobackups) + private static async Task ApplyDowngradeAsync(string db, int dbversion, int targetversion, IEnumerable scripts, bool nobackups) { if (dbversion > targetversion) { diff --git a/Duplicati/CommandLine/DatabaseTool/Commands/Upgrade.cs b/Duplicati/CommandLine/DatabaseTool/Commands/Upgrade.cs index 9bfc3e5b4..3c46b574b 100644 --- a/Duplicati/CommandLine/DatabaseTool/Commands/Upgrade.cs +++ b/Duplicati/CommandLine/DatabaseTool/Commands/Upgrade.cs @@ -51,7 +51,7 @@ public static class Upgrade } .WithHandler(CommandHandler.Create((databases, serverdatafolder, serverversion, localversion, nobackups, includeuntrackeddatabases) => { - databases = Helper.FindAllDatabases(databases, serverdatafolder.FullName, includeuntrackeddatabases).Await(); + databases = Helper.FindAllDatabasesAsync(databases, serverdatafolder.FullName, includeuntrackeddatabases).Await(); if (databases.Length == 0) { Console.WriteLine("No databases found to upgrade"); @@ -84,7 +84,7 @@ public static class Upgrade bool isserverdb; try { - (version, isserverdb) = Helper.ExamineDatabase(db).Await(); + (version, isserverdb) = Helper.ExamineDatabaseAsync(db).Await(); } catch (Exception ex) { @@ -93,7 +93,7 @@ public static class Upgrade } Console.WriteLine($"Database {db} is version {version} and is a {(isserverdb ? "server" : "local")} database"); - ApplyUpgrade(db, version, + ApplyUpgradeAsync(db, version, isserverdb ? serverversion : localversion, isserverdb ? serverVersions : localVersions, nobackups) @@ -148,7 +148,7 @@ public static class Upgrade /// The upgrade scripts /// Flag to disable backups /// A task that completes when the upgrade is done. - private static async Task ApplyUpgrade(string db, int dbversion, int targetversion, IEnumerable scripts, bool nobackups) + private static async Task ApplyUpgradeAsync(string db, int dbversion, int targetversion, IEnumerable scripts, bool nobackups) { if (targetversion > dbversion) { diff --git a/Duplicati/CommandLine/DatabaseTool/Helper.cs b/Duplicati/CommandLine/DatabaseTool/Helper.cs index 022f1cdd6..340af22d1 100644 --- a/Duplicati/CommandLine/DatabaseTool/Helper.cs +++ b/Duplicati/CommandLine/DatabaseTool/Helper.cs @@ -42,7 +42,7 @@ public static class Helper /// The folder to scan /// Whether to scan for extra databases /// A task that when awaited contains the list of databases. - public static async Task FindAllDatabases(string[]? databases, string datafolder, bool scanExtra) + public static async Task FindAllDatabasesAsync(string[]? databases, string datafolder, bool scanExtra) { databases ??= []; if (databases.Length != 0) @@ -91,7 +91,7 @@ public static class Helper /// /// The path to the database /// A task that when awaited contains a tuple with the version and whether it is a server database - public static async Task<(int Version, bool isserver)> ExamineDatabase(string db) + public static async Task<(int Version, bool isserver)> ExamineDatabaseAsync(string db) { await using var con = await SQLiteLoader.LoadConnectionAsync(db) .ConfigureAwait(false); diff --git a/Duplicati/CommandLine/DatabaseTool/Program.cs b/Duplicati/CommandLine/DatabaseTool/Program.cs index bdd57d0b6..887a4f55f 100644 --- a/Duplicati/CommandLine/DatabaseTool/Program.cs +++ b/Duplicati/CommandLine/DatabaseTool/Program.cs @@ -36,7 +36,7 @@ public static class Program /// /// The arguments /// The return code - public static Task Main(string[] args) + public static Task MainAsync(string[] args) { Library.AutoUpdater.PreloadSettingsLoader.ConfigurePreloadSettings(ref args, Library.AutoUpdater.PackageHelper.NamedExecutable.ServerUtil); diff --git a/Duplicati/CommandLine/RecoveryTool/Recompress.cs b/Duplicati/CommandLine/RecoveryTool/Recompress.cs index bca6dff2b..170149510 100644 --- a/Duplicati/CommandLine/RecoveryTool/Recompress.cs +++ b/Duplicati/CommandLine/RecoveryTool/Recompress.cs @@ -342,7 +342,7 @@ namespace Duplicati.CommandLine.RecoveryTool if (string.IsNullOrWhiteSpace(dbpath) || !File.Exists(dbpath)) return; - Duplicati.Library.Main.Utility.UpdateOptionsFromDatabase(dbpath, options, "RecoveryTool Recompress", CancellationToken.None).Await(); + Duplicati.Library.Main.Utility.UpdateOptionsFromDatabaseAsync(dbpath, options, "RecoveryTool Recompress", CancellationToken.None).Await(); } } } diff --git a/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs b/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs index 6f54e71fc..53c9f06c4 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.DatabaseCollector.cs @@ -118,7 +118,7 @@ partial class BackendManager /// The database to write pending messages to. /// A cancellation token to cancel the operation. /// A task that when awaited contains whether any messages were flushed. - public async Task FlushPendingMessages(LocalDatabase db, CancellationToken cancellationToken) + public async Task FlushPendingMessagesAsync(LocalDatabase db, CancellationToken cancellationToken) { List entries; lock (m_dbqueuelock) @@ -136,19 +136,19 @@ partial class BackendManager // As we replace the list, we can now freely access the elements without locking foreach (var e in entries) if (e is RemoteOperationLogEntry operation) - await db.LogRemoteOperation(operation.Action, operation.File, operation.Result, cancellationToken) + await db.LogRemoteOperationAsync(operation.Action, operation.File, operation.Result, cancellationToken) .ConfigureAwait(false); else if (e is RemoteVolumeUpdate update && update.State == RemoteVolumeState.Deleted) { - await db.UpdateRemoteVolume(update.Remotename, RemoteVolumeState.Deleted, update.Size, update.Hash, true, TimeSpan.FromHours(2), null, cancellationToken) + await db.UpdateRemoteVolumeAsync(update.Remotename, RemoteVolumeState.Deleted, update.Size, update.Hash, true, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); volsRemoved.Add(update.Remotename); } else if (e is RemoteVolumeUpdate dbUpdate) - await db.UpdateRemoteVolume(dbUpdate.Remotename, dbUpdate.State, dbUpdate.Size, dbUpdate.Hash, cancellationToken) + await db.UpdateRemoteVolumeAsync(dbUpdate.Remotename, dbUpdate.State, dbUpdate.Size, dbUpdate.Hash, cancellationToken) .ConfigureAwait(false); else if (e is RenameRemoteVolume rename) - await db.RenameRemoteFile(rename.Oldname, rename.Newname, cancellationToken) + await db.RenameRemoteFileAsync(rename.Oldname, rename.Newname, cancellationToken) .ConfigureAwait(false); else if (e != null) Log.WriteErrorMessage(LOGTAG, "InvalidQueueElement", null, "Queue had element of type: {0}, {1}", e.GetType(), e); @@ -156,7 +156,7 @@ partial class BackendManager // Finally remove volumes from DB. if (volsRemoved.Count > 0) { - await db.RemoveRemoteVolumes(volsRemoved, cancellationToken).ConfigureAwait(false); + await db.RemoveRemoteVolumesAsync(volsRemoved, cancellationToken).ConfigureAwait(false); // This commit is to mimic the pre Microsoft.Data.Sqlite backend // behavior, in which RemoveRemoteVolumes started a new // transaction, if none was passed down. diff --git a/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs index 8460b5851..1bc91bd59 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.GetOperation.cs @@ -140,7 +140,7 @@ partial class BackendManager { await streamingBackend.GetAsync(RemoteFilename, pgs, cancelToken).ConfigureAwait(false); } - ss.Flush(); + await ss.FlushAsync(); retDownloadSize = ss.TotalBytesWritten; retHashcode = Convert.ToBase64String(hs.GetFinalHash()); } diff --git a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs index 8cf6d49d3..4592a3502 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.Handler.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.Handler.cs @@ -153,7 +153,7 @@ partial class BackendManager async self => { using var handler = new Handler(backendUrl, context); - await handler.Run(self.requestChannel); + await handler.RunAsync(self.requestChannel); }); /// @@ -198,7 +198,7 @@ partial class BackendManager /// /// The list of tasks to reclaim /// An awaitable task - private static async Task ReclaimCompletedTasks(List tasks) + private static async Task ReclaimCompletedTasksAsync(List tasks) { for (int i = tasks.Count - 1; i >= 0; i--) { @@ -216,10 +216,10 @@ partial class BackendManager /// Reclaims completed tasks from uploads and downloads /// /// An awaitable task - private async Task ReclaimCompletedTasks() + private async Task ReclaimCompletedTasksAsync() { - await ReclaimCompletedTasks(activeUploads); - await ReclaimCompletedTasks(activeDownloads); + await ReclaimCompletedTasksAsync(activeUploads); + await ReclaimCompletedTasksAsync(activeDownloads); } /// @@ -228,12 +228,12 @@ partial class BackendManager /// The maximum number of active tasks /// The list of active tasks /// An awaitable task - private static async Task EnsureAtMostNActiveTasks(int n, List tasks) + private static async Task EnsureAtMostNActiveTasksAsync(int n, List tasks) { while (tasks.Count >= n) { await Task.WhenAny(tasks).ConfigureAwait(false); - await ReclaimCompletedTasks(tasks).ConfigureAwait(false); + await ReclaimCompletedTasksAsync(tasks).ConfigureAwait(false); } } @@ -243,11 +243,11 @@ partial class BackendManager /// The number of active uploads /// The number of active downloads /// An awaitable task - private async Task EnsureAtMostNActiveTasks(int uploads, int downloads) + private async Task EnsureAtMostNActiveTasksAsync(int uploads, int downloads) { context.ProgressHandler?.SetIsBlocking(true); - await EnsureAtMostNActiveTasks(uploads, activeUploads).ConfigureAwait(false); - await EnsureAtMostNActiveTasks(downloads, activeDownloads).ConfigureAwait(false); + await EnsureAtMostNActiveTasksAsync(uploads, activeUploads).ConfigureAwait(false); + await EnsureAtMostNActiveTasksAsync(downloads, activeDownloads).ConfigureAwait(false); context.ProgressHandler?.SetIsBlocking(false); } @@ -256,7 +256,7 @@ partial class BackendManager /// /// The channel for pending operations /// An awaitable task - private async Task Run(IReadChannel requestChannel) + private async Task RunAsync(IReadChannel requestChannel) { using var tcs = new CancellationTokenSource(); try @@ -269,33 +269,33 @@ partial class BackendManager try { // Clean up completed uploads, if any - await ReclaimCompletedTasks().ConfigureAwait(false); + await ReclaimCompletedTasksAsync().ConfigureAwait(false); // Allow PUT operations to be queued, if requested if (op is PutOperation putOp && !putOp.WaitForComplete) { // Wait for any active downloads to complete before starting an upload - await EnsureAtMostNActiveTasks(maxParallelUploads, 1).ConfigureAwait(false); + await EnsureAtMostNActiveTasksAsync(maxParallelUploads, 1).ConfigureAwait(false); // Operation is accepted into queue, so we can signal completion putOp.SetComplete(true); - activeUploads.Add(ExecuteWithRetry(putOp, tcs.Token)); + activeUploads.Add(ExecuteWithRetryAsync(putOp, tcs.Token)); } else if (op is GetOperation getOp) { // Wait for any active uploads to complete before starting a download - await EnsureAtMostNActiveTasks(1, maxParallelDownloads).ConfigureAwait(false); + await EnsureAtMostNActiveTasksAsync(1, maxParallelDownloads).ConfigureAwait(false); // Operation is accepted into queue, so we can signal completion - activeDownloads.Add(ExecuteWithRetry(getOp, tcs.Token)); + activeDownloads.Add(ExecuteWithRetryAsync(getOp, tcs.Token)); } else { // Wait for all of the active uploads and downloads to complete - await EnsureAtMostNActiveTasks(1, 1).ConfigureAwait(false); + await EnsureAtMostNActiveTasksAsync(1, 1).ConfigureAwait(false); // Execute the operation - await ExecuteWithRetry(op, tcs.Token).ConfigureAwait(false); + await ExecuteWithRetryAsync(op, tcs.Token).ConfigureAwait(false); } } catch (Exception ex) @@ -310,10 +310,10 @@ partial class BackendManager finally { // Terminate any active uploads and downloads. Exceptions thrown by the downloads should be captured by the callers. - tcs.Cancel(); + await tcs.CancelAsync(); - await WaitForPendingItems("upload", activeUploads).ConfigureAwait(false); - await WaitForPendingItems("download", activeDownloads).ConfigureAwait(false); + await WaitForPendingItemsAsync("upload", activeUploads).ConfigureAwait(false); + await WaitForPendingItemsAsync("download", activeDownloads).ConfigureAwait(false); // Dispose of any remaining backends while (backendPool.TryDequeue(out var backend)) @@ -328,7 +328,7 @@ partial class BackendManager /// /// /// - private static async Task WaitForPendingItems(string description, List tasks) + private static async Task WaitForPendingItemsAsync(string description, List tasks) { // If we have tasks that have completed successfully, remove them from the list // as they should not trigger any warnings @@ -370,7 +370,7 @@ partial class BackendManager /// Tries to create a folder, handling errors /// /// true if the folder was created, false otherwise - private async Task TryCreateFolder() + private async Task TryCreateFolderAsync() { using var backend = CreateBackend(); try @@ -395,7 +395,7 @@ partial class BackendManager /// The operation to execute /// The cancellation token /// An awaitable task - private async Task ExecuteWithRetry(PendingOperationBase op, CancellationToken cancellationToken) + private async Task ExecuteWithRetryAsync(PendingOperationBase op, CancellationToken cancellationToken) { // Once in this method, we MUST set the op result, // or the program will hang waiting for the operation to complete @@ -408,7 +408,7 @@ partial class BackendManager try { // Happy case is execute and return - await Execute(op, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(op, cancellationToken).ConfigureAwait(false); return; } catch (Exception ex) @@ -438,7 +438,7 @@ partial class BackendManager using (var backend = CreateBackend()) foreach (var name in await backend.Backend.GetDNSNamesAsync(context.TaskReader.TransferToken).ConfigureAwait(false) ?? []) if (!string.IsNullOrWhiteSpace(name)) - System.Net.Dns.GetHostEntry(name); + await System.Net.Dns.GetHostEntryAsync(name); } catch { @@ -453,7 +453,7 @@ partial class BackendManager // Check if this was a folder missing exception and we are allowed to autocreate folders if (!(anyDownloaded || anyUploaded) && context.Options.AutocreateFolders && Library.Utility.ExceptionExtensions.FlattenException(ex).Any(x => x is FolderMissingException)) { - if (await TryCreateFolder().ConfigureAwait(false)) + if (await TryCreateFolderAsync().ConfigureAwait(false)) recovered = true; } @@ -466,7 +466,7 @@ partial class BackendManager } } - if (!await context.TaskReader.ProgressRendevouz()) + if (!await context.TaskReader.ProgressRendevouzAsync()) { op.SetCancelled(); return; @@ -495,37 +495,37 @@ partial class BackendManager /// The operation to execute /// The cancellation token /// An awaitable task - private async Task Execute(PendingOperationBase op, CancellationToken cancellationToken) + private async Task ExecuteAsync(PendingOperationBase op, CancellationToken cancellationToken) { - await context.TaskReader.ProgressRendevouz().ConfigureAwait(false); + await context.TaskReader.ProgressRendevouzAsync().ConfigureAwait(false); using (new Logging.Timer(LOGTAG, $"RemoteOperation{op.Operation}", $"RemoteOperation{op.Operation}")) switch (op) { case PutOperation putOp: - await Execute(putOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(putOp, cancellationToken).ConfigureAwait(false); anyUploaded = true; return; case GetOperation getOp: - await Execute(getOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(getOp, cancellationToken).ConfigureAwait(false); anyDownloaded = true; return; case DeleteOperation deleteOp: - await Execute(deleteOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(deleteOp, cancellationToken).ConfigureAwait(false); return; case ListOperation listOp: - await Execute(listOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(listOp, cancellationToken).ConfigureAwait(false); return; case QuotaInfoOperation quotaOp: - await Execute(quotaOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(quotaOp, cancellationToken).ConfigureAwait(false); return; case WaitForEmptyOperation waitOp: waitOp.SetComplete(true); return; case SetObjectLockOperation setLockOp: - await Execute(setLockOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(setLockOp, cancellationToken).ConfigureAwait(false); return; case GetObjectLockOperation getLockOp: - await Execute(getLockOp, cancellationToken).ConfigureAwait(false); + await ExecuteAsync(getLockOp, cancellationToken).ConfigureAwait(false); return; default: throw new NotImplementedException($"Operation type {op.GetType()} is not supported"); @@ -539,7 +539,7 @@ partial class BackendManager /// The operation to execute /// The cancellation token /// An awaitable task - private async Task Execute(PendingOperation op, CancellationToken cancellationToken) + private async Task ExecuteAsync(PendingOperation op, CancellationToken cancellationToken) { using var backend = CreateBackend(); using var token = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, op.CancelToken, context.TaskReader.TransferToken); diff --git a/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs index 9efd7830b..5777af238 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.PendingOperation.cs @@ -106,7 +106,7 @@ partial class BackendManager /// /// The task that is completed when the operation is done /// - public Task GetResult() => taskCompleteSignal.Task; + public Task GetResultAsync() => taskCompleteSignal.Task; /// /// Sets the operation as complete diff --git a/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs b/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs index 2511ed75a..4c259b41d 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.ProgressHandler.cs @@ -20,7 +20,7 @@ partial class BackendManager public void HandleProgress(long pg, string filename) { // This pauses and throws on cancellation, but ignores stop - TaskReader.TransferRendevouz().Await(); + TaskReader.TransferRendevouzAsync().Await(); Stats.BackendProgressUpdater.UpdateProgress(filename, pg); } diff --git a/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs b/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs index 6f1e19017..9c2f968b5 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.PutOperation.cs @@ -251,7 +251,7 @@ partial class BackendManager // Flag for next attempt, if any operationState = OperationState.Uploading; - await PerformUpload(backend, hash, size, cancelToken).ConfigureAwait(false); + await PerformUploadAsync(backend, hash, size, cancelToken).ConfigureAwait(false); operationState = OperationState.Uploaded; @@ -278,7 +278,7 @@ partial class BackendManager /// The size of the file /// The cancellation token /// An awaitable task - private async Task PerformUpload(IBackend backend, string hash, long size, CancellationToken cancelToken) + private async Task PerformUploadAsync(IBackend backend, string hash, long size, CancellationToken cancelToken) { Context.Database.LogRemoteOperation("put", RemoteFilename, JsonConvert.SerializeObject(new { Size = size, Hash = hash })); Context.Statwriter.SendEvent(BackendActionType.Put, BackendEventType.Started, RemoteFilename, size); diff --git a/Duplicati/Library/Main/Backend/BackendManager.cs b/Duplicati/Library/Main/Backend/BackendManager.cs index cb626b5b4..f9eb5c17a 100644 --- a/Duplicati/Library/Main/Backend/BackendManager.cs +++ b/Duplicati/Library/Main/Backend/BackendManager.cs @@ -112,7 +112,7 @@ internal partial class BackendManager : IBackendManager /// /// The operation to queue /// An awaitable task - private async Task QueueTask(PendingOperationBase op) + private async Task QueueTaskAsync(PendingOperationBase op) { if (queueRunner.IsCompleted) { @@ -173,8 +173,8 @@ internal partial class BackendManager : IBackendManager public async Task DeleteAsync(string remotename, long size, bool waitForComplete, CancellationToken cancelToken) { var op = new DeleteOperation(remotename, size, context, waitForComplete, cancelToken); - await QueueTask(op).ConfigureAwait(false); - await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + await op.GetResultAsync().ConfigureAwait(false); } /// @@ -187,8 +187,8 @@ internal partial class BackendManager : IBackendManager public async Task SetObjectLockUntilAsync(string remotename, DateTime lockUntilUtc, CancellationToken cancelToken) { var op = new SetObjectLockOperation(remotename, lockUntilUtc, context, cancelToken); - await QueueTask(op).ConfigureAwait(false); - await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + await op.GetResultAsync().ConfigureAwait(false); } /// @@ -200,8 +200,8 @@ internal partial class BackendManager : IBackendManager public async Task GetObjectLockUntilAsync(string remotename, CancellationToken cancelToken) { var op = new GetObjectLockOperation(remotename, context, cancelToken); - await QueueTask(op).ConfigureAwait(false); - return await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + return await op.GetResultAsync().ConfigureAwait(false); } /// @@ -219,8 +219,8 @@ internal partial class BackendManager : IBackendManager Hash = hash, Decrypt = true }; - await QueueTask(op).ConfigureAwait(false); - (var file, var _, var _) = await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + (var file, var _, var _) = await op.GetResultAsync().ConfigureAwait(false); return file; } @@ -239,8 +239,8 @@ internal partial class BackendManager : IBackendManager Hash = hash, Decrypt = false }; - await QueueTask(op).ConfigureAwait(false); - (var file, var _, var _) = await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + (var file, var _, var _) = await op.GetResultAsync().ConfigureAwait(false); return file; } @@ -252,8 +252,8 @@ internal partial class BackendManager : IBackendManager public async Task GetQuotaInfoAsync(CancellationToken cancelToken) { var op = new QuotaInfoOperation(context, cancelToken); - await QueueTask(op).ConfigureAwait(false); - return await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + return await op.GetResultAsync().ConfigureAwait(false); } /// @@ -271,8 +271,8 @@ internal partial class BackendManager : IBackendManager Hash = hash, Decrypt = true }; - await QueueTask(op).ConfigureAwait(false); - (var file, var downloadHash, var downloadSize) = await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + (var file, var downloadHash, var downloadSize) = await op.GetResultAsync().ConfigureAwait(false); return (file, downloadHash, downloadSize); } @@ -284,8 +284,8 @@ internal partial class BackendManager : IBackendManager public async Task> ListAsync(CancellationToken cancelToken) { var op = new ListOperation(context, cancelToken); - await QueueTask(op).ConfigureAwait(false); - return await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + return await op.GetResultAsync().ConfigureAwait(false); } /// @@ -314,8 +314,8 @@ internal partial class BackendManager : IBackendManager // Prepare encryption op.StartEncryptionAndHashing(); - await QueueTask(op).ConfigureAwait(false); - await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + await op.GetResultAsync().ConfigureAwait(false); } /// @@ -339,8 +339,8 @@ internal partial class BackendManager : IBackendManager // Sets the task as already completed op.StartEncryptionAndHashing(); - await QueueTask(op).ConfigureAwait(false); - await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + await op.GetResultAsync().ConfigureAwait(false); } /// @@ -351,7 +351,7 @@ internal partial class BackendManager : IBackendManager /// A task that completes when the messages are flushed. public async Task FlushPendingMessagesAsync(LocalDatabase database, CancellationToken cancellationToken) { - await context.Database.FlushPendingMessages(database, cancellationToken).ConfigureAwait(false); + await context.Database.FlushPendingMessagesAsync(database, cancellationToken).ConfigureAwait(false); } /// @@ -362,8 +362,8 @@ internal partial class BackendManager : IBackendManager public async Task WaitForEmptyAsync(CancellationToken cancellationToken) { var op = new WaitForEmptyOperation(context, cancellationToken); - await QueueTask(op).ConfigureAwait(false); - await op.GetResult().ConfigureAwait(false); + await QueueTaskAsync(op).ConfigureAwait(false); + await op.GetResultAsync().ConfigureAwait(false); } /// @@ -385,7 +385,7 @@ internal partial class BackendManager : IBackendManager /// /// The database to write pending messages to. /// A task that completes when the runner is stopped and messages are flushed. - public async Task StopRunnerAndFlushMessages(LocalDatabase database) + public async Task StopRunnerAndFlushMessagesAsync(LocalDatabase database) { await requestChannel.RetireAsync().ConfigureAwait(false); await FlushPendingMessagesAsync(database, CancellationToken.None).ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs b/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs index 27048958c..06be6d483 100644 --- a/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs +++ b/Duplicati/Library/Main/Backend/LightWeightBackendManager.cs @@ -73,7 +73,7 @@ namespace Duplicati.Library.Main.Backend /// A task representing the asynchronous delete operation. public Task DeleteAsync(string remotename, CancellationToken token) { - return RetryWithDelay( + return RetryWithDelayAsync( $"Delete {remotename}", () => _streamingBackend!.DeleteAsync(remotename, token), null, @@ -125,7 +125,7 @@ namespace Duplicati.Library.Main.Backend /// A task representing the asynchronous get operation. public Task GetAsync(string remotename, Stream stream, CancellationToken token) { - return RetryWithDelay( + return RetryWithDelayAsync( $"Get {remotename}", async () => { @@ -231,7 +231,7 @@ namespace Duplicati.Library.Main.Backend // crashed. Current "workaround" is to build the entire list before // returning it. List entries = []; - await RetryWithDelay("List", async () => + await RetryWithDelayAsync("List", async () => { entries = await _streamingBackend!.ListAsync(token).ToListAsync().ConfigureAwait(false); }, @@ -255,7 +255,7 @@ namespace Duplicati.Library.Main.Backend /// A task representing the asynchronous put operation. public Task PutAsync(string remotename, Stream stream, CancellationToken token, string? md5 = null, string? sha256 = null, long? length = null) { - return RetryWithDelay( + return RetryWithDelayAsync( $"Put {remotename}", async () => { @@ -310,7 +310,7 @@ namespace Duplicati.Library.Main.Backend return _backend switch { IStreamingBackend sb => - RetryWithDelay( + RetryWithDelayAsync( $"Rename {oldname} to {newname}", async () => { @@ -328,7 +328,7 @@ namespace Duplicati.Library.Main.Backend token ), IRenameEnabledBackend ireb => - RetryWithDelay( + RetryWithDelayAsync( $"Rename {oldname} to {newname}", async () => { @@ -349,7 +349,7 @@ namespace Duplicati.Library.Main.Backend /// This method resets the download and upload flags and resets the retry delay to its initial value. /// /// A task representing the asynchronous reset operation. - public async Task Reset() + public async Task ResetAsync() { _anyDownloaded = false; _anyUploaded = false; @@ -370,7 +370,7 @@ namespace Duplicati.Library.Main.Backend /// 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) + private async Task RetryWithDelayAsync(string operationName, Func action, Stream? stream, bool resetStream, CancellationToken token) { int instantiations = 0; _currentRetryDelay = _retryDelay; // Reset the current retry delay to the initial value @@ -401,7 +401,7 @@ namespace Duplicati.Library.Main.Backend } // Try to see if we can recover from the error. - await TryRecoverFromException(ex, token).ConfigureAwait(false); + await TryRecoverFromExceptionAsync(ex, token).ConfigureAwait(false); } } while (instantiations < _maxRetries); @@ -414,10 +414,10 @@ namespace Duplicati.Library.Main.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. - public async Task TryCreateFolder(CancellationToken token) + public async Task TryCreateFolderAsync(CancellationToken token) { bool created = false; - await RetryWithDelay("CreateFolder", async () => + await RetryWithDelayAsync("CreateFolder", async () => { try { @@ -445,7 +445,7 @@ namespace Duplicati.Library.Main.Backend /// 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) + private async Task TryRecoverFromExceptionAsync(Exception ex, CancellationToken token) { // Copied from Duplicati.Library.Main.Backend.BackendManager.Handler. @@ -465,7 +465,7 @@ namespace Duplicati.Library.Main.Backend foreach (var name in await _backend!.GetDNSNamesAsync(token).ConfigureAwait(false) ?? []) if (!string.IsNullOrWhiteSpace(name)) - System.Net.Dns.GetHostEntry(name); + await System.Net.Dns.GetHostEntryAsync(name); } catch { } } @@ -475,7 +475,7 @@ namespace Duplicati.Library.Main.Backend // Check if this was a folder missing exception and we are allowed to autocreate folders if (!(_anyDownloaded || _anyUploaded) && _autoCreateFolders && ExceptionExtensions.FlattenException(ex).Any(x => x is FolderMissingException)) { - if (await TryCreateFolder(token).ConfigureAwait(false)) + if (await TryCreateFolderAsync(token).ConfigureAwait(false)) recovered = true; } diff --git a/Duplicati/Library/Main/Controller.cs b/Duplicati/Library/Main/Controller.cs index d7f0b5979..ee261c947 100644 --- a/Duplicati/Library/Main/Controller.cs +++ b/Duplicati/Library/Main/Controller.cs @@ -34,7 +34,7 @@ using Duplicati.Library.Backends; namespace Duplicati.Library.Main { - public class Controller : IDisposable + public class Controller : IDisposable, IController { /// /// The tag used for logging @@ -96,28 +96,25 @@ namespace Duplicati.Library.Main m_messageSink = messageSink; } - /// - /// Appends another message sink to the controller - /// - /// The sink to use. - public void AppendSink(IMessageSink sink) + /// + public Task AppendSinkAsync(IMessageSink sink) { if (this.m_messageSink is MultiMessageSink messageSink) messageSink.Append(sink); else m_messageSink = new MultiMessageSink(m_messageSink, sink); + return Task.CompletedTask; } - /// - /// Sets a secret provider to use for all operations - /// - /// The secret provider to use - public void SetSecretProvider(ISecretProvider secretProvider) + /// + public Task SetSecretProviderAsync(ISecretProvider secretProvider) { SecretProvider = secretProvider; + return Task.CompletedTask; } - public IBackupResults Backup(string[] inputsources, IFilter filter = null) + /// + public async Task BackupAsync(string[] inputsources, IFilter filter = null) { UsageReporter.Reporter.Report("USE_BACKEND", new Library.Utility.Uri(m_backendUrl).Scheme); UsageReporter.Reporter.Report("USE_COMPRESSION", m_options.CompressionModule); @@ -133,7 +130,7 @@ namespace Duplicati.Library.Main CheckAutoCompactInterval(); CheckAutoVacuumInterval(); - return RunAction(new BackupResults(), ref inputsources, ref filter, async (result, backendManager) => + return await RunActionAsync(new BackupResults(), inputsources, filter, async (result, backendManager) => { using (var h = new Operation.BackupHandler(m_options, result)) @@ -147,12 +144,13 @@ namespace Duplicati.Library.Main using (var h = new Operation.RemoteSynchronizationHandler(m_backendUrl, m_options, result)) await h.RunAsync() .ConfigureAwait(false); - }); + }).ConfigureAwait(false); } - public IRestoreResults Restore(string[] paths, IFilter filter = null) + /// + public async Task RestoreAsync(string[] paths, IFilter filter = null) { - return RunAction(new RestoreResults(), ref paths, ref filter, async (result, backendManager) => + return await RunActionAsync(new RestoreResults(), paths, filter, async (result, backendManager) => { using var restoreDestination = (m_options.Restorepath ?? "").StartsWith("@") @@ -184,120 +182,101 @@ namespace Duplicati.Library.Main UsageReporter.Reporter.Report("RESTORE_FILECOUNT", result.RestoredFiles); UsageReporter.Reporter.Report("RESTORE_FILESIZE", result.SizeOfRestoredFiles); UsageReporter.Reporter.Report("RESTORE_DURATION", (long)result.Duration.TotalSeconds); - }); + }).ConfigureAwait(false); } - public IRestoreControlFilesResults RestoreControlFiles(IEnumerable files = null, IFilter filter = null) + /// + public async Task RestoreControlFilesAsync(IEnumerable files = null, IFilter filter = null) { - return RunAction(new RestoreControlFilesResults(), ref filter, (result, backendManager) => + return await RunActionAsync(new RestoreControlFilesResults(), filter, (result, backendManager) => new Operation.RestoreControlFilesHandler(m_options, result) .RunAsync(files, backendManager, filter) - ); + ).ConfigureAwait(false); } - public IDeleteResults Delete() + /// + public async Task DeleteAsync() { - return RunAction(new DeleteResults(), (result, backendManager) => + return await RunActionAsync(new DeleteResults(), (result, backendManager) => new Operation.DeleteHandler(m_options, result) .RunAsync(backendManager) - ); + ).ConfigureAwait(false); } - public IRepairResults Repair(IFilter filter = null) + /// + public async Task RepairAsync(IFilter filter = null) { - return RunAction(new RepairResults(), ref filter, (result, backendManager) => + return await RunActionAsync(new RepairResults(), filter, (result, backendManager) => new Operation.RepairHandler(m_options, result) .RunAsync(backendManager, filter) - ); + ).ConfigureAwait(false); } - /// - /// Lists all filesets in the remote store or local database - /// - /// The fileset results - public IListFilesetResults ListFilesets() - => RunAction(new ListFilesetResults(), (result, backendManager) => + /// + public async Task ListFilesetsAsync() + => await RunActionAsync(new ListFilesetResults(), (result, backendManager) => Operation.ListFilesetsHandler.RunAsync(m_options, result, backendManager) - ); + ).ConfigureAwait(false); - /// - /// List the contents of one or more folders in a backup fileset - /// - /// The folders to list - /// The offset to start listing from - /// The maximum number of entries to list - /// Whether to include extended data - /// >The folder contents - public IListFolderResults ListFolder(string[] folders, long offset, long limit, bool extendedData) - => RunAction(new ListFolderResults(), (result, _) => + /// + public async Task ListFolderAsync(string[] folders, long offset, long limit, bool extendedData) + => await RunActionAsync(new ListFolderResults(), (result, _) => Operation.ListFolderHandler.RunAsync(m_options, result, folders, offset, limit, extendedData) - ); + ).ConfigureAwait(false); - /// - /// Lists all versions of a file or folder in the backup fileset - /// - /// The paths to list versions for - /// The offset to start listing from - /// The maximum number of results to return - /// >The file versions - public IListFileVersionsResults ListFileVersions(string[] files, long offset, long limit) - => RunAction(new ListFileVersionsResults(), (result, backendManager) => + /// + public async Task ListFileVersionsAsync(string[] files, long offset, long limit) + => await RunActionAsync(new ListFileVersionsResults(), (result, backendManager) => Operation.ListFileVersionsHandler.RunAsync(m_options, result, files, offset, limit) - ); + ).ConfigureAwait(false); - /// - /// Searches for entries in the backup fileset - /// - /// The paths to search in - /// The filter to use for searching - /// The offset to start searching from - /// The maximum number of results to return - /// Whether to include extended data - /// >The search results - public ISearchFilesResults SearchEntries(string[] pathprefixes, IFilter filter, long offset, long limit, bool extendedData) - => RunAction(new SearchFilesResults(), ref filter, (result, backendManager) => + /// + public async Task SearchEntriesAsync(string[] pathprefixes, IFilter filter, long offset, long limit, bool extendedData) + => await RunActionAsync(new SearchFilesResults(), filter, (result, backendManager) => Operation.SearchEntriesHandler.RunAsync(m_options, result, pathprefixes, filter, offset, limit, extendedData) - ); + ).ConfigureAwait(false); - public IListResults List() - { - return List(null, null); - } + /// + public Task ListAsync() + => ListAsync(null, null); - public IListResults List(string filterstring) - { - return List(filterstring == null ? null : new string[] { filterstring }, null); - } + /// + public Task ListAsync(string filterstring) + => ListAsync(filterstring == null ? null : new string[] { filterstring }, null); - public IListResults List(IEnumerable filterstrings, IFilter filter) + /// + public async Task ListAsync(IEnumerable filterstrings, IFilter filter) { - return RunAction(new ListResults(), ref filter, (result, backendManager) => + return await RunActionAsync(new ListResults(), filter, (result, backendManager) => new Operation.ListFilesHandler(m_options, result) .RunAsync(backendManager, filterstrings, filter) - ); + ).ConfigureAwait(false); } - public IListResults ListControlFiles(IEnumerable filterstrings, IFilter filter) + /// + public async Task ListControlFilesAsync(IEnumerable filterstrings, IFilter filter) { - return RunAction(new ListResults(), ref filter, (result, backendManager) => + return await RunActionAsync(new ListResults(), filter, (result, backendManager) => new Operation.ListControlFilesHandler(m_options, result) .RunAsync(backendManager, filterstrings, filter) - ); + ).ConfigureAwait(false); } - public IListRemoteResults ListRemote() + /// + public async Task ListRemoteAsync() { - return RunAction(new ListRemoteResults(), async (result, backendManager) => + return await RunActionAsync(new ListRemoteResults(), async (result, backendManager) => { using var tf = File.Exists(m_options.Dbpath) ? null : new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(((string)tf) ?? m_options.Dbpath, "list-remote", true, null, result.TaskControl.ProgressToken).ConfigureAwait(false); result.SetResult(await backendManager.ListAsync(CancellationToken.None).ConfigureAwait(false)); - }); + }).ConfigureAwait(false); } - public IListRemoteResults DeleteAllRemoteFiles() + /// + public async Task DeleteAllRemoteFilesAsync() { - return RunAction(new ListRemoteResults(), async (result, backendManager) => + return await RunActionAsync(new ListRemoteResults(), async (result, backendManager) => { result.OperationProgressUpdater.UpdatePhase(OperationPhase.Delete_Listing); { @@ -315,7 +294,7 @@ namespace Duplicati.Library.Main if (File.Exists(m_options.Dbpath)) { await using LocalDatabase db = await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, "list-remote", true, null, result.TaskControl.ProgressToken).ConfigureAwait(false); - var dbRemoteVolumes = db.GetRemoteVolumes(result.TaskControl.ProgressToken); + var dbRemoteVolumes = db.GetRemoteVolumesAsync(result.TaskControl.ProgressToken); var dbRemoteFiles = await dbRemoteVolumes .Select(x => x.Name) .ToHashSetAsync(cancellationToken: result.TaskControl.ProgressToken) @@ -348,88 +327,95 @@ namespace Duplicati.Library.Main } result.OperationProgressUpdater.UpdateProgress(1); } - }); + }).ConfigureAwait(false); } - public ICompactResults Compact() + /// + public async Task CompactAsync() { CheckAutoVacuumInterval(); - return RunAction(new CompactResults(), (result, backendManager) => + return await RunActionAsync(new CompactResults(), (result, backendManager) => new Operation.CompactHandler(m_options, result) .RunAsync(backendManager) - ); + ).ConfigureAwait(false); } - public ISetLockResults SetLocks() + /// + public async Task SetLocksAsync() { - return RunAction(new SetLockResults(), (result, backendManager) => + return await RunActionAsync(new SetLockResults(), (result, backendManager) => new Operation.SetLocksHandler(m_options, result) .RunAsync(backendManager) - ); + ).ConfigureAwait(false); } - public IReadLockInfoResults ReadLockInfo() + /// + public async Task ReadLockInfoAsync() { - return RunAction(new ReadLockInfoResults(), (result, backendManager) => + return await RunActionAsync(new ReadLockInfoResults(), (result, backendManager) => new Operation.ReadLockInfoHandler(m_options, result) .RunAsync(backendManager) - ); + ).ConfigureAwait(false); } - - - public IRecreateDatabaseResults UpdateDatabaseWithVersions(IFilter filter = null) + /// + public async Task UpdateDatabaseWithVersionsAsync(IFilter filter = null) { var filelistfilter = Operation.RestoreHandler.GetNumberedFilelistFilterDelegate(m_options.Time, m_options.Version, singleTimeMatch: true); - return RunAction(new RecreateDatabaseResults(), ref filter, async (result, backendManager) => + return await RunActionAsync(new RecreateDatabaseResults(), filter, async (result, backendManager) => { using (var h = new Operation.RecreateDatabaseHandler(m_options, result)) await h.RunUpdateAsync(backendManager, filter, filelistfilter, null) .ConfigureAwait(false); - }); + }).ConfigureAwait(false); } - public ICreateLogDatabaseResults CreateLogDatabase(string targetpath) + /// + public async Task CreateLogDatabaseAsync(string targetpath) { var t = new string[] { targetpath }; - return RunAction(new CreateLogDatabaseResults(), ref t, (result, backendManager) => - new Operation.CreateBugReportHandler(t[0], m_options, result).RunAsync() - ); + return await RunActionAsync(new CreateLogDatabaseResults(), t, async (result, backendManager) => + await new Operation.CreateBugReportHandler(t[0], m_options, result).RunAsync() + ).ConfigureAwait(false); } - public IListChangesResults ListChanges(string baseVersion, string targetVersion, IEnumerable filterstrings = null, IFilter filter = null, Action>> callback = null) + /// + public async Task ListChangesAsync(string baseVersion, string targetVersion, IEnumerable filterstrings = null, IFilter filter = null, Action>> callback = null) { var t = new string[] { baseVersion, targetVersion }; - return RunAction(new ListChangesResults(), ref t, ref filter, (result, backendManager) => - new Operation.ListChangesHandler(m_options, result) + return await RunActionAsync(new ListChangesResults(), t, filter, async (result, backendManager) => + await new Operation.ListChangesHandler(m_options, result) .RunAsync(t[0], t[1], backendManager, filterstrings, filter, callback) - ); + ).ConfigureAwait(false); } - public IListAffectedResults ListAffected(List args, Action callback = null) + /// + public async Task ListAffectedAsync(List args, Action callback = null) { - return RunAction(new ListAffectedResults(), (result, backendManager) => + return await RunActionAsync(new ListAffectedResults(), (result, backendManager) => new Operation.ListAffected(m_options, result) .RunAsync(args, callback) - ); + ).ConfigureAwait(false); } - public ITestResults Test(long samples = 1) + /// + public async Task TestAsync(long samples = 1) { if (!m_options.RawOptions.ContainsKey("full-remote-verification")) m_options.RawOptions["full-remote-verification"] = "true"; - return RunAction(new TestResults(), (result, backendManager) => + return await RunActionAsync(new TestResults(), (result, backendManager) => new Operation.TestHandler(m_options, result) .RunAsync(samples, backendManager) - ); + ).ConfigureAwait(false); } - public ITestFilterResults TestFilter(string[] paths, IFilter filter = null) + /// + public async Task TestFilterAsync(string[] paths, IFilter filter = null) { m_options.RawOptions["dry-run"] = "true"; m_options.RawOptions["dbpath"] = "INVALID!"; @@ -438,45 +424,50 @@ namespace Duplicati.Library.Main var filtertag = Logging.Log.LogTagFromType(typeof(Operation.Backup.FileEnumerationProcess)); using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.FilterTag.Contains(filtertag))) { - return RunAction(new TestFilterResults(), ref paths, ref filter, (result, backendManager) => + return await RunActionAsync(new TestFilterResults(), paths, filter, (result, backendManager) => new Operation.TestFilterHandler(m_options, result) .RunAsync(ExpandInputSources(paths, filter), filter) - ); + ).ConfigureAwait(false); } } - public ISystemInfoResults SystemInfo() + /// + public async Task SystemInfoAsync() { - return RunAction(new SystemInfoResults(), (result, backendManager) => + return await RunActionAsync(new SystemInfoResults(), (result, backendManager) => Operation.SystemInfoHandler.RunAsync(result) - ); + ).ConfigureAwait(false); } - public IPurgeFilesResults PurgeFiles(IFilter filter) + /// + public async Task PurgeFilesAsync(IFilter filter) { - return RunAction(new PurgeFilesResults(), (result, backendManager) => + return await RunActionAsync(new PurgeFilesResults(), (result, backendManager) => new Operation.PurgeFilesHandler(m_options, result) .RunAsync(backendManager, filter) - ); + ).ConfigureAwait(false); } - public IListBrokenFilesResults ListBrokenFiles(IFilter filter, Func callbackhandler = null) + /// + public async Task ListBrokenFilesAsync(IFilter filter, Func callbackhandler = null) { - return RunAction(new ListBrokenFilesResults(), (result, backendManager) => + return await RunActionAsync(new ListBrokenFilesResults(), (result, backendManager) => new Operation.ListBrokenFilesHandler(m_options, result) .RunAsync(backendManager, filter, callbackhandler) - ); + ).ConfigureAwait(false); } - public IPurgeBrokenFilesResults PurgeBrokenFiles(IFilter filter) + /// + public async Task PurgeBrokenFilesAsync(IFilter filter) { - return RunAction(new PurgeBrokenFilesResults(), (result, backendManager) => + return await RunActionAsync(new PurgeBrokenFilesResults(), (result, backendManager) => new Operation.PurgeBrokenFilesHandler(m_options, result) .RunAsync(backendManager, filter) - ); + ).ConfigureAwait(false); } - public ISendMailResults SendMail() + /// + public async Task SendMailAsync() { m_options.RawOptions["send-mail-level"] = "all"; m_options.RawOptions["send-mail-any-operation"] = "true"; @@ -498,47 +489,48 @@ namespace Duplicati.Library.Main var filtertag = Logging.Log.LogTagFromType(DynamicLoader.GenericLoader.GetModule(sendMailModuleKey).GetType()); using (Logging.Log.StartScope(m_messageSink.WriteMessage, x => x.FilterTag.Contains(filtertag))) { - return RunAction(new SendMailResults(), (result, backendManager) => + return await RunActionAsync(new SendMailResults(), (result, backendManager) => Task.Run(() => { result.Lines = new string[0]; System.Threading.Thread.Sleep(5); }) - ); + ).ConfigureAwait(false); } } - public IVacuumResults Vacuum() + /// + public async Task VacuumAsync() { - return RunAction(new VacuumResults(), (result, backendManager) => + return await RunActionAsync(new VacuumResults(), (result, backendManager) => new Operation.VacuumHandler(m_options, result) .RunAsync() - ); + ).ConfigureAwait(false); } - private T RunAction(T result, Func method) + private Task RunActionAsync(T result, Func method) where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { var tmp = new string[0]; IFilter tempfilter = null; - return RunAction(result, ref tmp, ref tempfilter, method); + return RunActionAsync(result, tmp, tempfilter, method); } - private T RunAction(T result, ref string[] paths, Func method) + private Task RunActionAsync(T result, string[] paths, Func method) where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { IFilter tempfilter = null; - return RunAction(result, ref paths, ref tempfilter, method); + return RunActionAsync(result, paths, tempfilter, method); } - private T RunAction(T result, ref IFilter filter, Func method) + private Task RunActionAsync(T result, IFilter filter, Func method) where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { var tmp = new string[0]; - return RunAction(result, ref tmp, ref filter, method); + return RunActionAsync(result, tmp, filter, method); } - private T RunAction(T result, ref string[] paths, ref IFilter filter, Func method) + private async Task RunActionAsync(T result, string[] paths, IFilter filter, Func method) where T : ISetCommonOptions, ITaskControlProvider, Logging.ILogDestination, IBasicResults, IBackendWriterProvider { OnOperationStarted?.Invoke(result); @@ -556,8 +548,8 @@ namespace Duplicati.Library.Main { m_currentTaskControl = result.TaskControl; m_options.MainAction = result.MainOperation; - ApplySecretProvider(CancellationToken.None).Await(); - SetupCommonOptions(result, ref paths, ref filter, logTarget); + await ApplySecretProviderAsync(CancellationToken.None).ConfigureAwait(false); + (paths, filter) = SetupCommonOptions(result, paths, filter, logTarget); Logging.Log.WriteInformationMessage(LOGTAG, "StartingOperation", Strings.Controller.StartingOperationMessage(m_options.MainAction)); using (SlowQueryMonitor.StartMonitoring(m_options.SlowQueryThreshold)) @@ -571,7 +563,7 @@ namespace Duplicati.Library.Main { backend.UpdateThrottleValues(m_options.MaxUploadPrSecond, m_options.MaxDownloadPrSecond); m_currentBackendManager = backend; - method(result, backend).Await(); + await method(result, backend).ConfigureAwait(false); } finally { @@ -590,8 +582,8 @@ namespace Duplicati.Library.Main { try { - using var db = LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, result.MainOperation.ToString(), true, null, CancellationToken.None).Await(); - backend.StopRunnerAndFlushMessages(db).Await(); + await using var db = await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, result.MainOperation.ToString(), true, null, CancellationToken.None).ConfigureAwait(false); + await backend.StopRunnerAndFlushMessagesAsync(db).ConfigureAwait(false); } catch (Exception ex) { @@ -613,10 +605,10 @@ namespace Duplicati.Library.Main { try { - using var db = LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, null, true, null, CancellationToken.None).Await(); - db.WriteResultsAndCommit(result, CancellationToken.None).Await(); - db.PurgeLogData(m_options.LogRetention, CancellationToken.None).Await(); - db.PurgeDeletedVolumes(DateTime.UtcNow, CancellationToken.None).Await(); + await using var db = await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, null, true, null, CancellationToken.None).ConfigureAwait(false); + await db.WriteResultsAndCommitAsync(result, CancellationToken.None).ConfigureAwait(false); + await db.PurgeLogDataAsync(m_options.LogRetention, CancellationToken.None).ConfigureAwait(false); + await db.PurgeDeletedVolumesAsync(DateTime.UtcNow, CancellationToken.None).ConfigureAwait(false); // Vacuum is done AFTER the results are written to the database // This means that the information about the vacuum is not stored in the database, @@ -626,8 +618,8 @@ namespace Duplicati.Library.Main try { vacuumResults.VacuumResults = new VacuumResults(basicResults); - new Operation.VacuumHandler(m_options, (VacuumResults)vacuumResults.VacuumResults) - .RunAsync().Await(); + await new Operation.VacuumHandler(m_options, (VacuumResults)vacuumResults.VacuumResults) + .RunAsync().ConfigureAwait(false); } catch (Exception ex) { @@ -660,8 +652,8 @@ namespace Duplicati.Library.Main { // No operation was started in database, so write logs to new operation if (File.Exists(m_options.Dbpath) && !m_options.Dryrun) - using (var db = LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, result.MainOperation.ToString(), true, null, CancellationToken.None).Await()) - db.WriteResultsAndCommit(result, CancellationToken.None).Await(); + await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, result.MainOperation.ToString(), true, null, CancellationToken.None).ConfigureAwait(false)) + await db.WriteResultsAndCommitAsync(result, CancellationToken.None).ConfigureAwait(false); } catch (Exception we) { @@ -685,8 +677,8 @@ namespace Duplicati.Library.Main resultSetter.Fatal = true; // Write logs to previous operation if database exists if (File.Exists(m_options.Dbpath) && !m_options.Dryrun) - using (var db = LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, null, true, null, CancellationToken.None).Await()) - db.WriteResultsAndCommit(result, CancellationToken.None).Await(); + await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, null, true, null, CancellationToken.None).ConfigureAwait(false)) + await db.WriteResultsAndCommitAsync(result, CancellationToken.None).ConfigureAwait(false); } catch (Exception we) { @@ -731,7 +723,7 @@ namespace Duplicati.Library.Main OnOperationCompleted?.Invoke(result, exception); } - private void SetupCommonOptions(ISetCommonOptions result, ref string[] paths, ref IFilter filter, ControllerMultiLogTarget logTarget) + private (string[] paths, IFilter filter) SetupCommonOptions(ISetCommonOptions result, string[] paths, IFilter filter, ControllerMultiLogTarget logTarget) { m_options.MainAction = result.MainOperation; @@ -834,6 +826,8 @@ namespace Duplicati.Library.Main } ValidateOptions(); + + return (paths, filter); } @@ -843,10 +837,10 @@ namespace Duplicati.Library.Main return; using var db = LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, operation.ToString(), true, null, CancellationToken.None).Await(); - Utility.UpdateOptionsFromDb(db, m_options, CancellationToken.None).Await(); + Utility.UpdateOptionsFromDbAsync(db, m_options, CancellationToken.None).Await(); } - private async Task ApplySecretProvider(CancellationToken cancellationToken) + private async Task ApplySecretProviderAsync(CancellationToken cancellationToken) { var args = new[] { new Library.Utility.Uri(m_backendUrl) }; await SecretProviderHelper.ApplySecretProviderAsync([], args, m_options.RawOptions, TempFolder.SystemTempPath, SecretProvider, cancellationToken).ConfigureAwait(false); @@ -1222,73 +1216,98 @@ namespace Duplicati.Library.Main return sources.ToArray(); } - public void Pause(bool alsoTransfers) + /// + public Task PauseAsync(bool alsoTransfers) { var ct = m_currentTaskControl; if (ct != null) ct.Pause(alsoTransfers); + return Task.CompletedTask; } - public void Resume() + /// + public Task ResumeAsync() { var ct = m_currentTaskControl; if (ct != null) ct.Resume(); + return Task.CompletedTask; } - public void Stop() + /// + public Task StopAsync() { var ct = m_currentTaskControl; if (ct == null) - return; + return Task.CompletedTask; Logging.Log.WriteVerboseMessage(LOGTAG, "CancellationRequested", "Cancellation Requested"); ct.Stop(); + return Task.CompletedTask; } - public void Abort() + /// + public Task AbortAsync() { m_currentTaskControl?.Terminate(); + return Task.CompletedTask; } - public void SetThrottleSpeeds(long maxUploadPrSecond, long maxDownloadPrSecond) + /// + public Task SetThrottleSpeedsAsync(long maxUploadPrSecond, long maxDownloadPrSecond) { // Set these values in the options, in case the backend is not up yet m_options.MaxUploadPrSecond = maxUploadPrSecond; m_options.MaxDownloadPrSecond = maxDownloadPrSecond; m_currentBackendManager?.UpdateThrottleValues(maxUploadPrSecond, maxDownloadPrSecond); + return Task.CompletedTask; } /// - /// Time of last compact operation + /// The time of the last compact operation /// - public DateTime LastCompact { get; set; } + private DateTime m_lastCompact; /// - /// Time of last vacuum operation + /// The time of the last vacuum operation /// - public DateTime LastVacuum { get; set; } + private DateTime m_lastVacuum; + + /// + public Task SetLastCompactAsync(DateTime lastCompact) + { + m_lastCompact = lastCompact; + return Task.CompletedTask; + } + + /// + public Task SetLastVacuumAsync(DateTime lastVacuum) + { + m_lastVacuum = lastVacuum; + return Task.CompletedTask; + } private void CheckAutoCompactInterval() { - if (!m_options.NoAutoCompact && (LastCompact > DateTime.MinValue) && (LastCompact.Add(m_options.AutoCompactInterval) > DateTime.Now)) + if (!m_options.NoAutoCompact && (m_lastCompact > DateTime.MinValue) && (m_lastCompact.Add(m_options.AutoCompactInterval) > DateTime.Now)) { - Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Skipping auto compaction until {0}", LastCompact.Add(m_options.AutoCompactInterval)); + Logging.Log.WriteInformationMessage(LOGTAG, "CompactResults", "Skipping auto compaction until {0}", m_lastCompact.Add(m_options.AutoCompactInterval)); m_options.RawOptions["no-auto-compact"] = "true"; } } private void CheckAutoVacuumInterval() { - if (m_options.AutoVacuum && (LastVacuum > DateTime.MinValue) && (LastVacuum.Add(m_options.AutoVacuumInterval) > DateTime.Now)) + if (m_options.AutoVacuum && (m_lastVacuum > DateTime.MinValue) && (m_lastVacuum.Add(m_options.AutoVacuumInterval) > DateTime.Now)) { - Logging.Log.WriteInformationMessage(LOGTAG, "VacuumResults", "Skipping auto vacuum until {0}", LastVacuum.Add(m_options.AutoVacuumInterval)); + Logging.Log.WriteInformationMessage(LOGTAG, "VacuumResults", "Skipping auto vacuum until {0}", m_lastVacuum.Add(m_options.AutoVacuumInterval)); m_options.RawOptions["auto-vacuum"] = "false"; } } #region IDisposable Members + /// public void Dispose() { } diff --git a/Duplicati/Library/Main/Database/ChangeStatistics.cs b/Duplicati/Library/Main/Database/ChangeStatistics.cs index cdfd3de73..f8bf0b056 100644 --- a/Duplicati/Library/Main/Database/ChangeStatistics.cs +++ b/Duplicati/Library/Main/Database/ChangeStatistics.cs @@ -47,7 +47,7 @@ public static class ChangeStatistics /// The ID of the previous fileset. /// The cancellation token to monitor for cancellation requests. /// A task that completes when the statistics are updated. - internal static async Task UpdateChangeStatistics(SqliteCommand cmd, BackupResults results, long currentFilesetId, long previousFilesetId, CancellationToken token) + internal static async Task UpdateChangeStatisticsAsync(SqliteCommand cmd, BackupResults results, long currentFilesetId, long previousFilesetId, CancellationToken token) { var tmpName = $"TmpFileState_{Library.Utility.Utility.GetHexGuid()}"; try @@ -94,33 +94,33 @@ public static class ChangeStatistics .ConfigureAwait(false); // Added - results.AddedFolders = await CountAdded(cmd, tmpName, LocalDatabase.FOLDER_BLOCKSET_ID, null, token) + results.AddedFolders = await CountAddedAsync(cmd, tmpName, LocalDatabase.FOLDER_BLOCKSET_ID, null, token) .ConfigureAwait(false); - results.AddedSymlinks = await CountAdded(cmd, tmpName, LocalDatabase.SYMLINK_BLOCKSET_ID, null, token) + results.AddedSymlinks = await CountAddedAsync(cmd, tmpName, LocalDatabase.SYMLINK_BLOCKSET_ID, null, token) .ConfigureAwait(false); - results.AddedFiles = await CountAdded(cmd, tmpName, null, [ + results.AddedFiles = await CountAddedAsync(cmd, tmpName, null, [ LocalDatabase.FOLDER_BLOCKSET_ID, LocalDatabase.SYMLINK_BLOCKSET_ID ], token) .ConfigureAwait(false); // Deleted - results.DeletedFolders = await CountDeleted(cmd, tmpName, LocalDatabase.FOLDER_BLOCKSET_ID, null, token) + results.DeletedFolders = await CountDeletedAsync(cmd, tmpName, LocalDatabase.FOLDER_BLOCKSET_ID, null, token) .ConfigureAwait(false); - results.DeletedSymlinks = await CountDeleted(cmd, tmpName, LocalDatabase.SYMLINK_BLOCKSET_ID, null, token) + results.DeletedSymlinks = await CountDeletedAsync(cmd, tmpName, LocalDatabase.SYMLINK_BLOCKSET_ID, null, token) .ConfigureAwait(false); - results.DeletedFiles = await CountDeleted(cmd, tmpName, null, [ + results.DeletedFiles = await CountDeletedAsync(cmd, tmpName, null, [ LocalDatabase.FOLDER_BLOCKSET_ID, LocalDatabase.SYMLINK_BLOCKSET_ID ], token) .ConfigureAwait(false); // Modified - results.ModifiedFolders = await CountModified(cmd, tmpName, LocalDatabase.FOLDER_BLOCKSET_ID, null, token) + results.ModifiedFolders = await CountModifiedAsync(cmd, tmpName, LocalDatabase.FOLDER_BLOCKSET_ID, null, token) .ConfigureAwait(false); - results.ModifiedSymlinks = await CountModified(cmd, tmpName, LocalDatabase.SYMLINK_BLOCKSET_ID, null, token) + results.ModifiedSymlinks = await CountModifiedAsync(cmd, tmpName, LocalDatabase.SYMLINK_BLOCKSET_ID, null, token) .ConfigureAwait(false); - results.ModifiedFiles = await CountModified(cmd, tmpName, null, [ + results.ModifiedFiles = await CountModifiedAsync(cmd, tmpName, null, [ LocalDatabase.FOLDER_BLOCKSET_ID, LocalDatabase.SYMLINK_BLOCKSET_ID ], token) @@ -149,7 +149,7 @@ public static class ChangeStatistics /// The blocksets to exclude from the count. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains the number of added files or folders. - private static async Task CountAdded(SqliteCommand cmd, string tmpName, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) + private static async Task CountAddedAsync(SqliteCommand cmd, string tmpName, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) { var conditions = $@" ""Source"" = 1 @@ -163,7 +163,7 @@ public static class ChangeStatistics ) "; - return await CountWithCondition(cmd, tmpName, "A", conditions, blocksetId, excludeBlocksets, token) + return await CountWithConditionAsync(cmd, tmpName, "A", conditions, blocksetId, excludeBlocksets, token) .ConfigureAwait(false); } @@ -176,7 +176,7 @@ public static class ChangeStatistics /// The blocksets to exclude from the count. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains the number of deleted files or folders. - private static async Task CountDeleted(SqliteCommand cmd, string tmpName, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) + private static async Task CountDeletedAsync(SqliteCommand cmd, string tmpName, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) { var conditions = @$" ""Source"" = 0 @@ -190,7 +190,7 @@ public static class ChangeStatistics ) "; - return await CountWithCondition(cmd, tmpName, "A", conditions, blocksetId, excludeBlocksets, token) + return await CountWithConditionAsync(cmd, tmpName, "A", conditions, blocksetId, excludeBlocksets, token) .ConfigureAwait(false); } @@ -203,7 +203,7 @@ public static class ChangeStatistics /// The blocksets to exclude from the count. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains the number of modified files or folders. - private static async Task CountModified(SqliteCommand cmd, string tmpName, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) + private static async Task CountModifiedAsync(SqliteCommand cmd, string tmpName, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) { string conditions; @@ -261,7 +261,7 @@ public static class ChangeStatistics /// The blocksets to exclude from the count. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains the number of files or folders that match the condition. - private static async Task CountWithCondition(SqliteCommand cmd, string tmpName, string alias, string baseCondition, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) + private static async Task CountWithConditionAsync(SqliteCommand cmd, string tmpName, string alias, string baseCondition, long? blocksetId, long[]? excludeBlocksets, CancellationToken token) { var fullCondition = baseCondition; var blocksetCondition = GetBlocksetCondition(alias, blocksetId, excludeBlocksets); diff --git a/Duplicati/Library/Main/Database/ExtensionMethods.cs b/Duplicati/Library/Main/Database/ExtensionMethods.cs index a9b9b2c08..9dd46f74b 100644 --- a/Duplicati/Library/Main/Database/ExtensionMethods.cs +++ b/Duplicati/Library/Main/Database/ExtensionMethods.cs @@ -602,7 +602,7 @@ public static partial class ExtensionMethods // We have a temporary table, so we need to replace the parameter with the table name cmd.CommandText = cmd.CommandText.Replace( originalParamName, - await values.GetInClause(cancellationToken).ConfigureAwait(false), + await values.GetInClauseAsync(cancellationToken).ConfigureAwait(false), StringComparison.OrdinalIgnoreCase ); @@ -1251,7 +1251,7 @@ public static partial class ExtensionMethods return ExpandInClauseParameter(cmd, originalParamName, values.Values); // We have a temporary table, so we need to replace the parameter with the table name - cmd.CommandText = cmd.CommandText.Replace(originalParamName, values.GetInClause(default).Await(), StringComparison.OrdinalIgnoreCase); + cmd.CommandText = cmd.CommandText.Replace(originalParamName, values.GetInClauseAsync(default).Await(), StringComparison.OrdinalIgnoreCase); return cmd; } diff --git a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs index 34d8828c2..177540f62 100644 --- a/Duplicati/Library/Main/Database/LocalBackupDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBackupDatabase.cs @@ -742,7 +742,7 @@ namespace Duplicati.Library.Main.Database /// The size of the block. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains true if the block should be added to the current output. - public async Task FindBlockID(string key, long size, CancellationToken token) + public async Task FindBlockIDAsync(string key, long size, CancellationToken token) { return await m_findblockCommand .SetTransaction(m_rtr) @@ -760,9 +760,9 @@ namespace Duplicati.Library.Main.Database /// The ID of the volume to which the block belongs. /// The cancellation token to monitor for cancellation requests. /// A taskt that when awaited contains true if the block should be added to the current output. - public async Task AddBlock(string key, long size, long volumeid, CancellationToken token) + public async Task AddBlockAsync(string key, long size, long volumeid, CancellationToken token) { - var r = await FindBlockID(key, size, token).ConfigureAwait(false); + var r = await FindBlockIDAsync(key, size, token).ConfigureAwait(false); if (r == -1L) { if (m_moveblockfromdeletedCommand != null) @@ -842,7 +842,7 @@ namespace Duplicati.Library.Main.Database /// The list of hashes for the blocklist, or null if no blocklist is used. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains a tuple with the first value indicating whether the blockset was created, and the second value being the blockset ID. - public async Task<(bool, long)> AddBlockset(string filehash, long size, int blocksize, IEnumerable hashes, IEnumerable blocklistHashes, CancellationToken token) + public async Task<(bool, long)> AddBlocksetAsync(string filehash, long size, int blocksize, IEnumerable hashes, IEnumerable blocklistHashes, CancellationToken token) { long blocksetid = await m_findblocksetCommand .SetTransaction(m_rtr) @@ -946,7 +946,7 @@ namespace Duplicati.Library.Main.Database /// The size of the metadata. /// The cancellation token to cancel the operation. /// A task that when awaited contains a tuple with the first value indicating if the metadataset was found, and the second value being the metadataset ID. - public async Task<(bool, long)> GetMetadatasetID(string filehash, long size, CancellationToken token) + public async Task<(bool, long)> GetMetadatasetIDAsync(string filehash, long size, CancellationToken token) { long metadataid; @@ -975,9 +975,9 @@ namespace Duplicati.Library.Main.Database /// The metahash object. /// The cancellation token to cancel the operation. /// A task that when awaited contains a tuple with the first value indicating if the metadata set was added, and the second value being the metadata ID. - public async Task<(bool, long)> AddMetadataset(string filehash, long size, long blocksetid, IMetahash metahash, CancellationToken token) + public async Task<(bool, long)> AddMetadatasetAsync(string filehash, long size, long blocksetid, IMetahash metahash, CancellationToken token) { - var (metadatafound, metadataid) = await GetMetadatasetID(filehash, size, token) + var (metadatafound, metadataid) = await GetMetadatasetIDAsync(filehash, size, token) .ConfigureAwait(false); if (metadatafound) return (false, metadataid); @@ -1004,7 +1004,7 @@ namespace Duplicati.Library.Main.Database /// The ID for the metadata. /// The cancellation token to cancel the operation. /// A task that completes when the file is added. - public async Task AddFile(long pathprefixid, string filename, DateTime lastmodified, long blocksetID, long metadataID, CancellationToken token) + public async Task AddFileAsync(long pathprefixid, string filename, DateTime lastmodified, long blocksetID, long metadataID, CancellationToken token) { var fileidobj = await m_findfilesetCommand .SetTransaction(m_rtr) @@ -1028,7 +1028,7 @@ namespace Duplicati.Library.Main.Database await m_rtr.CommitAsync(token).ConfigureAwait(false); } - await AddKnownFile(fileidobj, lastmodified, token).ConfigureAwait(false); + await AddKnownFileAsync(fileidobj, lastmodified, token).ConfigureAwait(false); } /// @@ -1040,12 +1040,12 @@ namespace Duplicati.Library.Main.Database /// The ID for the metadata. /// The cancellation token to cancel the operation. /// A task that completes when the file is added. - public async Task AddFile(string filename, DateTime lastmodified, long blocksetID, long metadataID, CancellationToken token) + public async Task AddFileAsync(string filename, DateTime lastmodified, long blocksetID, long metadataID, CancellationToken token) { var split = SplitIntoPrefixAndName(filename); - await AddFile( - await GetOrCreatePathPrefix(split.Key, token).ConfigureAwait(false), + await AddFileAsync( + await GetOrCreatePathPrefixAsync(split.Key, token).ConfigureAwait(false), split.Value, lastmodified, blocksetID, @@ -1062,7 +1062,7 @@ namespace Duplicati.Library.Main.Database /// The time the file was modified. /// The cancellation token to cancel the operation. /// A task that completes when the file is added. - public async Task AddKnownFile(long fileid, DateTime lastmodified, CancellationToken token) + public async Task AddKnownFileAsync(long fileid, DateTime lastmodified, CancellationToken token) { await m_insertfileOperationCommand .SetTransaction(m_rtr) @@ -1081,11 +1081,8 @@ namespace Duplicati.Library.Main.Database /// The time the directory was modified. /// The cancellation token to cancel the operation. /// A task that completes when the directory entry is added. - public async Task AddDirectoryEntry(string path, long metadataID, DateTime lastmodified, CancellationToken token) - { - await AddFile(path, lastmodified, FOLDER_BLOCKSET_ID, metadataID, token) - .ConfigureAwait(false); - } + public Task AddDirectoryEntryAsync(string path, long metadataID, DateTime lastmodified, CancellationToken token) + => AddFileAsync(path, lastmodified, FOLDER_BLOCKSET_ID, metadataID, token); /// /// Adds a symlink entry to the fileset. @@ -1095,11 +1092,8 @@ namespace Duplicati.Library.Main.Database /// The time the symlink was modified. /// The cancellation token to cancel the operation. /// A task that completes when the symlink entry is added. - public async Task AddSymlinkEntry(string path, long metadataID, DateTime lastmodified, CancellationToken token) - { - await AddFile(path, lastmodified, SYMLINK_BLOCKSET_ID, metadataID, token) - .ConfigureAwait(false); - } + public Task AddSymlinkEntryAsync(string path, long metadataID, DateTime lastmodified, CancellationToken token) + => AddFileAsync(path, lastmodified, SYMLINK_BLOCKSET_ID, metadataID, token); /// /// Gets the ID, last modified time and size of a file in the fileset. @@ -1110,7 +1104,7 @@ namespace Duplicati.Library.Main.Database /// Whether to include the file length in the result. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains a tuple with the file ID, last modified time, and file length. - public async Task<(long, DateTime, long)> GetFileLastModified(long prefixid, string path, long filesetid, bool includeLength, CancellationToken token) + public async Task<(long, DateTime, long)> GetFileLastModifiedAsync(long prefixid, string path, long filesetid, bool includeLength, CancellationToken token) { DateTime oldModified; long length; @@ -1160,7 +1154,7 @@ namespace Duplicati.Library.Main.Database /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains a tuple with the file ID, last modified time, file size, metadata hash, and metadata size. /// - public async Task<(long, DateTime, long, string?, long)> GetFileEntry(long prefixid, string path, long filesetid, CancellationToken token) + public async Task<(long, DateTime, long, string?, long)> GetFileEntryAsync(long prefixid, string path, long filesetid, CancellationToken token) { DateTime oldModified; long lastFileSize; @@ -1212,7 +1206,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the file. /// A cancellation token to monitor for cancellation requests. /// A task that when awaited contains a tuple with the metadata hash and size, or null if the file does not exist. - public async Task<(string MetadataHash, long Size)?> GetMetadataHashAndSizeForFile(long fileid, CancellationToken token) + public async Task<(string MetadataHash, long Size)?> GetMetadataHashAndSizeForFileAsync(long fileid, CancellationToken token) { m_selectfilemetadatahashandsizeCommand .SetTransaction(m_rtr) @@ -1234,7 +1228,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the file. /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains the hash of the file, or null if the file does not exist. - public async Task GetFileHash(long fileid, CancellationToken token) + public async Task GetFileHashAsync(long fileid, CancellationToken token) { var r = await m_selectfileHashCommand .SetTransaction(m_rtr) @@ -1276,7 +1270,7 @@ namespace Duplicati.Library.Main.Database /// /// The cancellation token to monitor for cancellation requests. /// A task that when awaited contains the size of the last written DBlock volume, or -1 if no such volume exists. - public async Task GetLastWrittenDBlockVolumeSize(CancellationToken token) + public async Task GetLastWrittenDBlockVolumeSizeAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); return await cmd.SetCommandAndParameters(@" @@ -1300,9 +1294,9 @@ namespace Duplicati.Library.Main.Database /// The command to use for the query. /// The cancellation token to cancel the operation. /// A task that when awaited contains the ID of the previous fileset, or -1 if no such fileset exists. - private async Task GetPreviousFilesetID(SqliteCommand cmd, CancellationToken token) + private async Task GetPreviousFilesetIDAsync(SqliteCommand cmd, CancellationToken token) { - return await GetPreviousFilesetID(cmd, OperationTimestamp, m_filesetId, token) + return await GetPreviousFilesetIDAsync(cmd, OperationTimestamp, m_filesetId, token) .ConfigureAwait(false); } @@ -1314,7 +1308,7 @@ namespace Duplicati.Library.Main.Database /// The current fileset ID. /// The cancellation token to cancel the operation. /// A task that when awaited contains the ID of the previous fileset, or -1 if no such fileset exists. - private async Task GetPreviousFilesetID(SqliteCommand cmd, DateTime timestamp, long filesetid, CancellationToken token) + private async Task GetPreviousFilesetIDAsync(SqliteCommand cmd, DateTime timestamp, long filesetid, CancellationToken token) { return await cmd .SetTransaction(m_rtr) @@ -1337,7 +1331,7 @@ namespace Duplicati.Library.Main.Database /// /// The cancellation token to cancel the operation. /// A task that when awaited contains a tuple with the count of files and the total size of files in the last backup fileset. - internal async Task> GetLastBackupFileCountAndSize(CancellationToken token) + internal async Task> GetLastBackupFileCountAndSizeAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var lastFilesetId = await cmd.ExecuteScalarInt64Async(@" @@ -1396,12 +1390,12 @@ namespace Duplicati.Library.Main.Database /// The results of the backup operation. /// The cancellation token to cancel the operation. /// A task that completes when the change statistics are updated. - internal async Task UpdateChangeStatistics(BackupResults results, CancellationToken token) + internal async Task UpdateChangeStatisticsAsync(BackupResults results, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); - var prevFileSetId = await GetPreviousFilesetID(cmd, token) + var prevFileSetId = await GetPreviousFilesetIDAsync(cmd, token) .ConfigureAwait(false); - await ChangeStatistics.UpdateChangeStatistics(cmd, results, m_filesetId, prevFileSetId, token) + await ChangeStatistics.UpdateChangeStatisticsAsync(cmd, results, m_filesetId, prevFileSetId, token) .ConfigureAwait(false); } @@ -1412,9 +1406,9 @@ namespace Duplicati.Library.Main.Database /// List of deleted paths, or null." /// The cancellation token to cancel the operation. /// A task that completes when the files are appended. - public async Task AppendFilesFromPreviousSet(IEnumerable? deleted, CancellationToken token) + public async Task AppendFilesFromPreviousSetAsync(IEnumerable? deleted, CancellationToken token) { - await AppendFilesFromPreviousSet(deleted, m_filesetId, -1, OperationTimestamp, token) + await AppendFilesFromPreviousSetAsync(deleted, m_filesetId, -1, OperationTimestamp, token) .ConfigureAwait(false); } @@ -1428,12 +1422,12 @@ namespace Duplicati.Library.Main.Database /// If filesetid == -1, used to locate previous file-set. /// The cancellation token to cancel the operation. /// A task that completes when the files are appended. - public async Task AppendFilesFromPreviousSet(IEnumerable? deleted, long filesetid, long prevId, DateTime timestamp, CancellationToken token) + public async Task AppendFilesFromPreviousSetAsync(IEnumerable? deleted, long filesetid, long prevId, DateTime timestamp, CancellationToken token) { await using var cmd = m_connection.CreateCommand(); await using var cmdDelete = m_connection.CreateCommand(); long lastFilesetId = prevId < 0 - ? await GetPreviousFilesetID(cmd, timestamp, filesetid, token) + ? await GetPreviousFilesetIDAsync(cmd, timestamp, filesetid, token) .ConfigureAwait(false) : prevId; @@ -1602,9 +1596,9 @@ namespace Duplicati.Library.Main.Database /// Optional exclusion predicate (true = exclude file). /// The cancellation token to cancel the operation. /// A task that completes when the files are appended. - public async Task AppendFilesFromPreviousSetWithPredicate(Func exclusionPredicate, CancellationToken token) + public async Task AppendFilesFromPreviousSetWithPredicateAsync(Func exclusionPredicate, CancellationToken token) { - await AppendFilesFromPreviousSetWithPredicate(exclusionPredicate, m_filesetId, -1, OperationTimestamp, token) + await AppendFilesFromPreviousSetWithPredicateAsync(exclusionPredicate, m_filesetId, -1, OperationTimestamp, token) .ConfigureAwait(false); } @@ -1619,11 +1613,11 @@ namespace Duplicati.Library.Main.Database /// If prevFileSetId == -1, used to locate previous fileset /// The cancellation token to cancel the operation. /// A task that completes when the files are appended. - public async Task AppendFilesFromPreviousSetWithPredicate(Func exclusionPredicate, long fileSetId, long prevFileSetId, DateTime timestamp, CancellationToken token) + public async Task AppendFilesFromPreviousSetWithPredicateAsync(Func exclusionPredicate, long fileSetId, long prevFileSetId, DateTime timestamp, CancellationToken token) { if (exclusionPredicate == null) { - await AppendFilesFromPreviousSet(null, fileSetId, prevFileSetId, timestamp, token) + await AppendFilesFromPreviousSetAsync(null, fileSetId, prevFileSetId, timestamp, token) .ConfigureAwait(false); return; } @@ -1631,7 +1625,7 @@ namespace Duplicati.Library.Main.Database await using var cmd = m_connection.CreateCommand(); await using var cmdDelete = m_connection.CreateCommand(); long lastFilesetId = prevFileSetId < 0 ? - await GetPreviousFilesetID(cmd, timestamp, fileSetId, token) + await GetPreviousFilesetIDAsync(cmd, timestamp, fileSetId, token) .ConfigureAwait(false) : prevFileSetId; @@ -1731,9 +1725,9 @@ namespace Duplicati.Library.Main.Database /// The timestamp of the operation to create. /// The cancellation token to cancel the operation. /// A task that when awaited contains the ID of the created fileset. - public override async Task CreateFileset(long volumeid, DateTime timestamp, CancellationToken token) + public override async Task CreateFilesetAsync(long volumeid, DateTime timestamp, CancellationToken token) { - return m_filesetId = await base.CreateFileset(volumeid, timestamp, token) + return m_filesetId = await base.CreateFilesetAsync(volumeid, timestamp, token) .ConfigureAwait(false); } @@ -1743,9 +1737,9 @@ namespace Duplicati.Library.Main.Database /// If true, only the latest incomplete fileset volume will be returned. /// The cancellation token to cancel the operation. /// A task that when awaited contains a list of volume names. - public async Task> GetTemporaryFilelistVolumeNames(bool latestOnly, CancellationToken token) + public async Task> GetTemporaryFilelistVolumeNamesAsync(bool latestOnly, CancellationToken token) { - var incompleteFilesetIDs = GetIncompleteFilesets(token).OrderBy(x => x.Value).Select(x => x.Key); + var incompleteFilesetIDs = GetIncompleteFilesetsAsync(token).OrderBy(x => x.Value).Select(x => x.Key); if (!await incompleteFilesetIDs.AnyAsync(token).ConfigureAwait(false)) return []; @@ -1759,7 +1753,7 @@ namespace Duplicati.Library.Main.Database var volumeNames = new List(); await foreach (var filesetID in incompleteFilesetIDs.ConfigureAwait(false)) volumeNames.Add(( - await GetRemoteVolumeFromFilesetID(filesetID, token) + await GetRemoteVolumeFromFilesetIDAsync(filesetID, token) .ConfigureAwait(false) ).Name); @@ -1771,7 +1765,7 @@ namespace Duplicati.Library.Main.Database /// /// The cancellation token to cancel the operation. /// An asynchronous enumerable of volume names that are missing index files. - public async IAsyncEnumerable GetMissingIndexFiles([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMissingIndexFilesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr) .SetCommandAndParameters(@" @@ -1805,7 +1799,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the target volume. /// The cancellation token to cancel the operation. /// A task that completes when the block is moved. - public async Task MoveBlockToVolume(string blockkey, long size, long sourcevolumeid, long targetvolumeid, CancellationToken token) + public async Task MoveBlockToVolumeAsync(string blockkey, long size, long sourcevolumeid, long targetvolumeid, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var c = await cmd.SetCommandAndParameters(@" @@ -1835,9 +1829,9 @@ namespace Duplicati.Library.Main.Database /// The cancellation token to cancel the operation. /// A task that completes when the remote volume is safely deleted. /// Thrown if the volume has associated blocks. - public async Task SafeDeleteRemoteVolume(string name, CancellationToken token) + public async Task SafeDeleteRemoteVolumeAsync(string name, CancellationToken token) { - var volumeid = await GetRemoteVolumeID(name, token).ConfigureAwait(false); + var volumeid = await GetRemoteVolumeIDAsync(name, token).ConfigureAwait(false); await using var cmd = m_connection.CreateCommand(m_rtr); var c = await cmd.SetCommandAndParameters(@" @@ -1852,7 +1846,7 @@ namespace Duplicati.Library.Main.Database if (c != 0) throw new Exception($"Failed to safe-delete volume {name}, blocks: {c}"); - await RemoveRemoteVolume(name, token).ConfigureAwait(false); + await RemoveRemoteVolumeAsync(name, token).ConfigureAwait(false); } /// @@ -1861,9 +1855,9 @@ namespace Duplicati.Library.Main.Database /// The name of the volume to check. /// The cancellation token to cancel the operation. /// A task that when awaited contains an array of blocklist hashes. - public async Task GetBlocklistHashes(string name, CancellationToken token) + public async Task GetBlocklistHashesAsync(string name, CancellationToken token) { - var volumeid = GetRemoteVolumeID(name, token); + var volumeid = GetRemoteVolumeIDAsync(name, token); await using var cmd = m_connection.CreateCommand(m_rtr); // Grab the strings and return as array to avoid concurrent access to the IEnumerable cmd.SetCommandAndParameters(@" @@ -1889,7 +1883,7 @@ namespace Duplicati.Library.Main.Database /// /// The cancellation token to cancel the operation. /// A task that when awaited contains the first path, or null if no paths exist. - public async Task GetFirstPath(CancellationToken token) + public async Task GetFirstPathAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var v0 = await cmd.ExecuteScalarAsync(@" @@ -1911,7 +1905,7 @@ namespace Duplicati.Library.Main.Database /// /// The Fileset-ID. /// An asynchronous enumerable of USN journal data entries. - public async IAsyncEnumerable GetChangeJournalData(long fileSetId, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetChangeJournalDataAsync(long fileSetId, [EnumeratorCancellation] CancellationToken token) { var data = new List(); @@ -1947,7 +1941,7 @@ namespace Duplicati.Library.Main.Database /// The cancellation token to cancel the operation. /// A task that completes when the data is added. /// Thrown if unable to add change journal entry. - public async Task CreateChangeJournalData(IEnumerable data, CancellationToken token) + public async Task CreateChangeJournalDataAsync(IEnumerable data, CancellationToken token) { using (new Logging.Timer(LOGTAG, "CreateChangeJournalData", "Inserting USN entries")) foreach (var entry in data) @@ -1991,7 +1985,7 @@ namespace Duplicati.Library.Main.Database /// Existing file set to update. /// The cancellation token to cancel the operation. /// A task that completes when the data is added. - public async Task UpdateChangeJournalData(IEnumerable data, long fileSetId, CancellationToken token) + public async Task UpdateChangeJournalDataAsync(IEnumerable data, long fileSetId, CancellationToken token) { using (new Logging.Timer(LOGTAG, "UpdateChangeJournalData", "Updating USN entries")) foreach (var entry in data) @@ -2023,7 +2017,7 @@ namespace Duplicati.Library.Main.Database /// The hash to check. /// The cancellation token to cancel the operation. /// A task that when awaited returns true if the hash is known, false otherwise. - public async Task IsBlocklistHashKnown(string hash, CancellationToken token) + public async Task IsBlocklistHashKnownAsync(string hash, CancellationToken token) { var res = await m_getfirstfilesetwithblockinblockset .SetTransaction(m_rtr) @@ -2043,7 +2037,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the fileset to clean up. /// The cancellation token to cancel the operation. /// A task that completes when the cleanup is finished. - public async Task RemoveDuplicatePathsFromFileset(long filesetId, CancellationToken token) + public async Task RemoveDuplicatePathsFromFilesetAsync(long filesetId, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); diff --git a/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs b/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs index 78f15f600..84a44af25 100644 --- a/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalBugReportDatabase.cs @@ -63,7 +63,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that completes when the obfuscation is finished. - public async Task Fix(CancellationToken token) + public async Task ObfuscateAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var tablename = $"PathMap-{Library.Utility.Utility.GetHexGuid()}"; diff --git a/Duplicati/Library/Main/Database/LocalDatabase.cs b/Duplicati/Library/Main/Database/LocalDatabase.cs index 1a5ed1116..d1df274eb 100644 --- a/Duplicati/Library/Main/Database/LocalDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDatabase.cs @@ -521,7 +521,7 @@ namespace Duplicati.Library.Main.Database /// Fileset IDs to resolve. /// Cancellation token. /// An async sequence of remote volume name and lock expiration time (UTC) if present. - public async IAsyncEnumerable<(string Name, DateTime? LockExpirationTime)> GetRemoteVolumesDependingOnFilesets( + public async IAsyncEnumerable<(string Name, DateTime? LockExpirationTime)> GetRemoteVolumesDependingOnFilesetsAsync( IEnumerable filesetIds, [EnumeratorCancellation] CancellationToken token) { @@ -594,7 +594,7 @@ namespace Duplicati.Library.Main.Database while (await rd.ReadAsync(token).ConfigureAwait(false)) { var name = rd.ConvertValueToString(0) ?? string.Empty; - var lockUntil = rd.IsDBNull(1) + var lockUntil = await rd.IsDBNullAsync(1) ? (DateTime?)null : ParseFromEpochSeconds(rd.ConvertValueToInt64(1)); @@ -611,9 +611,9 @@ namespace Duplicati.Library.Main.Database /// The hash of the remote volume, or null if not applicable. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the remote volume has been updated. - public async Task UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string? hash, CancellationToken token) + public async Task UpdateRemoteVolumeAsync(string name, RemoteVolumeState state, long size, string? hash, CancellationToken token) { - await UpdateRemoteVolume(name, state, size, hash, false, token) + await UpdateRemoteVolumeAsync(name, state, size, hash, false, token) .ConfigureAwait(false); } @@ -627,9 +627,9 @@ namespace Duplicati.Library.Main.Database /// If true, suppresses cleanup of the remote volume after updating. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the remote volume has been updated. - public async Task UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string? hash, bool suppressCleanup, CancellationToken token) + public async Task UpdateRemoteVolumeAsync(string name, RemoteVolumeState state, long size, string? hash, bool suppressCleanup, CancellationToken token) { - await UpdateRemoteVolume(name, state, size, hash, suppressCleanup, new TimeSpan(0), null, token) + await UpdateRemoteVolumeAsync(name, state, size, hash, suppressCleanup, new TimeSpan(0), null, token) .ConfigureAwait(false); } @@ -645,7 +645,7 @@ namespace Duplicati.Library.Main.Database /// If true, sets the remote volume as archived. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the remote volume has been updated. - public async Task UpdateRemoteVolume(string name, RemoteVolumeState state, long size, string? hash, bool suppressCleanup, TimeSpan deleteGraceTime, bool? setArchived, CancellationToken token) + public async Task UpdateRemoteVolumeAsync(string name, RemoteVolumeState state, long size, string? hash, bool suppressCleanup, TimeSpan deleteGraceTime, bool? setArchived, CancellationToken token) { var c = await m_updateremotevolumeCommand.SetTransaction(m_rtr) .SetParameterValue("@OperationID", m_operationid) @@ -701,7 +701,7 @@ namespace Duplicati.Library.Main.Database if (!suppressCleanup && state == RemoteVolumeState.Deleted) { - await RemoveRemoteVolume(name, token).ConfigureAwait(false); + await RemoveRemoteVolumeAsync(name, token).ConfigureAwait(false); } } @@ -710,7 +710,7 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of key-value pairs, where each pair contains the fileset ID and its timestamp. - public async IAsyncEnumerable> FilesetTimes([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> FilesetTimesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" SELECT @@ -739,13 +739,13 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns a tuple containing the SQL WHERE clause and a dictionary of parameter values. /// Thrown if the provided time is unspecified. - public async Task<(string Query, Dictionary Values)> GetFilelistWhereClause(DateTime time, long[]? versions, IEnumerable>? filesetslist, bool singleTimeMatch, CancellationToken token) + public async Task<(string Query, Dictionary Values)> GetFilelistWhereClauseAsync(DateTime time, long[]? versions, IEnumerable>? filesetslist, bool singleTimeMatch, CancellationToken token) { KeyValuePair[] filesets; if (filesetslist != null) filesets = [.. filesetslist]; else - filesets = await FilesetTimes(token) + filesets = await FilesetTimesAsync(token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); @@ -805,7 +805,7 @@ namespace Duplicati.Library.Main.Database /// The name of the remote volume. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the ID of the remote volume. If the volume does not exist, it returns -1. - public async Task GetRemoteVolumeID(string file, CancellationToken token) + public async Task GetRemoteVolumeIDAsync(string file, CancellationToken token) { return await m_selectremotevolumeIdCommand .SetTransaction(m_rtr) @@ -820,7 +820,7 @@ namespace Duplicati.Library.Main.Database /// An enumerable collection of file names. /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of key-value pairs, where each pair contains the file name and its corresponding remote volume ID. - public async IAsyncEnumerable> GetRemoteVolumeIDs(IEnumerable files, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> GetRemoteVolumeIDsAsync(IEnumerable files, [EnumeratorCancellation] CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" SELECT @@ -848,7 +848,7 @@ namespace Duplicati.Library.Main.Database /// The name of the remote volume file. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns a representing the remote volume. If the volume does not exist, it returns an empty entry. - public async Task GetRemoteVolume(string file, CancellationToken token) + public async Task GetRemoteVolumeAsync(string file, CancellationToken token) { m_selectremotevolumeCommand .SetTransaction(m_rtr) @@ -876,7 +876,7 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of key-value pairs, where each pair contains the name of the remote volume and its state. - public async IAsyncEnumerable> DuplicateRemoteVolumes([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> DuplicateRemoteVolumesAsync([EnumeratorCancellation] CancellationToken token) { m_selectduplicateRemoteVolumesCommand.SetTransaction(m_rtr); @@ -896,7 +896,7 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of representing all remote volumes. - public async IAsyncEnumerable GetRemoteVolumes([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetRemoteVolumesAsync([EnumeratorCancellation] CancellationToken token) { m_selectremotevolumesCommand.SetTransaction(m_rtr); await using var rd = await m_selectremotevolumesCommand @@ -927,7 +927,7 @@ namespace Duplicati.Library.Main.Database /// Any data relating to the operation. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the operation log has been recorded. - public async Task LogRemoteOperation(string operation, string path, string? data, CancellationToken token) + public async Task LogRemoteOperationAsync(string operation, string path, string? data, CancellationToken token) { await m_insertremotelogCommand .SetTransaction(m_rtr) @@ -948,7 +948,7 @@ namespace Duplicati.Library.Main.Database /// An optional exception. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the log message has been recorded. - public async Task LogMessage(string type, string message, Exception? exception, CancellationToken token) + public async Task LogMessageAsync(string type, string message, Exception? exception, CancellationToken token) { await m_insertlogCommand .SetTransaction(m_rtr) @@ -970,7 +970,7 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// A task that completes when the remote volume has been unlinked. /// Thrown if the number of unlinked remote volumes is not equal to 1. - public async Task UnlinkRemoteVolume(string name, RemoteVolumeState state, CancellationToken token) + public async Task UnlinkRemoteVolumeAsync(string name, RemoteVolumeState state, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" DELETE FROM ""RemoteVolume"" @@ -996,9 +996,9 @@ namespace Duplicati.Library.Main.Database /// The name of the remote volume to remove. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the remote volume has been removed. - public async Task RemoveRemoteVolume(string name, CancellationToken token) + public async Task RemoveRemoteVolumeAsync(string name, CancellationToken token) { - await RemoveRemoteVolumes([name], token).ConfigureAwait(false); + await RemoveRemoteVolumesAsync([name], token).ConfigureAwait(false); } /// @@ -1007,7 +1007,7 @@ namespace Duplicati.Library.Main.Database /// An enumerable collection of names of remote volumes to remove. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the remote volumes have been removed. - public async Task RemoveRemoteVolumes(IEnumerable names, CancellationToken token) + public async Task RemoveRemoteVolumesAsync(IEnumerable names, CancellationToken token) { if (names == null || !names.Any()) return; @@ -1371,7 +1371,7 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// A task that completes when the VACUUM operation has finished. - public async Task Vacuum(CancellationToken token) + public async Task VacuumAsync(CancellationToken token) { m_hasExecutedVacuum = true; await using var cmd = m_connection.CreateCommand(); @@ -1389,9 +1389,9 @@ namespace Duplicati.Library.Main.Database /// The state of the remote volume. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the ID of the newly registered remote volume. - public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, long size, RemoteVolumeState state, CancellationToken token) + public async Task RegisterRemoteVolumeAsync(string name, RemoteVolumeType type, long size, RemoteVolumeState state, CancellationToken token) { - return await RegisterRemoteVolume(name, type, state, size, new TimeSpan(0), token) + return await RegisterRemoteVolumeAsync(name, type, state, size, new TimeSpan(0), token) .ConfigureAwait(false); } @@ -1403,9 +1403,9 @@ namespace Duplicati.Library.Main.Database /// The state of the remote volume. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the ID of the newly registered remote volume. - public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, CancellationToken token) + public async Task RegisterRemoteVolumeAsync(string name, RemoteVolumeType type, RemoteVolumeState state, CancellationToken token) { - return await RegisterRemoteVolume(name, type, state, new TimeSpan(0), token) + return await RegisterRemoteVolumeAsync(name, type, state, new TimeSpan(0), token) .ConfigureAwait(false); } @@ -1418,9 +1418,9 @@ namespace Duplicati.Library.Main.Database /// The time after which the remote volume can be deleted. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the ID of the newly registered remote volume. - public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, TimeSpan deleteGraceTime, CancellationToken token) + public async Task RegisterRemoteVolumeAsync(string name, RemoteVolumeType type, RemoteVolumeState state, TimeSpan deleteGraceTime, CancellationToken token) { - return await RegisterRemoteVolume(name, type, state, -1, deleteGraceTime, token) + return await RegisterRemoteVolumeAsync(name, type, state, -1, deleteGraceTime, token) .ConfigureAwait(false); } @@ -1434,7 +1434,7 @@ namespace Duplicati.Library.Main.Database /// The time after which the remote volume can be deleted. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the ID of the newly registered remote volume. - public async Task RegisterRemoteVolume(string name, RemoteVolumeType type, RemoteVolumeState state, long size, TimeSpan deleteGraceTime, CancellationToken token) + public async Task RegisterRemoteVolumeAsync(string name, RemoteVolumeType type, RemoteVolumeState state, long size, TimeSpan deleteGraceTime, CancellationToken token) { var r = await m_createremotevolumeCommand .SetTransaction(m_rtr) @@ -1464,13 +1464,13 @@ namespace Duplicati.Library.Main.Database /// An asynchronous enumerable of fileset IDs that match the criteria. /// Thrown if the provided DateTime is unspecified. /// Thrown if no backups are found at the specified date. - public async IAsyncEnumerable GetFilesetIDs(DateTime restoretime, long[]? versions, bool singleTimeMatch, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesetIDsAsync(DateTime restoretime, long[]? versions, bool singleTimeMatch, [EnumeratorCancellation] CancellationToken token) { if (restoretime.Kind == DateTimeKind.Unspecified) throw new Exception("Invalid DateTime given, must be either local or UTC"); (var wherequery, var values) = - await GetFilelistWhereClause(restoretime, versions, null, singleTimeMatch, token) + await GetFilelistWhereClauseAsync(restoretime, versions, null, singleTimeMatch, token) .ConfigureAwait(false); var res = new List(); await using var cmd = m_connection.CreateCommand(); @@ -1514,13 +1514,13 @@ namespace Duplicati.Library.Main.Database /// Optional array of versions to match against the filesets. /// Cancellation token to monitor for cancellation requests. /// An asynchronous task that returns a collection of fileset IDs that match the criteria. - public async Task> FindMatchingFilesets(DateTime restoretime, long[]? versions, CancellationToken token) + public async Task> FindMatchingFilesetsAsync(DateTime restoretime, long[]? versions, CancellationToken token) { if (restoretime.Kind == DateTimeKind.Unspecified) throw new Exception("Invalid DateTime given, must be either local or UTC"); var (wherequery, args) = - await GetFilelistWhereClause(restoretime, versions, null, true, token) + await GetFilelistWhereClauseAsync(restoretime, versions, null, true, token) .ConfigureAwait(false); var res = new List(); @@ -1546,7 +1546,7 @@ namespace Duplicati.Library.Main.Database /// The timestamp of the fileset to check. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns true if the fileset is a full backup, otherwise false. - public async Task IsFilesetFullBackup(DateTime filesetTime, CancellationToken token) + public async Task IsFilesetFullBackupAsync(DateTime filesetTime, CancellationToken token) { await using var cmd = m_connection.CreateCommand(); cmd.SetCommandAndParameters($@" @@ -1569,7 +1569,7 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of key-value pairs representing the database options. - private async IAsyncEnumerable> GetDbOptionList([EnumeratorCancellation] CancellationToken token) + private async IAsyncEnumerable> GetDbOptionListAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -1592,9 +1592,9 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns a dictionary containing all database options. - public async Task> GetDbOptions(CancellationToken token) + public async Task> GetDbOptionsAsync(CancellationToken token) { - var res = await GetDbOptionList(token) + var res = await GetDbOptionListAsync(token) .ToDictionaryAsync(x => x.Key, x => x.Value, cancellationToken: token) .ConfigureAwait(false); @@ -1608,16 +1608,16 @@ namespace Duplicati.Library.Main.Database /// The value to set. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the option has been updated. - private async Task UpdateDbOption(string key, bool value, CancellationToken token) + private async Task UpdateDbOptionAsync(string key, bool value, CancellationToken token) { - var opts = await GetDbOptions(token).ConfigureAwait(false); + var opts = await GetDbOptionsAsync(token).ConfigureAwait(false); if (value) opts[key] = "true"; else opts.Remove(key); - await SetDbOptions(opts, token).ConfigureAwait(false); + await SetDbOptionsAsync(opts, token).ConfigureAwait(false); await m_rtr.CommitAsync(token: token).ConfigureAwait(false); } @@ -1627,17 +1627,17 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// Optional value to set the flag to. If null, the current value in the database is returned. /// A task that, when awaited, returns true if a repair is in progress, otherwise false. - public async Task RepairInProgress(CancellationToken token, bool? value = null) + public async Task RepairInProgressAsync(CancellationToken token, bool? value = null) { if (value is bool v) { - await UpdateDbOption("repair-in-progress", v, token) + await UpdateDbOptionAsync("repair-in-progress", v, token) .ConfigureAwait(false); return v; } - var opts = await GetDbOptions(token).ConfigureAwait(false); + var opts = await GetDbOptionsAsync(token).ConfigureAwait(false); return opts.ContainsKey("repair-in-progress"); } @@ -1647,17 +1647,17 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// Optional value to set the flag to. If null, the current value in the database is returned. /// A task that, when awaited, returns true if the database has been partially recreated, otherwise false. - public async Task PartiallyRecreated(CancellationToken token, bool? value = null) + public async Task PartiallyRecreatedAsync(CancellationToken token, bool? value = null) { if (value is bool v) { - await UpdateDbOption("partially-recreated", v, token) + await UpdateDbOptionAsync("partially-recreated", v, token) .ConfigureAwait(false); return v; } - var opts = await GetDbOptions(token).ConfigureAwait(false); + var opts = await GetDbOptionsAsync(token).ConfigureAwait(false); return opts.ContainsKey("partially-recreated"); } @@ -1667,17 +1667,17 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// Optional value to set the flag to. If null, the current value in the database is returned. /// A task that, when awaited, returns true if the database has been terminated with active uploads, otherwise false. - public async Task TerminatedWithActiveUploads(CancellationToken token, bool? value = null) + public async Task TerminatedWithActiveUploadsAsync(CancellationToken token, bool? value = null) { if (value is bool v) { - await UpdateDbOption("terminated-with-active-uploads", v, token) + await UpdateDbOptionAsync("terminated-with-active-uploads", v, token) .ConfigureAwait(false); return v; } - var opts = await GetDbOptions(token).ConfigureAwait(false); + var opts = await GetDbOptionsAsync(token).ConfigureAwait(false); return opts.ContainsKey("terminated-with-active-uploads"); } @@ -1687,7 +1687,7 @@ namespace Duplicati.Library.Main.Database /// The options to set. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the options have been set. - public async Task SetDbOptions(IDictionary options, CancellationToken token) + public async Task SetDbOptionsAsync(IDictionary options, CancellationToken token) { await using var cmd = m_connection.CreateCommand(); await cmd.ExecuteNonQueryAsync(@" @@ -1721,7 +1721,7 @@ namespace Duplicati.Library.Main.Database /// The size in bytes to compare against. /// Cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the count of blocks larger than the specified size. - public async Task GetBlocksLargerThan(long fhblocksize, CancellationToken token) + public async Task GetBlocksLargerThanAsync(long fhblocksize, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" SELECT COUNT(*) @@ -1744,8 +1744,8 @@ namespace Duplicati.Library.Main.Database /// Also verify filelists (can be slow). /// Cancellation token to monitor for cancellation requests. /// A task that completes when the consistency check is finished. - public async Task VerifyConsistency(long blocksize, long hashsize, bool verifyfilelists, CancellationToken token) - => await VerifyConsistencyInner(blocksize, hashsize, verifyfilelists, false, token) + public async Task VerifyConsistencyAsync(long blocksize, long hashsize, bool verifyfilelists, CancellationToken token) + => await VerifyConsistencyInnerAsync(blocksize, hashsize, verifyfilelists, false, token) .ConfigureAwait(false); /// @@ -1756,8 +1756,8 @@ namespace Duplicati.Library.Main.Database /// Also verify filelists (can be slow). /// Cancellation token to monitor for cancellation requests. /// A task that completes when the consistency check is finished. - public async Task VerifyConsistencyForRepair(long blocksize, long hashsize, bool verifyfilelists, CancellationToken token) - => await VerifyConsistencyInner(blocksize, hashsize, verifyfilelists, true, token) + public async Task VerifyConsistencyForRepairAsync(long blocksize, long hashsize, bool verifyfilelists, CancellationToken token) + => await VerifyConsistencyInnerAsync(blocksize, hashsize, verifyfilelists, true, token) .ConfigureAwait(false); /// @@ -1769,7 +1769,7 @@ namespace Duplicati.Library.Main.Database /// Disable verify for errors that will be fixed by repair. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the consistency check is finished. - private async Task VerifyConsistencyInner(long blocksize, long hashsize, bool verifyfilelists, bool laxVerifyForRepair, CancellationToken token) + private async Task VerifyConsistencyInnerAsync(long blocksize, long hashsize, bool verifyfilelists, bool laxVerifyForRepair, CancellationToken token) { await using var cmd = m_connection.CreateCommand() .SetTransaction(m_rtr); @@ -2048,7 +2048,7 @@ namespace Duplicati.Library.Main.Database filesetIds.Add(filesetIdReader.ConvertValueToInt64(0)); } - var pairs = FilesetTimes(token) + var pairs = FilesetTimesAsync(token) .Select((x, i) => new { FilesetId = x.Key, Version = i, Time = x.Value }) .Where(x => filesetIds.Contains(x.FilesetId)) .Select(x => $"Fileset {x.Version}: {x.Time} (id = {x.FilesetId})"); @@ -2105,9 +2105,9 @@ namespace Duplicati.Library.Main.Database if (expandedlist != storedlist) { var filesetname = filesetid.ToString(); - var fileset = await FilesetTimes(token) + var fileset = await FilesetTimesAsync(token) .Zip( - AsyncEnumerable.Range(0, await FilesetTimes(token) + AsyncEnumerable.Range(0, await FilesetTimesAsync(token) .CountAsync(cancellationToken: token) .ConfigureAwait(false) ), (a, b) => new Tuple(b, a.Key, a.Value) @@ -2162,7 +2162,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the volume to retrieve blocks for. /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of blocks associated with the specified volume ID. - public async IAsyncEnumerable GetBlocks(long volumeid, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetBlocksAsync(long volumeid, [EnumeratorCancellation] CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" WITH AllBlocks AS ( @@ -2467,7 +2467,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the fileset to write. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the fileset has been written. - public async Task WriteFileset(Volumes.FilesetVolumeWriter filesetvolume, long filesetId, CancellationToken token) + public async Task WriteFilesetAsync(Volumes.FilesetVolumeWriter filesetvolume, long filesetId, CancellationToken token) { await using var cmd = m_connection.CreateCommand() .SetCommandAndParameters(LIST_FOLDERS_AND_SYMLINKS) @@ -2556,7 +2556,7 @@ namespace Duplicati.Library.Main.Database if (metablockhash == metahash) metablockhash = null; - await filesetvolume.AddFile( + await filesetvolume.AddFileAsync( path, filehash, size, @@ -2587,7 +2587,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the volume to link the fileset to. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the link operation is finished. - public async Task LinkFilesetToVolume(long filesetid, long volumeid, CancellationToken token) + public async Task LinkFilesetToVolumeAsync(long filesetid, long volumeid, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" UPDATE ""Fileset"" @@ -2612,7 +2612,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the fileset whose timestamp changes will be pushed. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the timestamp changes have been pushed. - public async Task PushTimestampChangesToPreviousVersion(long filesetId, CancellationToken token) + public async Task PushTimestampChangesToPreviousVersionAsync(long filesetId, CancellationToken token) { var query = @" UPDATE ""FilesetEntry"" AS ""oldVersion"" @@ -2822,7 +2822,7 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// A task that completes when the renaming operation is finished. /// Thrown if the renaming operation does not affect exactly one row. - public async Task RenameRemoteFile(string oldname, string newname, CancellationToken token) + public async Task RenameRemoteFileAsync(string oldname, string newname, CancellationToken token) { //Rename the old entry, to preserve ID links await using var cmd = await m_connection.CreateCommandAsync(@" @@ -2859,13 +2859,13 @@ namespace Duplicati.Library.Main.Database //Create a fake new entry with the old name and mark as deleting // as this ensures we will remove it, if it shows up in some later listing var newvolId = - await RegisterRemoteVolume(oldname, type, RemoteVolumeState.Deleting, token) + await RegisterRemoteVolumeAsync(oldname, type, RemoteVolumeState.Deleting, token) .ConfigureAwait(false); // IF needed, also create an empty fileset, so the validation works if (type == RemoteVolumeType.Files) { - await CreateFileset(newvolId, DateTime.UnixEpoch, token) + await CreateFilesetAsync(newvolId, DateTime.UnixEpoch, token) .ConfigureAwait(false); await m_rtr.CommitAsync(token: token).ConfigureAwait(false); } @@ -2878,7 +2878,7 @@ namespace Duplicati.Library.Main.Database /// The timestamp of the operation to create. /// Cancellation token to monitor for cancellation requests. /// A task that when awaited contains the ID of the newly created fileset. - public virtual async Task CreateFileset(long volumeid, DateTime timestamp, CancellationToken token) + public virtual async Task CreateFilesetAsync(long volumeid, DateTime timestamp, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" INSERT INTO ""Fileset"" ( @@ -2917,7 +2917,7 @@ namespace Duplicati.Library.Main.Database /// Cancellation token to monitor for cancellation requests. /// A task that completes when the link has been added. /// Thrown if either volume ID is less than or equal to 0. - public async Task AddIndexBlockLink(long indexVolumeID, long blockVolumeID, CancellationToken token) + public async Task AddIndexBlockLinkAsync(long indexVolumeID, long blockVolumeID, CancellationToken token) { if (indexVolumeID <= 0) throw new ArgumentOutOfRangeException(nameof(indexVolumeID), "Index volume ID must be greater than 0."); @@ -2940,7 +2940,7 @@ namespace Duplicati.Library.Main.Database /// The size of the hash. /// Cancellation token to monitor for cancellation requests. /// An enumerable of tuples containing the blocklist hash, the blocklist data and the length of the data - public async IAsyncEnumerable<(string Hash, byte[] Buffer, int Size)> GetBlocklists(long volumeid, long blocksize, int hashsize, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(string Hash, byte[] Buffer, int Size)> GetBlocklistsAsync(long volumeid, long blocksize, int hashsize, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); // Group subquery by hash to ensure that each blocklist hash appears only once in the result @@ -3035,7 +3035,7 @@ namespace Duplicati.Library.Main.Database /// Full backup state. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the update is finished. - public async Task UpdateFullBackupStateInFileset(long fileSetId, bool isFullBackup, CancellationToken token) + public async Task UpdateFullBackupStateInFilesetAsync(long fileSetId, bool isFullBackup, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" UPDATE ""Fileset"" @@ -3057,7 +3057,7 @@ namespace Duplicati.Library.Main.Database /// The fileset ID to clear. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the entries have been cleared. - public async Task ClearFilesetEntries(long filesetId, CancellationToken token) + public async Task ClearFilesetEntriesAsync(long filesetId, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" DELETE FROM ""FilesetEntry"" @@ -3077,13 +3077,13 @@ namespace Duplicati.Library.Main.Database /// The transaction to use. /// Cancellation token to monitor for cancellation requests. /// A task that when awaited returns the last incomplete fileset or default - public async Task GetLastIncompleteFilesetVolume(CancellationToken token) + public async Task GetLastIncompleteFilesetVolumeAsync(CancellationToken token) { - var candidates = GetIncompleteFilesets(token) + var candidates = GetIncompleteFilesetsAsync(token) .OrderBy(x => x.Value); if (await candidates.AnyAsync(cancellationToken: token).ConfigureAwait(false)) - return await GetRemoteVolumeFromFilesetID( + return await GetRemoteVolumeFromFilesetIDAsync( ( await candidates.LastAsync(cancellationToken: token).ConfigureAwait(false) ).Key @@ -3098,7 +3098,7 @@ namespace Duplicati.Library.Main.Database /// /// Cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of key-value pairs where the key is the fileset ID and the value is the timestamp of the fileset. - public async IAsyncEnumerable> GetIncompleteFilesets([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> GetIncompleteFilesetsAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@$" SELECT DISTINCT @@ -3140,7 +3140,7 @@ namespace Duplicati.Library.Main.Database /// The fileset ID. /// Cancellation token to monitor for cancellation requests. /// A task that when awaited returns the remote volume entry associated with the fileset ID, or default if not found. - public async Task GetRemoteVolumeFromFilesetID(long filesetID, CancellationToken token) + public async Task GetRemoteVolumeFromFilesetIDAsync(long filesetID, CancellationToken token) { await using var cmd = await m_connection.CreateCommandAsync(@" SELECT @@ -3189,7 +3189,7 @@ namespace Duplicati.Library.Main.Database /// The threshold date and time; all log data and remote operations older than this will be purged. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the purge operation is finished. - public async Task PurgeLogData(DateTime threshold, CancellationToken token) + public async Task PurgeLogDataAsync(DateTime threshold, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var t = Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(threshold); @@ -3218,7 +3218,7 @@ namespace Duplicati.Library.Main.Database /// /// The threshold date and time; all deleted remote volumes older than this will be purged. /// A task that completes when the purge operation is finished. - public async Task PurgeDeletedVolumes(DateTime threshold, CancellationToken token) + public async Task PurgeDeletedVolumesAsync(DateTime threshold, CancellationToken token) { await m_removedeletedremotevolumeCommand .SetTransaction(m_rtr) @@ -3238,7 +3238,7 @@ namespace Duplicati.Library.Main.Database /// The size of the empty blockset. /// A cancellation token to cancel the operation. /// A task that when awaited contains the ID of the empty metadata blockset, or -1 if no suitable blockset is found - public async Task GetEmptyMetadataBlocksetId(IEnumerable blockVolumeIds, string emptyHash, long emptyHashSize, CancellationToken token) + public async Task GetEmptyMetadataBlocksetIdAsync(IEnumerable blockVolumeIds, string emptyHash, long emptyHashSize, CancellationToken token) { await using var tmptable = await TemporaryDbValueList.CreateAsync(this, blockVolumeIds, token) .ConfigureAwait(false); @@ -3365,7 +3365,7 @@ namespace Duplicati.Library.Main.Database } } - m_connection.Close(); + await m_connection.CloseAsync(); } await m_connection.DisposeAsync().ConfigureAwait(false); @@ -3427,7 +3427,7 @@ namespace Duplicati.Library.Main.Database /// The results to write. /// Cancellation token to monitor for cancellation requests. /// A task that completes when the results have been written and committed. - public async Task WriteResultsAndCommit(IBasicResults result, CancellationToken token) + public async Task WriteResultsAndCommitAsync(IBasicResults result, CancellationToken token) { if (IsDisposed) return; @@ -3436,13 +3436,13 @@ namespace Duplicati.Library.Main.Database { if (result is BasicResults basicResults) { - await basicResults.FlushLog(this, token).ConfigureAwait(false); + await basicResults.FlushLogAsync(this, token).ConfigureAwait(false); if (basicResults.EndTime.Ticks == 0) basicResults.EndTime = DateTime.UtcNow; } var serializer = new JsonFormatSerializer(); - await LogMessage("Result", + await LogMessageAsync("Result", serializer.SerializeResults(result), null, token @@ -3468,7 +3468,7 @@ namespace Duplicati.Library.Main.Database /// The path to get the prefix for. /// Cancellation token to monitor for cancellation requests. /// A task that when awaited returns the path prefix ID. - public async Task GetOrCreatePathPrefix(string prefix, CancellationToken token) + public async Task GetOrCreatePathPrefixAsync(string prefix, CancellationToken token) { // Ring-buffer style lookup for (var i = 0; i < m_pathPrefixLookup.Length; i++) diff --git a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs index d4067be18..1be856f12 100644 --- a/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalDeleteDatabase.cs @@ -120,7 +120,7 @@ namespace Duplicati.Library.Main.Database /// The fileset entries to delete. /// A cancellation token to cancel the operation. /// An async enumerable of key-value pairs, where the key is the fileset name and the value is the size of the fileset. - public async IAsyncEnumerable> DropFilesetsFromTable(DateTime[] toDelete, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> DropFilesetsFromTableAsync(DateTime[] toDelete, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var deleted = 0; @@ -300,7 +300,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An async enumerable of IListResultFileset. - internal async IAsyncEnumerable FilesetsWithBackupVersion([EnumeratorCancellation] CancellationToken token) + internal async IAsyncEnumerable FilesetsWithBackupVersionAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); // TODO check if this is still the case? (shouldn't be with new sqlite driver): @@ -347,9 +347,9 @@ namespace Duplicati.Library.Main.Database /// Checks whether any non-fileset volumes (block or index) referenced by the given fileset have an active lock. /// The lock expiration is stored as epoch seconds in the database. /// - internal async Task HasAnyLockedFiles(long filesetId, DateTime nowUtc, CancellationToken token) + internal async Task HasAnyLockedFilesAsync(long filesetId, DateTime nowUtc, CancellationToken token) { - await foreach (var (_, lockUntil) in GetRemoteVolumesDependingOnFilesets([filesetId], token).ConfigureAwait(false)) + await foreach (var (_, lockUntil) in GetRemoteVolumesDependingOnFilesetsAsync([filesetId], token).ConfigureAwait(false)) if (lockUntil.HasValue && lockUntil.Value.ToUniversalTime() > nowUtc) return true; @@ -405,7 +405,7 @@ namespace Duplicati.Library.Main.Database /// the size of the data stored in the volume, the size of the data that is no longer needed in the volume, /// and the size of the data that is compressed in the volume. /// - private async IAsyncEnumerable GetWastedSpaceReport([EnumeratorCancellation] CancellationToken token) + private async IAsyncEnumerable GetWastedSpaceReportAsync([EnumeratorCancellation] CancellationToken token) { var tmptablename = $"UsageReport-{Library.Utility.Utility.GetHexGuid()}"; @@ -764,14 +764,14 @@ namespace Duplicati.Library.Main.Database /// The maximum number of small files to trigger compaction. /// A cancellation token to cancel the operation. /// A task that when awaited contains an instance of . - public async Task GetCompactReport(long volsize, long wastethreshold, long smallfilesize, long maxsmallfilecount, CancellationToken token) + public async Task GetCompactReportAsync(long volsize, long wastethreshold, long smallfilesize, long maxsmallfilecount, CancellationToken token) { return new CompactReport( volsize, wastethreshold, smallfilesize, maxsmallfilecount, - await GetWastedSpaceReport(token) + await GetWastedSpaceReportAsync(token) .ToListAsync(cancellationToken: token) .ConfigureAwait(false) ); @@ -791,7 +791,7 @@ namespace Duplicati.Library.Main.Database /// The volume ID to check, or -1 to check all volumes. /// A cancellation token to cancel the operation. /// A task that when awaited returns true if the block is in use, false otherwise. - Task UseBlock(string hash, long size, long volumeId, CancellationToken token); + Task UseBlockAsync(string hash, long size, long volumeId, CancellationToken token); } /// @@ -844,7 +844,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task UseBlock(string hash, long size, long volumeId, CancellationToken token) + public async Task UseBlockAsync(string hash, long size, long volumeId, CancellationToken token) { var r = await m_command .SetTransaction(m_db.Transaction) @@ -880,7 +880,7 @@ namespace Duplicati.Library.Main.Database /// Builds a lookup table to enable faster response to block queries. /// /// A cancellation token to cancel the operation. - public async Task CreateBlockQueryHelper(CancellationToken token) + public async Task CreateBlockQueryHelperAsync(CancellationToken token) { return await BlockQuery.CreateAsync(this, token).ConfigureAwait(false); } @@ -893,7 +893,7 @@ namespace Duplicati.Library.Main.Database /// The new volume ID. /// A cancellation token to cancel the operation. /// A task that when completed indicates the block has been registered. - public async Task RegisterDuplicatedBlock(string hash, long size, long volumeID, CancellationToken token) + public async Task RegisterDuplicatedBlockAsync(string hash, long size, long volumeID, CancellationToken token) { // Using INSERT OR IGNORE to avoid duplicate entries, result may be 1 or 0 await m_registerDuplicateBlockCommand @@ -912,9 +912,9 @@ namespace Duplicati.Library.Main.Database /// The volume IDs that will be removed. /// A cancellation token to cancel the operation. /// A task that when completed indicates the operation has finished. - public async Task PrepareForDelete(string filename, IEnumerable volumeIdsToBeRemoved, CancellationToken token) + public async Task PrepareForDeleteAsync(string filename, IEnumerable volumeIdsToBeRemoved, CancellationToken token) { - var deletedVolume = await GetRemoteVolume(filename, token) + var deletedVolume = await GetRemoteVolumeAsync(filename, token) .ConfigureAwait(false); if (deletedVolume.Type != RemoteVolumeType.Blocks) return; @@ -1043,7 +1043,7 @@ namespace Duplicati.Library.Main.Database /// Block volumes slated for deletion. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of that represents the order in which volumes should be deleted. - public async IAsyncEnumerable ReOrderDeleteableVolumes(IEnumerable deleteableVolumes, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable ReOrderDeleteableVolumesAsync(IEnumerable deleteableVolumes, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); // Although the generated index volumes are always in pairs, diff --git a/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs b/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs index 4d354a65d..352d83b14 100644 --- a/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListAffectedDatabase.cs @@ -130,9 +130,9 @@ namespace Duplicati.Library.Main.Database /// The items to filter the filesets by. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of fileset times. - public async IAsyncEnumerable GetFilesets(IEnumerable items, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesetsAsync(IEnumerable items, [EnumeratorCancellation] CancellationToken token) { - var filesets = await FilesetTimes(token) + var filesets = await FilesetTimesAsync(token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); @@ -201,7 +201,7 @@ namespace Duplicati.Library.Main.Database /// The items to filter the files by. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of file results. - public async IAsyncEnumerable GetFiles(IEnumerable items, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesAsync(IEnumerable items, [EnumeratorCancellation] CancellationToken token) { var sql = $@" SELECT DISTINCT ""Path"" @@ -286,7 +286,7 @@ namespace Duplicati.Library.Main.Database /// The items to filter the log lines by. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of log line results. - public async IAsyncEnumerable GetLogLines(IEnumerable items, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetLogLinesAsync(IEnumerable items, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(); @@ -336,7 +336,7 @@ namespace Duplicati.Library.Main.Database /// The names of the remote volumes to filter by. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of remote volume results. - public async IAsyncEnumerable GetVolumes(IEnumerable items, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetVolumesAsync(IEnumerable items, [EnumeratorCancellation] CancellationToken token) { var itemsList = items.ToList(); if (itemsList.Count == 0) diff --git a/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs b/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs index 1e2c70f51..be3919f17 100644 --- a/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListBrokenFilesDatabase.cs @@ -206,10 +206,10 @@ namespace Duplicati.Library.Main.Database /// Optional array of versions to filter filesets by. If null, all versions are considered. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of broken file IDs. - public async IAsyncEnumerable<(DateTime FilesetTime, long FilesetID, long RemoveFileCount)> GetBrokenFilesets(DateTime time, long[]? versions, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(DateTime FilesetTime, long FilesetID, long RemoveFileCount)> GetBrokenFilesetsAsync(DateTime time, long[]? versions, [EnumeratorCancellation] CancellationToken token) { var query = BROKEN_FILE_SETS; - var clause = await GetFilelistWhereClause(time, versions, null, false, token) + var clause = await GetFilelistWhereClauseAsync(time, versions, null, false, token) .ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(clause.Query)) @@ -228,7 +228,7 @@ namespace Duplicati.Library.Main.Database .SetParameterValues(clause.Values); await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(token).ConfigureAwait(false)) - if (!rd.IsDBNull(0)) + if (!await rd.IsDBNullAsync(0)) yield return ( ParseFromEpochSeconds(rd.ConvertValueToInt64(0, 0)), rd.ConvertValueToInt64(1, -1), @@ -242,14 +242,14 @@ namespace Duplicati.Library.Main.Database /// The fileset ID to filter by. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of broken file IDs. - public async IAsyncEnumerable> GetBrokenFilenames(long filesetid, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> GetBrokenFilenamesAsync(long filesetid, [EnumeratorCancellation] CancellationToken token) { await using var cmd = Connection.CreateCommand(m_rtr) .SetCommandAndParameters(BROKEN_FILE_NAMES) .SetParameterValue("@FilesetId", filesetid); await foreach (var rd in cmd.ExecuteReaderEnumerableAsync(token).ConfigureAwait(false)) - if (!rd.IsDBNull(0)) + if (!await rd.IsDBNullAsync(0)) yield return new Tuple( rd.ConvertValueToString(0) ?? throw new Exception("Filename was null"), rd.ConvertValueToInt64(1) @@ -261,7 +261,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the orphaned index files. - public async IAsyncEnumerable GetOrphanedIndexFiles([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetOrphanedIndexFilesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = Connection.CreateCommand($@" SELECT @@ -294,7 +294,7 @@ namespace Duplicati.Library.Main.Database /// The name of the ID field in the table. /// A cancellation token to cancel the operation. /// A task that completes when the insertion is finished. - public async Task InsertBrokenFileIDsIntoTable(long filesetid, string tablename, string IDfieldname, CancellationToken token) + public async Task InsertBrokenFileIDsIntoTableAsync(long filesetid, string tablename, string IDfieldname, CancellationToken token) { await using var cmd = Connection.CreateCommand(m_rtr) .SetCommandAndParameters(INSERT_BROKEN_IDS(tablename, IDfieldname)) @@ -312,7 +312,7 @@ namespace Duplicati.Library.Main.Database /// The empty blockset ID to replace with. /// A cancellation token to cancel the operation. /// A task that when awaited contains the number of rows affected - public async Task ReplaceMetadata(long filesetId, long emptyBlocksetId, CancellationToken token) + public async Task ReplaceMetadataAsync(long filesetId, long emptyBlocksetId, CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" UPDATE ""Metadataset"" @@ -348,7 +348,7 @@ namespace Duplicati.Library.Main.Database /// The names of the volumes to check for missing blocks. /// A cancellation token to cancel the operation. /// A task that completes when the removal is finished. - public async Task RemoveMissingBlocks(IEnumerable names, CancellationToken token) + public async Task RemoveMissingBlocksAsync(IEnumerable names, CancellationToken token) { if (names == null || !names.Any()) return; @@ -432,7 +432,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the fileset to count files in. /// A cancellation token to cancel the operation. /// A task that when awaited contains the count of files in the specified fileset. - public async Task GetFilesetFileCount(long filesetid, CancellationToken token) + public async Task GetFilesetFileCountAsync(long filesetid, CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT COUNT(*) diff --git a/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs b/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs index 57320b983..e446e2931 100644 --- a/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListChangesDatabase.cs @@ -77,7 +77,7 @@ namespace Duplicati.Library.Main.Database /// If true, logs the SQL query. /// A cancellation token to cancel the operation. /// A task that completes when the element is added. - Task AddElement(string path, string filehash, string metahash, long size, Interface.ListChangesElementType type, bool asNew, bool logQuery, CancellationToken token); + Task AddElementAsync(string path, string filehash, string metahash, long size, Interface.ListChangesElementType type, bool asNew, bool logQuery, CancellationToken token); /// /// Adds elements from the database to the temporary storage. @@ -87,28 +87,28 @@ namespace Duplicati.Library.Main.Database /// An optional filter to apply when adding elements. /// A cancellation token to cancel the operation. /// A task that completes when the elements are added. - Task AddFromDb(long filesetId, bool asNew, IFilter filter, CancellationToken token); + Task AddFromDbAsync(long filesetId, bool asNew, IFilter filter, CancellationToken token); /// /// Creates a report containing the count of added, deleted, and modified elements. /// /// A cancellation token to cancel the operation. /// A task that, when awaited, returns an with the change counts. - Task CreateChangeCountReport(CancellationToken token); + Task CreateChangeCountReportAsync(CancellationToken token); /// /// Creates a report containing the size information for added, deleted, previous, and current elements. /// /// A cancellation token to cancel the operation. /// A task that, when awaited, returns an with the size details. - Task CreateChangeSizeReport(CancellationToken token); + Task CreateChangeSizeReportAsync(CancellationToken token); /// /// Asynchronously generates a report of changed files, yielding tuples that describe the change type, element type, and file path. /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of tuples containing the change type, element type, and file path. - IAsyncEnumerable> CreateChangedFileReport(CancellationToken token); + IAsyncEnumerable> CreateChangedFileReportAsync(CancellationToken token); } /// @@ -335,7 +335,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task AddFromDb(long filesetId, bool asNew, IFilter filter, CancellationToken token) + public async Task AddFromDbAsync(long filesetId, bool asNew, IFilter filter, CancellationToken token) { var tablename = asNew ? m_currentTable : m_previousTable; @@ -565,7 +565,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task AddElement(string path, string filehash, string metahash, long size, Interface.ListChangesElementType type, bool asNew, bool logQuery, CancellationToken token) + public async Task AddElementAsync(string path, string filehash, string metahash, long size, Interface.ListChangesElementType type, bool asNew, bool logQuery, CancellationToken token) { var cmd = asNew ? m_insertCurrentElementCommand : m_insertPreviousElementCommand; await cmd @@ -584,7 +584,7 @@ namespace Duplicati.Library.Main.Database /// The SqliteDataReader to read from. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of strings, where each string is a value from the first column of the reader. - private static async IAsyncEnumerable ReaderToStringList(SqliteDataReader rd, [EnumeratorCancellation] CancellationToken token) + private static async IAsyncEnumerable ReaderToStringListAsync(SqliteDataReader rd, [EnumeratorCancellation] CancellationToken token) { await using (rd) while (await rd.ReadAsync(token).ConfigureAwait(false)) @@ -647,7 +647,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that, when awaited, returns an with the size details. - public async Task CreateChangeSizeReport(CancellationToken token) + public async Task CreateChangeSizeReportAsync(CancellationToken token) { var (Added, Deleted, Modified) = GetSqls(true); @@ -689,7 +689,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that, when awaited, returns an with the change counts. - public async Task CreateChangeCountReport(CancellationToken token) + public async Task CreateChangeCountReportAsync(CancellationToken token) { var (Added, Deleted, Modified) = GetSqls(false); @@ -767,7 +767,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of tuples containing the change type, element type, and file path. - public async IAsyncEnumerable> CreateChangedFileReport([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> CreateChangedFileReportAsync([EnumeratorCancellation] CancellationToken token) { var (Added, Deleted, Modified) = GetSqls(false); @@ -783,7 +783,7 @@ namespace Duplicati.Library.Main.Database { cmd.SetCommandAndParameters(sql); foreach (var type in elTypes) - await foreach (var s in ReaderToStringList(await cmd.SetParameterValue("@Type", (int)type).ExecuteReaderAsync(token).ConfigureAwait(false), token).ConfigureAwait(false)) + await foreach (var s in ReaderToStringListAsync(await cmd.SetParameterValue("@Type", (int)type).ExecuteReaderAsync(token).ConfigureAwait(false), token).ConfigureAwait(false)) yield return new Tuple(changeType, type, s ?? ""); } @@ -858,7 +858,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that, when awaited, returns an instance of . - public async Task CreateStorageHelper(CancellationToken token) + public async Task CreateStorageHelperAsync(CancellationToken token) { return await StorageHelper.CreateAsync(this, token).ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Database/LocalListDatabase.cs b/Duplicati/Library/Main/Database/LocalListDatabase.cs index 4c6e49e21..d5f78b2a5 100644 --- a/Duplicati/Library/Main/Database/LocalListDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalListDatabase.cs @@ -74,7 +74,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of file sizes. - IAsyncEnumerable Sizes(CancellationToken token); + IAsyncEnumerable SizesAsync(CancellationToken token); } /// @@ -114,40 +114,40 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of filesets. - IAsyncEnumerable Sets(CancellationToken token); + IAsyncEnumerable SetsAsync(CancellationToken token); /// /// Gets all of the filesets in the database as an asynchronous enumerable, omitting file count and total size. /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of filesets. - IAsyncEnumerable QuickSets(CancellationToken token); + IAsyncEnumerable QuickSetsAsync(CancellationToken token); /// /// Gets the files in the filesets that match the provided filter as an asynchronous enumerable. /// /// The filter to apply to the files. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of file versions representing the selected files. - IAsyncEnumerable SelectFiles(IFilter filter, CancellationToken token); + IAsyncEnumerable SelectFilesAsync(IFilter filter, CancellationToken token); /// /// Gets the largest prefix of files in the filesets that match the provided filter as an asynchronous enumerable. /// /// The filter to apply to the files. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of file versions representing the largest prefix. - IAsyncEnumerable GetLargestPrefix(IFilter filter, CancellationToken token); + IAsyncEnumerable GetLargestPrefixAsync(IFilter filter, CancellationToken token); /// /// Selects the contents of a folder in the filesets that match the provided filter as an asynchronous enumerable. /// /// The filter to apply to the folder contents. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of file versions representing the folder contents. - IAsyncEnumerable SelectFolderContents(IFilter filter, CancellationToken token); + IAsyncEnumerable SelectFolderContentsAsync(IFilter filter, CancellationToken token); /// /// Deletes all filesets except the most recent one. /// /// A cancellation token to cancel the operation. /// A task that completes when the filesets have been deleted. - Task TakeFirst(CancellationToken token); + Task TakeFirstAsync(CancellationToken token); } /// @@ -196,12 +196,12 @@ namespace Duplicati.Library.Main.Database { m_db = owner, m_filesets = await owner - .FilesetTimes(token) + .FilesetTimesAsync(token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false), m_tablename = $"Filesets-{Library.Utility.Utility.GetHexGuid()}" }; - var (Query, Values) = await owner.GetFilelistWhereClause(time, versions, fs.m_filesets, false, token) + var (Query, Values) = await owner.GetFilelistWhereClauseAsync(time, versions, fs.m_filesets, false, token) .ConfigureAwait(false); await using (var cmd = fs.m_db.Connection.CreateCommand()) @@ -303,7 +303,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable Sizes([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable SizesAsync([EnumeratorCancellation] CancellationToken token) { while (More && Path == m_reader.ConvertValueToString(0)) { @@ -321,13 +321,13 @@ namespace Duplicati.Library.Main.Database /// public string? Path { get; internal set; } /// - public IAsyncEnumerable Sizes(CancellationToken token) => AsyncEnumerable.Empty(); + public IAsyncEnumerable SizesAsync(CancellationToken token) => AsyncEnumerable.Empty(); } /// - public IAsyncEnumerable GetLargestPrefix(IFilter filter, CancellationToken token) + public IAsyncEnumerable GetLargestPrefixAsync(IFilter filter, CancellationToken token) { - return GetLargestPrefix(filter, null, token); + return GetLargestPrefixAsync(filter, null, token); } /// @@ -337,7 +337,7 @@ namespace Duplicati.Library.Main.Database /// The prefix rule to apply, or null if no prefix rule is specified. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of file versions representing the largest prefix. - private async IAsyncEnumerable GetLargestPrefix(IFilter filter, string? prefixrule, [EnumeratorCancellation] CancellationToken token) + private async IAsyncEnumerable GetLargestPrefixAsync(IFilter filter, string? prefixrule, [EnumeratorCancellation] CancellationToken token) { await using var tmpnames = await FilteredFilenameTable .CreateFilteredFilenameTableAsync(m_db, filter, token) @@ -447,7 +447,7 @@ namespace Duplicati.Library.Main.Database var result = roots .Concat(rootsUNC) - .Select(x => GetLargestPrefix(filter, x, token) + .Select(x => GetLargestPrefixAsync(filter, x, token) .FirstAsync()) .Distinct(); @@ -469,7 +469,7 @@ namespace Duplicati.Library.Main.Database /// The name of the table to query. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of folder entries. - private static async IAsyncEnumerable SelectFolderEntries(SqliteCommand cmd, string prefix, string table, [EnumeratorCancellation] CancellationToken token) + private static async IAsyncEnumerable SelectFolderEntriesAsync(SqliteCommand cmd, string prefix, string table, [EnumeratorCancellation] CancellationToken token) { if (!string.IsNullOrEmpty(prefix)) prefix = Util.AppendDirSeparator(prefix, Util.GuessDirSeparator(prefix)); @@ -498,7 +498,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable SelectFolderContents(IFilter filter, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable SelectFolderContentsAsync(IFilter filter, [EnumeratorCancellation] CancellationToken token) { var tbname = $"Filenames-{Library.Utility.Utility.GetHexGuid()}"; try @@ -564,7 +564,7 @@ namespace Duplicati.Library.Main.Database "); using (new Logging.Timer(LOGTAG, "ListFolderContents", "Inserting paths into table")) - await foreach (var n in SelectFolderEntries(cmd, pathprefix, tmpnames.Tablename, token).Distinct().ConfigureAwait(false)) + await foreach (var n in SelectFolderEntriesAsync(cmd, pathprefix, tmpnames.Tablename, token).Distinct().ConfigureAwait(false)) await c2 .SetParameterValue("@Path", n) .ExecuteNonQueryAsync(token) // Not logging as we log the total time @@ -666,7 +666,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable SelectFiles(IFilter filter, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable SelectFilesAsync(IFilter filter, [EnumeratorCancellation] CancellationToken token) { await using var tmpnames = await FilteredFilenameTable .CreateFilteredFilenameTableAsync(m_db, filter, token) @@ -755,7 +755,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task TakeFirst(CancellationToken token) + public async Task TakeFirstAsync(CancellationToken token) { await using var cmd = m_db.Connection.CreateCommand(); await cmd.ExecuteNonQueryAsync($@" @@ -771,7 +771,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable QuickSets([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable QuickSetsAsync([EnumeratorCancellation] CancellationToken token) { var dict = new Dictionary(); for (var i = 0; i < m_filesets.Length; i++) @@ -803,7 +803,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable Sets([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable SetsAsync([EnumeratorCancellation] CancellationToken token) { var dict = new Dictionary(); for (var i = 0; i < m_filesets.Length; i++) @@ -887,7 +887,7 @@ namespace Duplicati.Library.Main.Database /// The time to filter filesets by. /// An array of versions to filter filesets by. /// A task that when awaited returns a new instance of . - public async Task SelectFileSets(DateTime time, long[] versions, CancellationToken token) + public async Task SelectFileSetsAsync(DateTime time, long[] versions, CancellationToken token) { return await FileSets.CreateAsync(this, time, versions, token) .ConfigureAwait(false); @@ -909,7 +909,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of fileset entries. - public async IAsyncEnumerable ListFilesetsExtended([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable ListFilesetsExtendedAsync([EnumeratorCancellation] CancellationToken token) { const string query = @" SELECT @@ -970,7 +970,7 @@ namespace Duplicati.Library.Main.Database /// >The limit for pagination. /// A cancellation token to cancel the operation. /// A task that when awaited returns a paginated result set of folder entries. - public async Task> ListFolder(IEnumerable prefixIds, long filesetid, long offset, long limit, CancellationToken token) + public async Task> ListFolderAsync(IEnumerable prefixIds, long filesetid, long offset, long limit, CancellationToken token) { if (offset != 0 && limit <= 0) throw new ArgumentException("Cannot use offset without limit specified.", nameof(offset)); @@ -1093,7 +1093,7 @@ namespace Duplicati.Library.Main.Database /// The prefixes to get the IDs for. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of prefix IDs. - public async IAsyncEnumerable GetPrefixIds(IEnumerable prefixes, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetPrefixIdsAsync(IEnumerable prefixes, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(); await using var prefixesTable = await TemporaryDbValueList.CreateAsync(this, prefixes, token) @@ -1120,7 +1120,7 @@ namespace Duplicati.Library.Main.Database /// The fileset id. /// A cancellation token to cancel the operation. /// A task that when awaited returns a list of the entries that are unique roots. - public async IAsyncEnumerable GetMinimalUniquePrefixEntries(long filesetId, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMinimalUniquePrefixEntriesAsync(long filesetId, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(); cmd.SetCommandAndParameters(@" @@ -1167,7 +1167,7 @@ namespace Duplicati.Library.Main.Database /// The folder entries to extend. /// A cancellation token to cancel the operation. /// A task that when awaited returns the extended folder entries. - public async Task>> GetMetadataForFilesetIds(IEnumerable filesetIds, CancellationToken token) + public async Task>> GetMetadataForFilesetIdsAsync(IEnumerable filesetIds, CancellationToken token) { await using var filesetIdsTable = await TemporaryDbValueList.CreateAsync(this, filesetIds, token) .ConfigureAwait(false); @@ -1226,7 +1226,7 @@ namespace Duplicati.Library.Main.Database /// Pagination limit. /// A cancellation token to cancel the operation. /// A task that when awaited returns all matching file versions ordered by path and then version time. - public async Task> ListFileVersions(IEnumerable paths, long[]? filesetIds, long offset, long limit, CancellationToken token) + public async Task> ListFileVersionsAsync(IEnumerable paths, long[]? filesetIds, long offset, long limit, CancellationToken token) { if (paths == null || !paths.Any()) return new PaginatedResults(0, (int)(limit > 0 ? limit : long.MaxValue), 0, 0, Enumerable.Empty()); @@ -1385,7 +1385,7 @@ namespace Duplicati.Library.Main.Database /// The limit for pagination. /// A cancellation token to cancel the operation. /// A task that when awaited returns all matching file versions ordered by path and then version time. - public async Task> SearchEntries(IEnumerable? pathprefixes, IFilter filter, long[]? filesetIds, long offset, long limit, CancellationToken token) + public async Task> SearchEntriesAsync(IEnumerable? pathprefixes, IFilter filter, long[]? filesetIds, long offset, long limit, CancellationToken token) { if (offset != 0 && limit <= 0) throw new ArgumentException("Cannot use offset without limit specified.", nameof(offset)); diff --git a/Duplicati/Library/Main/Database/LocalLockDatabase.cs b/Duplicati/Library/Main/Database/LocalLockDatabase.cs index c0320a2d2..dd5e946fa 100644 --- a/Duplicati/Library/Main/Database/LocalLockDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalLockDatabase.cs @@ -61,7 +61,7 @@ namespace Duplicati.Library.Main.Database /// The lock expiration time, or null to clear the lock. /// Cancellation token. /// A task that completes when the update is done. - public async Task UpdateRemoteVolumeLockExpiration(string name, DateTime lockExpirationTime, CancellationToken token) + public async Task UpdateRemoteVolumeLockExpirationAsync(string name, DateTime lockExpirationTime, CancellationToken token) { await using var cmd = m_connection.CreateCommand(); cmd.SetTransaction(m_rtr); @@ -84,7 +84,7 @@ namespace Duplicati.Library.Main.Database /// If true, only returns volumes without lock expiration /// Cancellation token. /// An asynchronous sequence of tuples containing the volume name and lock expiration time. - public async IAsyncEnumerable<(string Name, DateTime? LockExpirationTime)> GetRemoteVolumesWithLockExpiration(bool onlyWithoutLockInformation, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(string Name, DateTime? LockExpirationTime)> GetRemoteVolumesWithLockExpirationAsync(bool onlyWithoutLockInformation, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(); cmd.SetTransaction(m_rtr); diff --git a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs index 79f754609..92f874a94 100644 --- a/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalPurgeDatabase.cs @@ -85,7 +85,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the parent fileset. /// A cancellation token to cancel the operation. /// A task that when awaited contains a new instance of . - public async Task CreateTemporaryFileset(long parentid, CancellationToken token) + public async Task CreateTemporaryFilesetAsync(long parentid, CancellationToken token) { return await TemporaryFileset.CreateAsync(parentid, this, token) .ConfigureAwait(false); @@ -97,7 +97,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the fileset. /// A cancellation token to cancel the operation. /// A task that when awaited contains the name of the remote volume. - public async Task GetRemoteVolumeNameForFileset(long id, CancellationToken token) + public async Task GetRemoteVolumeNameForFilesetAsync(long id, CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -123,7 +123,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that when awaited contains the count of orphan files. - internal async Task CountOrphanFiles(CancellationToken token) + internal async Task CountOrphanFilesAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); cmd.SetCommandAndParameters(@" @@ -173,14 +173,14 @@ namespace Duplicati.Library.Main.Database /// The filter to apply. /// A cancellation token to cancel the operation. /// A task that completes when the filter has been applied. - Task ApplyFilter(Library.Utility.IFilter filter, CancellationToken token); + Task ApplyFilterAsync(Library.Utility.IFilter filter, CancellationToken token); /// /// Applies a filter using a custom command. /// /// The command to execute for filtering. /// A cancellation token to cancel the operation. /// A task that completes when the filter has been applied. - Task ApplyFilter(Func> filtercommand, CancellationToken token); + Task ApplyFilterAsync(Func> filtercommand, CancellationToken token); /// /// Converts the temporary fileset to a permanent fileset. /// @@ -189,13 +189,13 @@ namespace Duplicati.Library.Main.Database /// Indicates if this is a full backup. /// A cancellation token to cancel the operation. /// A task that when awaited contains a tuple with the remote volume ID and the new fileset ID. - Task> ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup, CancellationToken token); + Task> ConvertToPermanentFilesetAsync(string name, DateTime timestamp, bool isFullBackup, CancellationToken token); /// /// Lists all deleted files in the temporary fileset. /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of key-value pairs where the key is the file path and the value is the file size. - IAsyncEnumerable> ListAllDeletedFiles(CancellationToken token); + IAsyncEnumerable> ListAllDeletedFilesAsync(CancellationToken token); } /// @@ -265,14 +265,14 @@ namespace Duplicati.Library.Main.Database /// The command to execute for filtering. /// A cancellation token to cancel the operation. /// A task that completes when the filter has been applied. - public async Task ApplyFilter(Func> filtercommand, CancellationToken token) + public async Task ApplyFilterAsync(Func> filtercommand, CancellationToken token) { int updated; await using (var cmd = m_db.Connection.CreateCommand()) updated = await filtercommand(cmd, ParentID, m_tablename) .ConfigureAwait(false); - await PostFilterChecks(updated, token).ConfigureAwait(false); + await PostFilterChecksAsync(updated, token).ConfigureAwait(false); } /// @@ -281,7 +281,7 @@ namespace Duplicati.Library.Main.Database /// The filter to apply. /// A cancellation token to cancel the operation. /// A task that completes when the filter has been applied. - public async Task ApplyFilter(Library.Utility.IFilter filter, CancellationToken token) + public async Task ApplyFilterAsync(Library.Utility.IFilter filter, CancellationToken token) { if (Library.Utility.Utility.IsFSCaseSensitive && filter is FilterExpression expression && expression.Type == Duplicati.Library.Utility.FilterType.Simple) { @@ -374,7 +374,7 @@ namespace Duplicati.Library.Main.Database } } - await PostFilterChecks(0, token).ConfigureAwait(false); + await PostFilterChecksAsync(0, token).ConfigureAwait(false); } /// @@ -383,7 +383,7 @@ namespace Duplicati.Library.Main.Database /// The number of files updated by the filter. /// A cancellation token to cancel the operation. /// A task that completes when the checks have been applied. - private async Task PostFilterChecks(int updated, CancellationToken token) + private async Task PostFilterChecksAsync(int updated, CancellationToken token) { await using var cmd = m_db.Connection.CreateCommand(); UpdatedFileCount = updated; @@ -456,19 +456,19 @@ namespace Duplicati.Library.Main.Database } /// - public async Task> ConvertToPermanentFileset(string name, DateTime timestamp, bool isFullBackup, CancellationToken token) + public async Task> ConvertToPermanentFilesetAsync(string name, DateTime timestamp, bool isFullBackup, CancellationToken token) { var remotevolid = await m_db - .RegisterRemoteVolume(name, RemoteVolumeType.Files, RemoteVolumeState.Temporary, token) + .RegisterRemoteVolumeAsync(name, RemoteVolumeType.Files, RemoteVolumeState.Temporary, token) .ConfigureAwait(false); var filesetid = await m_db - .CreateFileset(remotevolid, timestamp, token) + .CreateFilesetAsync(remotevolid, timestamp, token) .ConfigureAwait(false); await m_db - .UpdateFullBackupStateInFileset(filesetid, isFullBackup, token) + .UpdateFullBackupStateInFilesetAsync(filesetid, isFullBackup, token) .ConfigureAwait(false); await using (var cmd = m_db.Connection.CreateCommand(m_db.Transaction)) @@ -496,7 +496,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable> ListAllDeletedFiles([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> ListAllDeletedFilesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_db.Connection.CreateCommand(); await using var rd = await cmd.ExecuteReaderAsync($@" diff --git a/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs b/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs index 768b73b23..be8549de9 100644 --- a/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRecreateDatabase.cs @@ -448,7 +448,7 @@ namespace Duplicati.Library.Main.Database /// The size of the blocks in the blocklist. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the operation is finished. - public async Task FindMissingBlocklistHashes(long hashsize, long blocksize, CancellationToken token) + public async Task FindMissingBlocklistHashesAsync(long hashsize, long blocksize, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); //Update all small blocklists and matching blocks @@ -648,7 +648,7 @@ namespace Duplicati.Library.Main.Database /// If true, only hash entries are processed, ignoring small blocks. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the operation is finished. - public async Task AddBlockAndBlockSetEntryFromTemp(long hashsize, long blocksize, bool hashOnly, CancellationToken token) + public async Task AddBlockAndBlockSetEntryFromTempAsync(long hashsize, long blocksize, bool hashOnly, CancellationToken token) { // TODO should values be parameters, rather than hardcoded into the SQL queries? await using var cmd = m_connection.CreateCommand(m_rtr); @@ -793,11 +793,8 @@ namespace Duplicati.Library.Main.Database /// The ID of the metadata associated with the directory entry. /// A cancellation token to observe while waiting for the task to complete. /// A task that when completed, indicates that the directory entry has been added. - public async Task AddDirectoryEntry(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, CancellationToken token) - { - await AddEntry(filesetid, pathprefixid, path, time, FOLDER_BLOCKSET_ID, metadataid, token) - .ConfigureAwait(false); - } + public Task AddDirectoryEntryAsync(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, CancellationToken token) + => AddEntryAsync(filesetid, pathprefixid, path, time, FOLDER_BLOCKSET_ID, metadataid, token); /// /// Adds a symlink entry to the FilesetEntry table, linking it to a fileset and a path prefix. @@ -809,11 +806,8 @@ namespace Duplicati.Library.Main.Database /// The ID of the metadata associated with the symlink entry. /// A cancellation token to observe while waiting for the task to complete. /// A task that when completed, indicates that the symlink entry has been added. - public async Task AddSymlinkEntry(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, CancellationToken token) - { - await AddEntry(filesetid, pathprefixid, path, time, SYMLINK_BLOCKSET_ID, metadataid, token) - .ConfigureAwait(false); - } + public Task AddSymlinkEntryAsync(long filesetid, long pathprefixid, string path, DateTime time, long metadataid, CancellationToken token) + => AddEntryAsync(filesetid, pathprefixid, path, time, SYMLINK_BLOCKSET_ID, metadataid, token); /// /// Adds a file entry to the FilesetEntry table, linking it to a fileset and a path prefix. @@ -826,11 +820,8 @@ namespace Duplicati.Library.Main.Database /// The ID of the metadata associated with the file entry. /// A cancellation token to observe while waiting for the task to complete. /// A task that when completed, indicates that the file entry has been added. - public async Task AddFileEntry(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, CancellationToken token) - { - await AddEntry(filesetid, pathprefixid, path, time, blocksetid, metadataid, token) - .ConfigureAwait(false); - } + public Task AddFileEntryAsync(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, CancellationToken token) + => AddEntryAsync(filesetid, pathprefixid, path, time, blocksetid, metadataid, token); /// /// Adds a file and fileset entry to the FilesetEntry table, linking it to a fileset and a path prefix. @@ -845,7 +836,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the metadata associated with the file entry. /// A cancellation token to observe while waiting for the task to complete. /// A task that when completed, indicates that the file and fileset entry has been added. - private async Task AddEntry(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, CancellationToken token) + private async Task AddEntryAsync(long filesetid, long pathprefixid, string path, DateTime time, long blocksetid, long metadataid, CancellationToken token) { var fileid = await m_findFilesetCommand .SetTransaction(m_rtr) @@ -887,7 +878,7 @@ namespace Duplicati.Library.Main.Database /// The expected number of blocklist hashes for the metadataset. /// A cancellation token to observe while waiting for the task to complete. /// A task that returns the ID of the added or existing metadataset. - public async Task AddMetadataset(string metahash, long metahashsize, IEnumerable metablocklisthashes, long expectedmetablocklisthashes, CancellationToken token) + public async Task AddMetadatasetAsync(string metahash, long metahashsize, IEnumerable metablocklisthashes, long expectedmetablocklisthashes, CancellationToken token) { var metadataid = -1L; if (metahash == null) @@ -904,7 +895,7 @@ namespace Duplicati.Library.Main.Database return metadataid; var blocksetid = - await AddBlockset(metahash, metahashsize, metablocklisthashes, expectedmetablocklisthashes, token) + await AddBlocksetAsync(metahash, metahashsize, metablocklisthashes, expectedmetablocklisthashes, token) .ConfigureAwait(false); metadataid = await m_insertMetadatasetCommand @@ -926,7 +917,7 @@ namespace Duplicati.Library.Main.Database /// The expected number of blocklist hashes for the blockset. /// A cancellation token to observe while waiting for the task to complete. /// A task that returns the ID of the added or existing blockset. - public async Task AddBlockset(string fullhash, long size, IEnumerable blocklisthashes, long expectedblocklisthashes, CancellationToken token) + public async Task AddBlocksetAsync(string fullhash, long size, IEnumerable blocklisthashes, long expectedblocklisthashes, CancellationToken token) { var blocksetid = await m_findBlocksetCommand .SetTransaction(m_rtr) @@ -986,7 +977,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the volume to which the block belongs. /// A cancellation token to observe while waiting for the task to complete. /// A task that returns a tuple indicating whether any changes were made and whether the block was newly inserted. - public async Task<(bool, bool)> UpdateBlock(string hash, long size, long volumeID, CancellationToken token) + public async Task<(bool, bool)> UpdateBlockAsync(string hash, long size, long volumeID, CancellationToken token) { var anyChange = false; var currentVolumeId = await m_findHashBlockCommand @@ -1053,7 +1044,7 @@ namespace Duplicati.Library.Main.Database /// The size of the small block in bytes. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the small blockset link has been added. - public async Task AddSmallBlocksetLink(string filehash, string blockhash, long blocksize, CancellationToken token) + public async Task AddSmallBlocksetLinkAsync(string filehash, string blockhash, long blocksize, CancellationToken token) { await m_insertSmallBlockset .SetTransaction(m_rtr) @@ -1072,7 +1063,7 @@ namespace Duplicati.Library.Main.Database /// A collection of block hashes associated with the temporary blocklist. /// A cancellation token to observe while waiting for the task to complete. /// A task that returns a boolean indicating whether the temporary blocklist hash was successfully added. - public async Task AddTempBlockListHash(string hash, IEnumerable blocklisthashes, CancellationToken token) + public async Task AddTempBlockListHashAsync(string hash, IEnumerable blocklisthashes, CancellationToken token) { var r = await m_findTempBlockListHashCommand .SetTransaction(m_rtr) @@ -1108,7 +1099,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the volume for which to retrieve block hashes. /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable collection of block hashes associated with the specified volume ID. - public async IAsyncEnumerable GetBlockLists(long volumeid, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetBlockListsAsync(long volumeid, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT DISTINCT ""BlocklistHash"".""Hash"" @@ -1137,7 +1128,7 @@ namespace Duplicati.Library.Main.Database /// A boolean indicating whether to force the use of blocks, even if they are not missing. /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable collection of remote volumes that are missing blocks. - public async IAsyncEnumerable GetMissingBlockListVolumes(int passNo, long blocksize, long hashsize, bool forceBlockUse, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMissingBlockListVolumesAsync(int passNo, long blocksize, long hashsize, bool forceBlockUse, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); var selectCommand = @" @@ -1267,7 +1258,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the cleanup operation is finished. - public async Task CleanupMissingVolumes(CancellationToken token) + public async Task CleanupMissingVolumesAsync(CancellationToken token) { var tablename = "SwapBlocks-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); @@ -1421,7 +1412,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the cleanup operation is finished. - public async Task CleanupDeletedBlocks(CancellationToken token) + public async Task CleanupDeletedBlocksAsync(CancellationToken token) { // Find out which blocks are deleted and move them into DeletedBlock, so that compact notices these blocks are empty // Deleted blocks do not appear in the BlocksetEntry and not in the BlocklistHash table @@ -1512,7 +1503,7 @@ namespace Duplicati.Library.Main.Database long VolumeSize ); - public async IAsyncEnumerable GetMissingMetadataBlocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMissingMetadataBlocksAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); cmd.CommandText = @" @@ -1554,13 +1545,13 @@ namespace Duplicati.Library.Main.Database rd.ConvertValueToString(3) ?? string.Empty, rd.ConvertValueToInt64(4), rd.ConvertValueToString(5) ?? string.Empty, - rd.IsDBNull(6) ? null : rd.ConvertValueToString(6), + await rd.IsDBNullAsync(6) ? null : rd.ConvertValueToString(6), rd.ConvertValueToInt64(7, -1) ); } } - public async Task SetMetadataContent(long metadataId, string content, CancellationToken token) + public async Task SetMetadataContentAsync(long metadataId, string content, CancellationToken token) { await using var update = m_connection.CreateCommand(m_rtr); update.CommandText = @" diff --git a/Duplicati/Library/Main/Database/LocalRepairDatabase.cs b/Duplicati/Library/Main/Database/LocalRepairDatabase.cs index 0fc9064fa..276fd9bf0 100644 --- a/Duplicati/Library/Main/Database/LocalRepairDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRepairDatabase.cs @@ -55,7 +55,7 @@ namespace Duplicati.Library.Main.Database /// An optional existing database instance to use. /// A cancellation token to cancel the operation. /// A task that when awaited returns a new instance of . - public static async Task CreateRepairDatabase(string path, LocalRepairDatabase? dbnew, CancellationToken token) + public static async Task CreateRepairDatabaseAsync(string path, LocalRepairDatabase? dbnew, CancellationToken token) { dbnew ??= new LocalRepairDatabase(); @@ -89,7 +89,7 @@ namespace Duplicati.Library.Main.Database /// A cancellation token to cancel the operation. /// A task that when awaited returns a tuple containing the fileset ID, timestamp, and whether it is a full backup. /// Thrown if the remote file does not exist. - public async Task<(long FilesetId, DateTime Time, bool IsFullBackup)> GetFilesetFromRemotename(string filelist, CancellationToken token) + public async Task<(long FilesetId, DateTime Time, bool IsFullBackup)> GetFilesetFromRemotenameAsync(string filelist, CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -126,7 +126,7 @@ namespace Duplicati.Library.Main.Database /// A cancellation token to cancel the operation. /// A task that represents the asynchronous operation. /// Thrown if either fileset ID is less than or equal to zero. - public async Task MoveFilesFromFileset(long filesetid, long prevFilesetId, CancellationToken token) + public async Task MoveFilesFromFilesetAsync(long filesetid, long prevFilesetId, CancellationToken token) { if (filesetid <= 0) throw new ArgumentException("filesetid must be > 0"); @@ -152,7 +152,7 @@ namespace Duplicati.Library.Main.Database /// The block file ID. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of index file names that reference the specified block file. - public async IAsyncEnumerable GetIndexFilesReferencingBlockFile(long blockfileid, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetIndexFilesReferencingBlockFileAsync(long blockfileid, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT ""RemoteVolume"".""Name"" @@ -177,7 +177,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of key-value pairs where the key is the fileset ID and the value is the timestamp of the fileset. - public async IAsyncEnumerable> GetFilesetsWithMissingFiles([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> GetFilesetsWithMissingFilesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -211,7 +211,7 @@ namespace Duplicati.Library.Main.Database /// The fileset ID. /// A cancellation token to cancel the operation. /// A task that when awaited returns the number of deleted entries. - public async Task DeleteFilesetEntries(long filesetid, CancellationToken token) + public async Task DeleteFilesetEntriesAsync(long filesetid, CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" DELETE FROM ""FilesetEntry"" @@ -261,7 +261,7 @@ namespace Duplicati.Library.Main.Database /// The name of the index volume. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the block volumes. - public async IAsyncEnumerable GetBlockVolumesFromIndexName(string indexName, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetBlockVolumesFromIndexNameAsync(string indexName, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -337,45 +337,45 @@ namespace Duplicati.Library.Main.Database /// The volume ID of the new target volume. /// A cancellation token to cancel the operation. /// A task that when awaited returns true if the block was successfully marked as restored, false otherwise. - Task SetBlockRestored(string hash, long size, long volumeId, CancellationToken token); + Task SetBlockRestoredAsync(string hash, long size, long volumeId, CancellationToken token); /// /// Gets the list of files that contains missing blocks. /// /// The blocksize setting. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the files with missing blocks. - IAsyncEnumerable GetSourceFilesWithBlocks(long blocksize, CancellationToken token); + IAsyncEnumerable GetSourceFilesWithBlocksAsync(long blocksize, CancellationToken token); /// /// Gets a list for filesystem entries that contain missing blocks in metadata. /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the metadata blocks. - IAsyncEnumerable GetSourceItemsWithMetadataBlocks(CancellationToken token); + IAsyncEnumerable GetSourceItemsWithMetadataBlocksAsync(CancellationToken token); /// /// Gets missing blocklist hashes. /// /// The number of hashes for each block. /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the blocklist hashes. - IAsyncEnumerable GetBlocklistHashes(long hashesPerBlock, CancellationToken token); + IAsyncEnumerable GetBlocklistHashesAsync(long hashesPerBlock, CancellationToken token); /// /// Gets the number of missing blocks. /// /// A cancellation token to cancel the operation. /// A task that when awaited returns the count of missing blocks. - Task GetMissingBlockCount(CancellationToken token); + Task GetMissingBlockCountAsync(CancellationToken token); /// /// Gets all the filesets that are affected by missing blocks. /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the filesets that contain missing blocks. - IAsyncEnumerable GetFilesetsUsingMissingBlocks(CancellationToken token); + IAsyncEnumerable GetFilesetsUsingMissingBlocksAsync(CancellationToken token); /// /// Gets a list of remote files that may contain missing blocks. /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of representing the remote volumes that may contain missing blocks. - IAsyncEnumerable GetMissingBlockSources(CancellationToken token); + IAsyncEnumerable GetMissingBlockSourcesAsync(CancellationToken token); } /// @@ -449,7 +449,7 @@ namespace Duplicati.Library.Main.Database /// The transaction to use. /// A cancellation token to cancel the operation. /// A task that when awaited returns a new instance of . - public static async Task CreateMissingBlockList(string volumename, SqliteConnection connection, ReusableTransaction transaction, CancellationToken token) + public static async Task CreateMissingBlockListAsync(string volumename, SqliteConnection connection, ReusableTransaction transaction, CancellationToken token) { var blocklist = new MissingBlockList(volumename, connection, transaction); await using (var cmd = connection.CreateCommand(transaction.Transaction)) @@ -553,7 +553,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task SetBlockRestored(string hash, long size, long targetVolumeId, CancellationToken token) + public async Task SetBlockRestoredAsync(string hash, long size, long targetVolumeId, CancellationToken token) { var restored = await m_insertCommand .SetTransaction(m_rtr) @@ -587,7 +587,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable GetSourceFilesWithBlocks(long blocksize, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetSourceFilesWithBlocksAsync(long blocksize, [EnumeratorCancellation] CancellationToken token) { var str_blocksize = Library.Utility.Utility.FormatInvariantValue(blocksize); await using var cmd = m_connection.CreateCommand($@" @@ -628,7 +628,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable GetSourceItemsWithMetadataBlocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetSourceItemsWithMetadataBlocksAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT DISTINCT @@ -667,7 +667,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable GetBlocklistHashes(long hashesPerBlock, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetBlocklistHashesAsync(long hashesPerBlock, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); @@ -760,7 +760,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task GetMissingBlockCount(CancellationToken token) + public async Task GetMissingBlockCountAsync(CancellationToken token) { return await m_missingBlocksCountCommand .SetTransaction(m_rtr) @@ -774,7 +774,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of tuples containing the block hash and size. - public async IAsyncEnumerable<(string Hash, long Size)> GetMissingBlocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(string Hash, long Size)> GetMissingBlocksAsync([EnumeratorCancellation] CancellationToken token) { m_missingBlocksCommand .SetTransaction(m_rtr) @@ -788,7 +788,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task MoveBlocksToNewVolume(long targetVolumeId, long sourceVolumeId, CancellationToken token) + public async Task MoveBlocksToNewVolumeAsync(long targetVolumeId, long sourceVolumeId, CancellationToken token) { if (targetVolumeId <= 0) throw new ArgumentOutOfRangeException(nameof(targetVolumeId), "Target volume ID must be greater than 0"); @@ -846,7 +846,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable GetFilesetsUsingMissingBlocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesetsUsingMissingBlocksAsync([EnumeratorCancellation] CancellationToken token) { var blocks = $@" SELECT DISTINCT ""FileLookup"".""ID"" AS ID @@ -912,7 +912,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable GetMissingBlockSources([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMissingBlockSourcesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT DISTINCT @@ -982,9 +982,9 @@ namespace Duplicati.Library.Main.Database /// The name of the volume with missing blocks. /// A cancellation token to cancel the operation. /// A task that when awaited returns an instance of . - public async Task CreateBlockList(string volumename, CancellationToken token) + public async Task CreateBlockListAsync(string volumename, CancellationToken token) { - return await MissingBlockList.CreateMissingBlockList(volumename, m_connection, m_rtr, token) + return await MissingBlockList.CreateMissingBlockListAsync(volumename, m_connection, m_rtr, token) .ConfigureAwait(false); } @@ -993,7 +993,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that when completed indicates that the repair has been attempted. - public async Task FixDuplicateMetahash(CancellationToken token) + public async Task FixDuplicateMetahashAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr.Transaction); @@ -1180,7 +1180,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that when completed indicates that the repair has been attempted. - public async Task FixDuplicateFileentries(CancellationToken token) + public async Task FixDuplicateFileentriesAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr.Transaction); @@ -1294,7 +1294,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// A task that when completed indicates that the repair has been attempted. - public async Task FixDuplicatePathsInFilesets(CancellationToken token) + public async Task FixDuplicatePathsInFilesetsAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr.Transaction); @@ -1356,7 +1356,7 @@ namespace Duplicati.Library.Main.Database /// The size of each block in bytes. /// A cancellation token to cancel the operation. /// A task that when completed indicates that the repair has been attempted. - public async Task FixMissingBlocklistHashes(string blockhashalgorithm, long blocksize, CancellationToken token) + public async Task FixMissingBlocklistHashesAsync(string blockhashalgorithm, long blocksize, CancellationToken token) { var blocklistbuffer = new byte[blocksize]; @@ -1621,7 +1621,7 @@ namespace Duplicati.Library.Main.Database /// The size of each hash in bytes. /// A cancellation token to cancel the operation. /// A task that when completed indicates that the repair has been attempted. - public async Task FixDuplicateBlocklistHashes(long blocksize, long hashsize, CancellationToken token) + public async Task FixDuplicateBlocklistHashesAsync(long blocksize, long hashsize, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr.Transaction); @@ -1709,7 +1709,7 @@ namespace Duplicati.Library.Main.Database try { - await VerifyConsistency(blocksize, hashsize, true, token) + await VerifyConsistencyAsync(blocksize, hashsize, true, token) .ConfigureAwait(false); } catch (Exception ex) @@ -1731,7 +1731,7 @@ namespace Duplicati.Library.Main.Database /// A cancellation token to cancel the operation. /// Thrown if not all blocks are found in the specified volume. /// A task that when awaited indicates the completion of the check. - public async Task CheckAllBlocksAreInVolume(string filename, IEnumerable> blocks, CancellationToken token) + public async Task CheckAllBlocksAreInVolumeAsync(string filename, IEnumerable> blocks, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr.Transaction); var tablename = $"ProbeBlocks-{Library.Utility.Utility.GetHexGuid()}"; @@ -1811,7 +1811,7 @@ namespace Duplicati.Library.Main.Database /// A cancellation token to cancel the operation. /// A task that when awaited indicates the completion of the check. /// Thrown if the blocklist does not match the expected entries. - public async Task CheckBlocklistCorrect(string hash, long length, IEnumerable blocklist, long blocksize, long blockhashlength, CancellationToken token) + public async Task CheckBlocklistCorrectAsync(string hash, long length, IEnumerable blocklist, long blocksize, long blockhashlength, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr.Transaction); @@ -1872,7 +1872,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of missing local fileset names. - public async IAsyncEnumerable MissingLocalFilesets([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable MissingLocalFilesetsAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT ""Name"" @@ -1901,7 +1901,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of tuples containing the fileset ID, timestamp, and whether it is a full backup. - public async IAsyncEnumerable<(long FilesetID, DateTime Timestamp, bool IsFull)> MissingRemoteFilesets([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(long FilesetID, DateTime Timestamp, bool IsFull)> MissingRemoteFilesetsAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -1937,7 +1937,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to cancel the operation. /// An asynchronous enumerable of remote volumes that are empty index files. - public async IAsyncEnumerable EmptyIndexFiles([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable EmptyIndexFilesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand(@" SELECT @@ -1968,7 +1968,7 @@ namespace Duplicati.Library.Main.Database ); } - public async Task FixEmptyMetadatasets(Options options, CancellationToken token) + public async Task FixEmptyMetadatasetsAsync(Options options, CancellationToken token) { using var cmd = m_connection.CreateCommand(@" SELECT COUNT(*) @@ -1988,7 +1988,7 @@ namespace Duplicati.Library.Main.Database // Create replacement metadata var emptyMeta = Utility.WrapMetadata(new Dictionary(), options); - var emptyBlocksetId = await GetEmptyMetadataBlocksetId(Array.Empty(), emptyMeta.FileHash, emptyMeta.Blob.Length, token) + var emptyBlocksetId = await GetEmptyMetadataBlocksetIdAsync(Array.Empty(), emptyMeta.FileHash, emptyMeta.Blob.Length, token) .ConfigureAwait(false); if (emptyBlocksetId < 0) diff --git a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs index c79c44808..a9b445e10 100644 --- a/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalRestoreDatabase.cs @@ -152,7 +152,7 @@ namespace Duplicati.Library.Main.Database /// But if it reaches a restored state later, it will be re-added (trigger will fire) /// /// A task that completes when the progress tracker is created. - public async Task CreateProgressTracker(bool createFilesNewlyDoneTracker, CancellationToken token) + public async Task CreateProgressTrackerAsync(bool createFilesNewlyDoneTracker, CancellationToken token) { m_fileprogtable = $"FileProgress-{m_temptabsetguid}"; m_totalprogtable = $"TotalProgress-{m_temptabsetguid}"; @@ -409,7 +409,7 @@ namespace Duplicati.Library.Main.Database /// An optional filter to apply to the files being restored. /// A cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns a tuple containing the count of files to restore and the total size of those files. - public async Task> PrepareRestoreFilelist(DateTime restoretime, long[] versions, IFilter filter, CancellationToken token) + public async Task> PrepareRestoreFilelistAsync(DateTime restoretime, long[] versions, IFilter filter, CancellationToken token) { m_tempfiletable = $"Fileset-{m_temptabsetguid}"; m_tempblocktable = $"Blocks-{m_temptabsetguid}"; @@ -418,7 +418,7 @@ namespace Duplicati.Library.Main.Database { cmd.SetTransaction(m_rtr); var filesetIds = - await GetFilesetIDs(Library.Utility.Utility.NormalizeDateTime(restoretime), versions, false, token) + await GetFilesetIDsAsync(Library.Utility.Utility.NormalizeDateTime(restoretime), versions, false, token) .ToListAsync(cancellationToken: token) .ConfigureAwait(false); @@ -438,7 +438,7 @@ namespace Duplicati.Library.Main.Database .ConfigureAwait(false) ); - var ix = await FilesetTimes(token) + var ix = await FilesetTimesAsync(token) .Select((value, index) => new { value.Key, index }) .Where(n => n.Key == filesetId) .Select(pair => pair.index + 1) @@ -729,7 +729,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the first path found in the temporary file table, or null if no paths are found. - public async Task GetFirstPath(CancellationToken token) + public async Task GetFirstPathAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT ""Path"" @@ -751,7 +751,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// A task that, when awaited, returns the largest prefix path found in the temporary file table. - public async Task GetLargestPrefix(CancellationToken token) + public async Task GetLargestPrefixAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT ""Path"" @@ -814,10 +814,10 @@ namespace Duplicati.Library.Main.Database /// The destination path to prepend to the target paths. /// A cancellation token to monitor for cancellation requests. /// A task that completes when the target paths have been set. - public async Task SetTargetPaths(string largest_prefix, string destination, CancellationToken token) + public async Task SetTargetPathsAsync(string largest_prefix, string destination, CancellationToken token) { var dirsep = Util.GuessDirSeparator(string.IsNullOrWhiteSpace(largest_prefix) ? - await GetFirstPath(token).ConfigureAwait(false) + await GetFirstPathAsync(token).ConfigureAwait(false) : largest_prefix); @@ -962,7 +962,7 @@ namespace Duplicati.Library.Main.Database /// If true, skips metadata blocks when searching for missing blocks. /// A cancellation token to monitor for cancellation requests. /// A task that completes when the missing blocks have been found and inserted into the temporary block table. - public async Task FindMissingBlocks(bool skipMetadata, CancellationToken token) + public async Task FindMissingBlocksAsync(bool skipMetadata, CancellationToken token) { await using var cmd = m_connection.CreateCommand() .SetTransaction(m_rtr); @@ -1062,7 +1062,7 @@ namespace Duplicati.Library.Main.Database /// The new target path to set for the file. /// A cancellation token to monitor for cancellation requests. /// A task that completes when the target path has been updated. - public async Task UpdateTargetPath(long ID, string newname, CancellationToken token) + public async Task UpdateTargetPathAsync(long ID, string newname, CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" UPDATE ""{m_tempfiletable}"" @@ -1123,7 +1123,7 @@ namespace Duplicati.Library.Main.Database /// An asynchronous enumerable of the blocks in the file. /// /// A cancellation token to monitor for cancellation requests. - IAsyncEnumerable Blocks(CancellationToken token); + IAsyncEnumerable BlocksAsync(CancellationToken token); } /// @@ -1174,7 +1174,7 @@ namespace Duplicati.Library.Main.Database /// An asynchronous enumerable of block sources for this block descriptor. /// /// A cancellation token to monitor for cancellation requests. - IAsyncEnumerable BlockSources(CancellationToken token); + IAsyncEnumerable BlockSourcesAsync(CancellationToken token); } /// @@ -1194,7 +1194,7 @@ namespace Duplicati.Library.Main.Database /// An asynchronous enumerable of block descriptors for the file. /// /// A cancellation token to monitor for cancellation requests. - IAsyncEnumerable Blocks(CancellationToken token); + IAsyncEnumerable BlocksAsync(CancellationToken token); } /// @@ -1252,7 +1252,7 @@ namespace Duplicati.Library.Main.Database /// Gets the size of the remote volume in bytes. /// /// A cancellation token to monitor for cancellation requests. - IAsyncEnumerable Blocks(CancellationToken token); + IAsyncEnumerable BlocksAsync(CancellationToken token); } /// @@ -1313,7 +1313,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IExistingFileBlock objects representing the blocks in the file. - public async IAsyncEnumerable Blocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable BlocksAsync([EnumeratorCancellation] CancellationToken token) { string p = TargetPath; while (HasMore && p == TargetPath) @@ -1330,7 +1330,7 @@ namespace Duplicati.Library.Main.Database /// The name of the table containing existing files. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IExistingFile objects representing the existing files with their blocks. - public static async IAsyncEnumerable GetExistingFilesWithBlocks(LocalDatabase db, string tablename, [EnumeratorCancellation] CancellationToken token) + public static async IAsyncEnumerable GetExistingFilesWithBlocksAsync(LocalDatabase db, string tablename, [EnumeratorCancellation] CancellationToken token) { await using var cmd = db.Connection.CreateCommand($@" SELECT @@ -1378,11 +1378,11 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IExistingFile objects representing the existing files with their blocks. - public IAsyncEnumerable GetExistingFilesWithBlocks(CancellationToken token) + public IAsyncEnumerable GetExistingFilesWithBlocksAsync(CancellationToken token) { if (string.IsNullOrWhiteSpace(m_tempfiletable) || string.IsNullOrWhiteSpace(m_tempblocktable)) throw new InvalidOperationException("No temporary file table set up for this restore."); - return ExistingFile.GetExistingFilesWithBlocks(this, m_tempfiletable, token); + return ExistingFile.GetExistingFilesWithBlocksAsync(this, m_tempfiletable, token); } /// @@ -1451,7 +1451,7 @@ namespace Duplicati.Library.Main.Database public bool HasMore { get; private set; } /// - public async IAsyncEnumerable BlockSources([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable BlockSourcesAsync([EnumeratorCancellation] CancellationToken token) { var p = TargetPath; var h = Hash; @@ -1490,7 +1490,7 @@ namespace Duplicati.Library.Main.Database public bool HasMore { get; private set; } /// - public async IAsyncEnumerable Blocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable BlocksAsync([EnumeratorCancellation] CancellationToken token) { var p = TargetPath; while (HasMore && p == TargetPath) @@ -1517,7 +1517,7 @@ namespace Duplicati.Library.Main.Database /// If true, skips metadata blocks when retrieving files and source blocks. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of ILocalBlockSource objects representing the files and their source blocks. - public static async IAsyncEnumerable GetFilesAndSourceBlocks(LocalDatabase db, string filetablename, string blocktablename, long blocksize, bool skipMetadata, [EnumeratorCancellation] CancellationToken token) + public static async IAsyncEnumerable GetFilesAndSourceBlocksAsync(LocalDatabase db, string filetablename, string blocktablename, long blocksize, bool skipMetadata, [EnumeratorCancellation] CancellationToken token) { // TODO: Skip metadata as required // Have to order by target path and hash, to ensure BlockDescriptor and BlockSource match adjacent rows @@ -1575,11 +1575,11 @@ namespace Duplicati.Library.Main.Database /// The size of each block in bytes. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of ILocalBlockSource objects representing the files and their source blocks. - public IAsyncEnumerable GetFilesAndSourceBlocks(bool skipMetadata, long blocksize, CancellationToken token) + public IAsyncEnumerable GetFilesAndSourceBlocksAsync(bool skipMetadata, long blocksize, CancellationToken token) { if (string.IsNullOrWhiteSpace(m_tempfiletable) || string.IsNullOrWhiteSpace(m_tempblocktable)) throw new InvalidOperationException("No temporary file table set up for this restore."); - return LocalBlockSource.GetFilesAndSourceBlocks(this, m_tempfiletable, m_tempblocktable, blocksize, skipMetadata, token); + return LocalBlockSource.GetFilesAndSourceBlocksAsync(this, m_tempfiletable, m_tempblocktable, blocksize, skipMetadata, token); } /// @@ -1587,7 +1587,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IRemoteVolume objects representing the missing remote volumes. - public async IAsyncEnumerable GetMissingVolumes([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMissingVolumesAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT @@ -1640,13 +1640,13 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IVolumePatch objects representing the files with missing blocks. - IAsyncEnumerable FilesWithMissingBlocks(CancellationToken token); + IAsyncEnumerable FilesWithMissingBlocksAsync(CancellationToken token); /// /// Retrieves metadata with missing blocks. /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IVolumePatch objects representing the metadata with missing blocks. - IAsyncEnumerable MetadataWithMissingBlocks(CancellationToken token); + IAsyncEnumerable MetadataWithMissingBlocksAsync(CancellationToken token); } private class FilesAndMetadata : IFilesAndMetadata @@ -1785,7 +1785,7 @@ namespace Duplicati.Library.Main.Database public bool HasMore { get; private set; } /// - public async IAsyncEnumerable Blocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable BlocksAsync([EnumeratorCancellation] CancellationToken token) { string p = Path; while (HasMore && p == Path) @@ -1801,7 +1801,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IVolumePatch objects representing the files with missing blocks. - public async IAsyncEnumerable FilesWithMissingBlocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable FilesWithMissingBlocksAsync([EnumeratorCancellation] CancellationToken token) { // The IN-clause with subquery enables SQLite to use indexes better. Three way join (A,B,C) is slow here! await using var cmd = m_db.Connection.CreateCommand($@" @@ -1855,7 +1855,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IVolumePatch objects representing the metadata with missing blocks. - public async IAsyncEnumerable MetadataWithMissingBlocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable MetadataWithMissingBlocksAsync([EnumeratorCancellation] CancellationToken token) { // The IN-clause with subquery enables SQLite to use indexes better. Three way join (A,B,C) is slow here! await using var cmd = m_db.Connection.CreateCommand($@" @@ -1912,7 +1912,7 @@ namespace Duplicati.Library.Main.Database /// The current volume reader containing the blocks to restore. /// A cancellation token to monitor for cancellation requests. /// A task that when awaited returns an IFilesAndMetadata instance containing the files and metadata with missing blocks. - public async Task GetMissingBlockData(BlockVolumeReader curvolume, long blocksize, CancellationToken token) + public async Task GetMissingBlockDataAsync(BlockVolumeReader curvolume, long blocksize, CancellationToken token) { if (string.IsNullOrWhiteSpace(m_tempfiletable) || string.IsNullOrWhiteSpace(m_tempblocktable)) throw new InvalidOperationException("No temporary file table set up for this restore."); @@ -1925,7 +1925,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// A task that when awaited returns a tuple containing the SqliteConnection and ReusableTransaction. - public async Task<(SqliteConnection, ReusableTransaction)> GetConnectionFromPool(CancellationToken token) + public async Task<(SqliteConnection, ReusableTransaction)> GetConnectionFromPoolAsync(CancellationToken token) { if (!m_connection_pool.TryTake(out var entry)) { @@ -1975,7 +1975,7 @@ namespace Duplicati.Library.Main.Database /// If true, only returns files that have not been verified. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of IFileToRestore objects representing the files to restore. - public async IAsyncEnumerable GetFilesToRestore(bool onlyNonVerified, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesToRestoreAsync(bool onlyNonVerified, [EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT @@ -2005,7 +2005,7 @@ namespace Duplicati.Library.Main.Database /// At its current state, this method is designed to be called by Duplicati.Library.Main.Operation.Restore.FileLister. It locks the database to ensure that calls from Duplicati.Library.Main.Operation.Restore.BlockManager do not interfere with each other. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of FileRequest objects representing the files and symlinks to restore. - public async IAsyncEnumerable GetFilesAndSymlinksToRestore([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesAndSymlinksToRestoreAsync([EnumeratorCancellation] CancellationToken token) { await m_dbLock.WaitAsync(token).ConfigureAwait(false); try @@ -2050,7 +2050,7 @@ namespace Duplicati.Library.Main.Database /// At its current state, this method is designed to be called by Duplicati.Library.Main.Operation.Restore.FileLister. It locks the database to ensure that calls from Duplicati.Library.Main.Operation.Restore.BlockManager do not interfere with each other. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of FileRequest objects representing the folders to restore. - public async IAsyncEnumerable GetFolderMetadataToRestore([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFolderMetadataToRestoreAsync([EnumeratorCancellation] CancellationToken token) { await m_dbLock.WaitAsync(token).ConfigureAwait(false); @@ -2098,7 +2098,7 @@ namespace Duplicati.Library.Main.Database /// Flag indicating whether the returned blocks should exclude the metadata blocks. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of tuples containing the block ID and volume ID. - public async IAsyncEnumerable<(long, long)> GetBlocksAndVolumeIDs(bool skipMetadata, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(long, long)> GetBlocksAndVolumeIDsAsync(bool skipMetadata, [EnumeratorCancellation] CancellationToken token) { await m_dbLock.WaitAsync(token).ConfigureAwait(false); @@ -2150,9 +2150,9 @@ namespace Duplicati.Library.Main.Database /// The BlocksetID of the file. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of representing the blocks of the file. - public async IAsyncEnumerable GetBlocksFromFile(long blocksetID, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetBlocksFromFileAsync(long blocksetID, [EnumeratorCancellation] CancellationToken token) { - var (connection, transaction) = await GetConnectionFromPool(token) + var (connection, transaction) = await GetConnectionFromPoolAsync(token) .ConfigureAwait(false); try @@ -2197,9 +2197,9 @@ namespace Duplicati.Library.Main.Database /// The ID of the file. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of representing the metadata blocks of the file. - public async IAsyncEnumerable GetMetadataBlocksFromFile(long fileID, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetMetadataBlocksFromFileAsync(long fileID, [EnumeratorCancellation] CancellationToken token) { - var (connection, transaction) = await GetConnectionFromPool(token) + var (connection, transaction) = await GetConnectionFromPoolAsync(token) .ConfigureAwait(false); try @@ -2273,9 +2273,9 @@ namespace Duplicati.Library.Main.Database /// The ID of the volume. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of tuples containing the volume name, size, and hash. - public async IAsyncEnumerable<(string, long, string)> GetVolumeInfo(long VolumeID, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable<(string, long, string)> GetVolumeInfoAsync(long VolumeID, [EnumeratorCancellation] CancellationToken token) { - var (connection, transaction) = await GetConnectionFromPool(token) + var (connection, transaction) = await GetConnectionFromPoolAsync(token) .ConfigureAwait(false); try @@ -2311,7 +2311,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// A task that completes when the temporary tables are dropped and the transaction is committed. - public async Task DropRestoreTable(CancellationToken token) + public async Task DropRestoreTableAsync(CancellationToken token) { await using var cmd = m_connection.CreateCommand() .SetTransaction(m_rtr); @@ -2388,7 +2388,7 @@ namespace Duplicati.Library.Main.Database /// Indicates whether the block is metadata. /// A cancellation token to monitor for cancellation requests. /// A task that completes when the block is marked as restored. - Task SetBlockRestored(long targetfileid, long index, string hash, long blocksize, bool metadata, CancellationToken token); + Task SetBlockRestoredAsync(long targetfileid, long index, string hash, long blocksize, bool metadata, CancellationToken token); /// /// Marks all blocks of a specific file as missing. @@ -2396,7 +2396,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the target file. /// A cancellation token to monitor for cancellation requests. /// A task that completes when all blocks of the file are marked as missing. - Task SetAllBlocksMissing(long targetfileid, CancellationToken token); + Task SetAllBlocksMissingAsync(long targetfileid, CancellationToken token); /// /// Marks all blocks of a specific file as restored. @@ -2405,7 +2405,7 @@ namespace Duplicati.Library.Main.Database /// Indicates whether to include metadata blocks. /// A cancellation token to monitor for cancellation requests. /// A task that completes when all blocks of the file are marked as restored. - Task SetAllBlocksRestored(long targetfileid, bool includeMetadata, CancellationToken token); + Task SetAllBlocksRestoredAsync(long targetfileid, bool includeMetadata, CancellationToken token); /// /// Marks the data of a specific file as verified. @@ -2413,7 +2413,7 @@ namespace Duplicati.Library.Main.Database /// The ID of the target file. /// A cancellation token to monitor for cancellation requests. /// A task that completes when the file data is marked as verified. - Task SetFileDataVerified(long targetfileid, CancellationToken token); + Task SetFileDataVerifiedAsync(long targetfileid, CancellationToken token); /// /// Commits the changes made by the block marker. @@ -2428,7 +2428,7 @@ namespace Duplicati.Library.Main.Database /// An operation progress updater to report the processed statistics. /// A cancellation token to monitor for cancellation requests. /// A task that completes when the processed statistics are updated. - Task UpdateProcessed(IOperationProgressUpdater writer, CancellationToken token); + Task UpdateProcessedAsync(IOperationProgressUpdater writer, CancellationToken token); } /// @@ -2574,7 +2574,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task UpdateProcessed(IOperationProgressUpdater updater, CancellationToken token) + public async Task UpdateProcessedAsync(IOperationProgressUpdater updater, CancellationToken token) { if (!m_hasUpdates) return; @@ -2588,7 +2588,7 @@ namespace Duplicati.Library.Main.Database var filesprocessed = 0L; var processedsize = 0L; - if (rd.Read()) + if (await rd.ReadAsync()) { filesprocessed += rd.ConvertValueToInt64(0, 0); processedsize += rd.ConvertValueToInt64(1, 0); @@ -2598,7 +2598,7 @@ namespace Duplicati.Library.Main.Database } // - public async Task SetAllBlocksMissing(long targetfileid, CancellationToken token) + public async Task SetAllBlocksMissingAsync(long targetfileid, CancellationToken token) { m_hasUpdates = true; m_resetfileCommand @@ -2612,7 +2612,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task SetAllBlocksRestored(long targetfileid, bool includeMetadata, CancellationToken token) + public async Task SetAllBlocksRestoredAsync(long targetfileid, bool includeMetadata, CancellationToken token) { m_hasUpdates = true; m_updateAsRestoredCommand @@ -2628,7 +2628,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task SetFileDataVerified(long targetfileid, CancellationToken token) + public async Task SetFileDataVerifiedAsync(long targetfileid, CancellationToken token) { m_hasUpdates = true; m_updateFileAsDataVerifiedCommand @@ -2643,7 +2643,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task SetBlockRestored(long targetfileid, long index, string hash, long size, bool metadata, CancellationToken token) + public async Task SetBlockRestoredAsync(long targetfileid, long index, string hash, long size, bool metadata, CancellationToken token) { m_hasUpdates = true; m_insertblockCommand @@ -2707,7 +2707,7 @@ namespace Duplicati.Library.Main.Database public override async ValueTask DisposeAsync() { await DisposePoolAsync().ConfigureAwait(false); - await DropRestoreTable(default).ConfigureAwait(false); + await DropRestoreTableAsync(default).ConfigureAwait(false); await base.DisposeAsync().ConfigureAwait(false); } @@ -2731,7 +2731,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of strings representing the target folders. - public async IAsyncEnumerable GetTargetFolders([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetTargetFoldersAsync([EnumeratorCancellation] CancellationToken token) { await using var cmd = m_connection.CreateCommand($@" SELECT ""TargetPath"" @@ -2768,7 +2768,7 @@ namespace Duplicati.Library.Main.Database /// /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of representing the blocks of the file. - IAsyncEnumerable Blocks(CancellationToken token); + IAsyncEnumerable BlocksAsync(CancellationToken token); } /// @@ -2861,7 +2861,7 @@ namespace Duplicati.Library.Main.Database public string SourcePath { get { return m_rd.ConvertValueToString(1) ?? ""; } } /// - public async IAsyncEnumerable Blocks([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable BlocksAsync([EnumeratorCancellation] CancellationToken token) { var tid = TargetFileID; @@ -2878,7 +2878,7 @@ namespace Duplicati.Library.Main.Database /// The size of the blocks to be used for restoration. /// A cancellation token to monitor for cancellation requests. /// An asynchronous enumerable of representing the files and their source blocks. - public async IAsyncEnumerable GetFilesAndSourceBlocksFast(long blocksize, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable GetFilesAndSourceBlocksFastAsync(long blocksize, [EnumeratorCancellation] CancellationToken token) { await using (var cmdReader = m_connection.CreateCommand()) await using (var cmd = m_connection.CreateCommand()) diff --git a/Duplicati/Library/Main/Database/LocalTestDatabase.cs b/Duplicati/Library/Main/Database/LocalTestDatabase.cs index 9535ba9ea..19c8129e4 100644 --- a/Duplicati/Library/Main/Database/LocalTestDatabase.cs +++ b/Duplicati/Library/Main/Database/LocalTestDatabase.cs @@ -82,7 +82,7 @@ namespace Duplicati.Library.Main.Database /// The name of the remote volume to update. /// A cancellation token to observe while waiting for the task to complete. /// A task that represents the asynchronous operation. - public async Task UpdateVerificationCount(string name, CancellationToken token) + public async Task UpdateVerificationCountAsync(string name, CancellationToken token) { await using var cmd = m_connection.CreateCommand(m_rtr); await cmd.SetCommandAndParameters(@" @@ -203,9 +203,9 @@ namespace Duplicati.Library.Main.Database /// The options that define selection criteria, such as time, version, and verification strategy. /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable of representing the selected test targets. - public async IAsyncEnumerable SelectTestTargets(long samples, Options options, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable SelectTestTargetsAsync(long samples, Options options, [EnumeratorCancellation] CancellationToken token) { - var tp = await GetFilelistWhereClause(options.Time, options.Version, null, false, token) + var tp = await GetFilelistWhereClauseAsync(options.Time, options.Version, null, false, token) .ConfigureAwait(false); samples = Math.Max(1, samples); @@ -463,14 +463,14 @@ namespace Duplicati.Library.Main.Database /// The timestamp of the file entry. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the file entry has been added. - Task Add(string path, long size, string hash, long metasize, string metahash, IEnumerable blocklistHashes, FilelistEntryType type, DateTime time, CancellationToken token); + Task AddAsync(string path, long size, string hash, long metasize, string metahash, IEnumerable blocklistHashes, FilelistEntryType type, DateTime time, CancellationToken token); /// /// Asynchronously compares the file list with remote volumes and yields differences. /// /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable of key-value pairs representing the comparison results, where the key is the test entry status and the value is the file path. - IAsyncEnumerable> Compare(CancellationToken token); + IAsyncEnumerable> CompareAsync(CancellationToken token); } /// @@ -547,7 +547,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task Add(string path, long size, string hash, long metasize, string metahash, IEnumerable blocklistHashes, FilelistEntryType type, DateTime time, CancellationToken token) + public async Task AddAsync(string path, long size, string hash, long metasize, string metahash, IEnumerable blocklistHashes, FilelistEntryType type, DateTime time, CancellationToken token) { await m_insertCommand .SetTransaction(m_db.Transaction) @@ -561,7 +561,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable> Compare([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> CompareAsync([EnumeratorCancellation] CancellationToken token) { var cmpName = $"CmpTable-{Library.Utility.Utility.GetHexGuid()}"; @@ -690,14 +690,14 @@ namespace Duplicati.Library.Main.Database /// The length of the block link in bytes. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the block link has been added. - Task AddBlockLink(string filename, string hash, long length, CancellationToken token); + Task AddBlockLinkAsync(string filename, string hash, long length, CancellationToken token); /// /// Asynchronously compares the index list with remote volumes and yields differences. /// /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable of key-value pairs representing the comparison results, where the key is the test entry status and the value is the file path. - IAsyncEnumerable> Compare(CancellationToken token); + IAsyncEnumerable> CompareAsync(CancellationToken token); } /// @@ -768,7 +768,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task AddBlockLink(string filename, string hash, long length, CancellationToken token) + public async Task AddBlockLinkAsync(string filename, string hash, long length, CancellationToken token) { await m_insertCommand .SetTransaction(m_db.Transaction) @@ -780,7 +780,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable> Compare([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> CompareAsync([EnumeratorCancellation] CancellationToken token) { var cmpName = $"CmpTable-{Library.Utility.Utility.GetHexGuid()}"; var create = $@" @@ -884,14 +884,14 @@ namespace Duplicati.Library.Main.Database /// The size of the block in bytes. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the block has been added. - Task AddBlock(string key, long value, CancellationToken token); + Task AddBlockAsync(string key, long value, CancellationToken token); /// /// Asynchronously compares the blocklist with remote volumes and yields differences. /// /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable of key-value pairs representing the comparison results, where the key is the test entry status and the value is the block hash. - IAsyncEnumerable> Compare(CancellationToken token); + IAsyncEnumerable> CompareAsync(CancellationToken token); } public interface IBlocklistHashList : IDisposable, IAsyncDisposable @@ -903,7 +903,7 @@ namespace Duplicati.Library.Main.Database /// The size of the block in bytes. /// A cancellation token to observe while waiting for the task to complete. /// A task that completes when the block hash has been added. - Task AddBlockHash(string hash, long size, CancellationToken token); + Task AddBlockHashAsync(string hash, long size, CancellationToken token); /// /// Asynchronously compares the blocklist hash list with remote volumes and yields differences. @@ -913,7 +913,7 @@ namespace Duplicati.Library.Main.Database /// The size of each block in bytes. /// A cancellation token to observe while waiting for the task to complete. /// An asynchronous enumerable of key-value pairs representing the comparison results, where the key is the test entry status and the value is the block hash. - IAsyncEnumerable> Compare(int hashesPerBlock, int hashSize, int blockSize, CancellationToken token); + IAsyncEnumerable> CompareAsync(int hashesPerBlock, int hashSize, int blockSize, CancellationToken token); } /// @@ -974,7 +974,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task AddBlock(string hash, long size, CancellationToken token) + public async Task AddBlockAsync(string hash, long size, CancellationToken token) { await m_insertCommand .SetTransaction(m_db.Transaction) @@ -985,7 +985,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable> Compare([EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> CompareAsync([EnumeratorCancellation] CancellationToken token) { var cmpName = "CmpTable-" + Library.Utility.Utility.ByteArrayAsHexString(Guid.NewGuid().ToByteArray()); @@ -1175,7 +1175,7 @@ namespace Duplicati.Library.Main.Database } /// - public async Task AddBlockHash(string hash, long size, CancellationToken token) + public async Task AddBlockHashAsync(string hash, long size, CancellationToken token) { await m_insertCommand .SetTransaction(m_db.Transaction) @@ -1186,7 +1186,7 @@ namespace Duplicati.Library.Main.Database } /// - public async IAsyncEnumerable> Compare(int hashesPerBlock, int hashSize, int blockSize, [EnumeratorCancellation] CancellationToken token) + public async IAsyncEnumerable> CompareAsync(int hashesPerBlock, int hashSize, int blockSize, [EnumeratorCancellation] CancellationToken token) { var cmpName = $"CmpTable-{Library.Utility.Utility.GetHexGuid()}"; @@ -1338,7 +1338,7 @@ namespace Duplicati.Library.Main.Database /// The name of the filelist to create. /// A cancellation token to observe while waiting for the task to complete. /// A task that when awaited returns a new instance of the interface. - public async Task CreateFilelist(string name, CancellationToken token) + public async Task CreateFilelistAsync(string name, CancellationToken token) { return await Filelist.CreateAsync(this, name, token) .ConfigureAwait(false); @@ -1350,7 +1350,7 @@ namespace Duplicati.Library.Main.Database /// The name of the indexlist to create. /// A cancellation token to observe while waiting for the task to complete. /// A task that when awaited returns a new instance of the interface. - public async Task CreateIndexlist(string name, CancellationToken token) + public async Task CreateIndexlistAsync(string name, CancellationToken token) { return await Indexlist.CreateAsync(this, name, token) .ConfigureAwait(false); @@ -1362,7 +1362,7 @@ namespace Duplicati.Library.Main.Database /// The name of the blocklist to create. /// A cancellation token to observe while waiting for the task to complete. /// A task that when awaited returns a new instance of the interface. - public async Task CreateBlocklist(string name, CancellationToken token) + public async Task CreateBlocklistAsync(string name, CancellationToken token) { return await Blocklist.CreateAsync(this, name, token) .ConfigureAwait(false); @@ -1374,7 +1374,7 @@ namespace Duplicati.Library.Main.Database /// The name of the blocklist hash list to create. /// A cancellation token to observe while waiting for the task to complete. /// A task that when awaited returns a new instance of the interface. - public async Task CreateBlocklistHashList(string name, CancellationToken token) + public async Task CreateBlocklistHashListAsync(string name, CancellationToken token) { return await BlocklistHashList.CreateAsync(this, name, token) .ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Database/TemporaryDbValueList.cs b/Duplicati/Library/Main/Database/TemporaryDbValueList.cs index cbd4a1866..fd9e88022 100644 --- a/Duplicati/Library/Main/Database/TemporaryDbValueList.cs +++ b/Duplicati/Library/Main/Database/TemporaryDbValueList.cs @@ -156,7 +156,7 @@ internal class TemporaryDbValueList : IDisposable, IAsyncDisposable private static async Task DoCreateAsync(TemporaryDbValueList valueList, CancellationToken token) { if (valueList._values.Count() > LocalDatabase.CHUNK_SIZE) - await valueList.ForceToTable(token).ConfigureAwait(false); + await valueList.ForceToTableAsync(token).ConfigureAwait(false); return valueList; } @@ -176,12 +176,12 @@ internal class TemporaryDbValueList : IDisposable, IAsyncDisposable /// /// A cancellation token to cancel the operation. /// A task that when awaited contains the SQL in clause string. - public async Task GetInClause(CancellationToken token) + public async Task GetInClauseAsync(CancellationToken token) { if (_disposed) throw new ObjectDisposedException(nameof(TemporaryDbValueList)); - await ForceToTable(token).ConfigureAwait(false); + await ForceToTableAsync(token).ConfigureAwait(false); return $@" SELECT ""Value"" @@ -194,7 +194,7 @@ internal class TemporaryDbValueList : IDisposable, IAsyncDisposable /// /// A cancellation token to cancel the operation. /// A task that completes when the table has been created and the values inserted. - public async Task ForceToTable(CancellationToken token) + public async Task ForceToTableAsync(CancellationToken token) { if (_disposed) throw new ObjectDisposedException(nameof(TemporaryDbValueList)); diff --git a/Duplicati/Library/Main/IController.cs b/Duplicati/Library/Main/IController.cs new file mode 100644 index 000000000..c29464975 --- /dev/null +++ b/Duplicati/Library/Main/IController.cs @@ -0,0 +1,320 @@ +// Copyright (C) 2026, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Duplicati.Library.Utility; +using Duplicati.Library.Interface; + +#nullable enable + +namespace Duplicati.Library.Main; + +/// +/// Interface for the controller +/// +public interface IController : IDisposable +{ + /// + /// Gets or sets the callback for when an operation is started + /// + Action? OnOperationStarted { get; set; } + + /// + /// Gets or sets the callback for when an operation is completed + /// + Action? OnOperationCompleted { get; set; } + + /// + /// Sets the time of the last compact operation + /// + /// The time of the last compact operation + Task SetLastCompactAsync(DateTime lastCompact); + + /// + /// Sets the time of the last vacuum operation + /// + /// The time of the last vacuum operation + Task SetLastVacuumAsync(DateTime lastVacuum); + + /// + /// Aborts the current operation + /// + Task AbortAsync(); + + /// + /// Performs a backup operation + /// + /// The sources to back up + /// An optional filter to apply + /// The backup results + Task BackupAsync(string[] inputsources, IFilter? filter = null); + + /// + /// Performs a compact operation + /// + /// The compact results + Task CompactAsync(); + + /// + /// Creates a log database at the specified path + /// + /// The target path for the log database + /// The results of the log database creation + Task CreateLogDatabaseAsync(string targetpath); + + /// + /// Deletes the backup data + /// + /// The delete results + Task DeleteAsync(); + + /// + /// Deletes all remote files + /// + /// The list of remote results + Task DeleteAllRemoteFilesAsync(); + + /// + /// Lists the backup contents + /// + /// The list results + Task ListAsync(); + + /// + /// Lists the backup contents filtered by a filter string + /// + /// The filter string + /// The list results + Task ListAsync(string? filterstring); + + /// + /// Lists the backup contents filtered by multiple filter strings and a filter + /// + /// The filter strings + /// The filter to apply + /// The list results + Task ListAsync(IEnumerable? filterstrings, IFilter? filter); + + /// + /// Lists the files affected by the specified arguments + /// + /// The arguments to check + /// An optional callback for progress updates + /// The list of affected results + Task ListAffectedAsync(List? args, Action? callback = null); + + /// + /// Lists broken files in the backup + /// + /// The filter to apply + /// An optional callback handler for progress updates + /// The list of broken files results + Task ListBrokenFilesAsync(IFilter? filter, Func? callbackhandler = null); + + /// + /// Lists the changes between two versions + /// + /// The base version + /// The target version + /// Optional filter strings + /// Optional filter to apply + /// Optional callback for progress updates + /// The list changes results + Task ListChangesAsync(string baseVersion, string targetVersion, IEnumerable? filterstrings = null, IFilter? filter = null, Action>>? callback = null); + + /// + /// Lists control files in the backup + /// + /// The filter strings + /// The filter to apply + /// The list results + Task ListControlFilesAsync(IEnumerable? filterstrings, IFilter? filter); + + /// + /// Lists all filesets in the backup + /// + /// The fileset results + Task ListFilesetsAsync(); + + /// + /// Lists file versions for the specified files + /// + /// The files to list versions for + /// The offset to start listing from + /// The maximum number of results to return + /// The file versions results + Task ListFileVersionsAsync(string[]? files, long offset, long limit); + + /// + /// Lists the contents of folders in the backup + /// + /// The folders to list + /// The offset to start listing from + /// The maximum number of entries to list + /// Whether to include extended data + /// The folder contents results + Task ListFolderAsync(string[]? folders, long offset, long limit, bool extendedData); + + /// + /// Lists remote files in the backend + /// + /// The list of remote results + Task ListRemoteAsync(); + + /// + /// Purges broken files from the backup + /// + /// The filter to apply + /// The purge broken files results + Task PurgeBrokenFilesAsync(IFilter? filter); + + /// + /// Purges files from the backup + /// + /// The filter to apply + /// The purge files results + Task PurgeFilesAsync(IFilter? filter); + + /// + /// Reads lock information + /// + /// The lock information results + Task ReadLockInfoAsync(); + + /// + /// Repairs the backup + /// + /// An optional filter to apply + /// The repair results + Task RepairAsync(IFilter? filter = null); + + /// + /// Restores files from the backup + /// + /// The paths to restore + /// An optional filter to apply + /// The restore results + Task RestoreAsync(string[]? paths, IFilter? filter = null); + + /// + /// Restores control files from the backup + /// + /// The control files to restore + /// An optional filter to apply + /// The restore control files results + Task RestoreControlFilesAsync(IEnumerable? files = null, IFilter? filter = null); + + + + /// + /// Searches for entries in the backup + /// + /// The path prefixes to search in + /// The filter to apply + /// The offset to start searching from + /// The maximum number of results to return + /// Whether to include extended data + /// The search results + Task SearchEntriesAsync(string[]? pathprefixes, IFilter? filter, long offset, long limit, bool extendedData); + + /// + /// Sends an email notification + /// + /// The send mail results + Task SendMailAsync(); + + /// + /// Sets the locks on the backup + /// + /// The set lock results + Task SetLocksAsync(); + + /// + /// Sets the secret provider to use for all operations + /// + /// The secret provider to use + Task SetSecretProviderAsync(ISecretProvider? secretProvider); + + /// + /// Sets the throttle speeds for uploads and downloads + /// + /// Maximum upload speed in bytes per second + /// Maximum download speed in bytes per second + Task SetThrottleSpeedsAsync(long maxUploadPrSecond, long maxDownloadPrSecond); + + /// + /// Stops the current operation + /// + Task StopAsync(); + + /// + /// Pauses the current operation + /// + /// Whether to also pause transfers + Task PauseAsync(bool alsoTransfers); + + /// + /// Resumes a paused operation + /// + Task ResumeAsync(); + + /// + /// Appends a message sink to the controller + /// + /// The message sink to append + Task AppendSinkAsync(IMessageSink sink); + + /// + /// Gets system information + /// + /// The system information results + Task SystemInfoAsync(); + + /// + /// Tests the backup by verifying data integrity + /// + /// The number of samples to test + /// The test results + Task TestAsync(long samples = 1); + + /// + /// Tests the filter against the backup + /// + /// The paths to test + /// An optional filter to apply + /// The test filter results + Task TestFilterAsync(string[] paths, IFilter? filter = null); + + /// + /// Updates the database with version information + /// + /// An optional filter to apply + /// The recreate database results + Task UpdateDatabaseWithVersionsAsync(IFilter? filter = null); + + /// + /// Performs a vacuum operation on the database + /// + /// The vacuum results + Task VacuumAsync(); +} diff --git a/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs b/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs index 6d0591235..7bcba55f8 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackupDatabase.cs @@ -57,91 +57,91 @@ namespace Duplicati.Library.Main.Operation.Backup public Task FindBlockIDAsync(string hash, long size, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .FindBlockID(hash, size, cancellationToken) + .FindBlockIDAsync(hash, size, cancellationToken) .ConfigureAwait(false) ); } public Task AddBlockAsync(string hash, long size, long volumeid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AddBlock(hash, size, volumeid, cancellationToken) + .AddBlockAsync(hash, size, volumeid, cancellationToken) .ConfigureAwait(false) ); } public Task GetFileHashAsync(long fileid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetFileHash(fileid, cancellationToken) + .GetFileHashAsync(fileid, cancellationToken) .ConfigureAwait(false) ); } public Task<(bool, long)> AddMetadatasetAsync(string hash, long size, long blocksetid, IMetahash metahash, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AddMetadataset(hash, size, blocksetid, metahash, cancellationToken) + .AddMetadatasetAsync(hash, size, blocksetid, metahash, cancellationToken) .ConfigureAwait(false) ); } public Task<(bool, long)> GetMetadataIDAsync(string hash, long size, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetMetadatasetID(hash, size, cancellationToken) + .GetMetadatasetIDAsync(hash, size, cancellationToken) .ConfigureAwait(false) ); } public Task AddDirectoryEntryAsync(string filename, long metadataid, DateTime lastModified, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AddDirectoryEntry(filename, metadataid, lastModified, cancellationToken) + .AddDirectoryEntryAsync(filename, metadataid, lastModified, cancellationToken) .ConfigureAwait(false) ); } public Task AddSymlinkEntryAsync(string filename, long metadataid, DateTime lastModified, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AddSymlinkEntry(filename, metadataid, lastModified, cancellationToken) + .AddSymlinkEntryAsync(filename, metadataid, lastModified, cancellationToken) .ConfigureAwait(false) ); } public Task<(string MetadataHash, long Size)?> GetMetadataHashAndSizeForFileAsync(long fileid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetMetadataHashAndSizeForFile(fileid, cancellationToken) + .GetMetadataHashAndSizeForFileAsync(fileid, cancellationToken) .ConfigureAwait(false) ); } public Task<(long, DateTime, long)> GetFileLastModifiedAsync(long prefixid, string path, long lastfilesetid, bool includeLength, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetFileLastModified(prefixid, path, lastfilesetid, includeLength, cancellationToken) + .GetFileLastModifiedAsync(prefixid, path, lastfilesetid, includeLength, cancellationToken) .ConfigureAwait(false) ); } public Task GetFileEntryAsync(long prefixid, string path, long lastfilesetid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => { var (id, oldModified, lastFileSize, oldMetahash, oldMetasize) = - await m_database.GetFileEntry(prefixid, path, lastfilesetid, cancellationToken) + await m_database.GetFileEntryAsync(prefixid, path, lastfilesetid, cancellationToken) .ConfigureAwait(false); return @@ -161,75 +161,75 @@ namespace Duplicati.Library.Main.Operation.Backup public Task AddBlocksetAsync(string filehash, long size, int blocksize, IEnumerable hashlist, IEnumerable blocklisthashes, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => { var (_, blocksetid) = await m_database - .AddBlockset(filehash, size, blocksize, hashlist, blocklisthashes, cancellationToken) + .AddBlocksetAsync(filehash, size, blocksize, hashlist, blocklisthashes, cancellationToken) .ConfigureAwait(false); return blocksetid; }); } - public Task GetOrCreatePathPrefix(string prefix, CancellationToken cancellationToken) + public Task GetOrCreatePathPrefixAsync(string prefix, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetOrCreatePathPrefix(prefix, cancellationToken) + .GetOrCreatePathPrefixAsync(prefix, cancellationToken) .ConfigureAwait(false) ); } public Task AddFileAsync(long prefixid, string filename, DateTime lastmodified, long blocksetid, long metadataid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AddFile(prefixid, filename, lastmodified, blocksetid, metadataid, cancellationToken) + .AddFileAsync(prefixid, filename, lastmodified, blocksetid, metadataid, cancellationToken) .ConfigureAwait(false) ); } public Task AddUnmodifiedAsync(long fileid, DateTime lastModified, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AddKnownFile(fileid, lastModified, cancellationToken) + .AddKnownFileAsync(fileid, lastModified, cancellationToken) .ConfigureAwait(false) ); } public Task RemoveDuplicatePathsFromFilesetAsync(long filesetId, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .RemoveDuplicatePathsFromFileset(filesetId, cancellationToken) + .RemoveDuplicatePathsFromFilesetAsync(filesetId, cancellationToken) .ConfigureAwait(false) ); } public Task MoveBlockToVolumeAsync(string blockkey, long size, long sourcevolumeid, long targetvolumeid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .MoveBlockToVolume(blockkey, size, sourcevolumeid, targetvolumeid, cancellationToken) + .MoveBlockToVolumeAsync(blockkey, size, sourcevolumeid, targetvolumeid, cancellationToken) .ConfigureAwait(false) ); } public Task SafeDeleteRemoteVolumeAsync(string remotename, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .SafeDeleteRemoteVolume(remotename, cancellationToken) + .SafeDeleteRemoteVolumeAsync(remotename, cancellationToken) .ConfigureAwait(false) ); } public Task GetBlocklistHashesAsync(string remotename, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetBlocklistHashes(remotename, cancellationToken) + .GetBlocklistHashesAsync(remotename, cancellationToken) .ConfigureAwait(false) ); } @@ -239,37 +239,37 @@ namespace Duplicati.Library.Main.Operation.Backup if (indexvolume != null) return Task.FromResult(true); - return RunOnMain(async () => + return RunOnMainAsync(async () => { await m_database - .AddIndexBlockLink(indexvolume.VolumeID, blockvolume.VolumeID, cancellationToken) + .AddIndexBlockLinkAsync(indexvolume.VolumeID, blockvolume.VolumeID, cancellationToken) .ConfigureAwait(false); indexvolume.StartVolume(blockvolume.RemoteFilename); - await foreach (var b in m_database.GetBlocks(blockvolume.VolumeID, cancellationToken).ConfigureAwait(false)) + await foreach (var b in m_database.GetBlocksAsync(blockvolume.VolumeID, cancellationToken).ConfigureAwait(false)) indexvolume.AddBlock(b.Hash, b.Size); await m_database - .UpdateRemoteVolume(indexvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) + .UpdateRemoteVolumeAsync(indexvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) .ConfigureAwait(false); }); } public Task AppendFilesFromPreviousSetAsync(string[] deletedFilelist, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AppendFilesFromPreviousSet(deletedFilelist, cancellationToken) + .AppendFilesFromPreviousSetAsync(deletedFilelist, cancellationToken) .ConfigureAwait(false) ); } public Task AppendFilesFromPreviousSetAsync(string[] deletedFilelist, long filesetid, long prevId, DateTime timestamp, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AppendFilesFromPreviousSet(deletedFilelist, filesetid, prevId, timestamp, cancellationToken) + .AppendFilesFromPreviousSetAsync(deletedFilelist, filesetid, prevId, timestamp, cancellationToken) .ConfigureAwait(false) ); } @@ -282,18 +282,18 @@ namespace Duplicati.Library.Main.Operation.Backup /// Optional exclusion predicate (true = exclude file) public Task AppendFilesFromPreviousSetWithPredicateAsync(Func exclusionPredicate, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .AppendFilesFromPreviousSetWithPredicate(exclusionPredicate, cancellationToken) + .AppendFilesFromPreviousSetWithPredicateAsync(exclusionPredicate, cancellationToken) .ConfigureAwait(false) ); } public Task[]> GetIncompleteFilesetsAsync(CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetIncompleteFilesets(cancellationToken) + .GetIncompleteFilesetsAsync(cancellationToken) .OrderBy(x => x.Value) .ToArrayAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) @@ -302,9 +302,9 @@ namespace Duplicati.Library.Main.Operation.Backup public Task[]> GetFilesetTimesAsync(CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .FilesetTimes(cancellationToken) + .FilesetTimesAsync(cancellationToken) .ToArrayAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); @@ -312,10 +312,10 @@ namespace Duplicati.Library.Main.Operation.Backup public Task CreateFilesetAsync(long volumeID, DateTime fileTime, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => { var fs = await m_database - .CreateFileset(volumeID, fileTime, cancellationToken) + .CreateFilesetAsync(volumeID, fileTime, cancellationToken) .ConfigureAwait(false); await m_database.Transaction @@ -328,104 +328,104 @@ namespace Duplicati.Library.Main.Operation.Backup public Task LinkFilesetToVolumeAsync(long filesetid, long volumeid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .LinkFilesetToVolume(filesetid, volumeid, cancellationToken) + .LinkFilesetToVolumeAsync(filesetid, volumeid, cancellationToken) .ConfigureAwait(false) ); } public Task WriteFilesetAsync(FilesetVolumeWriter fsw, long filesetid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .WriteFileset(fsw, filesetid, cancellationToken) + .WriteFilesetAsync(fsw, filesetid, cancellationToken) .ConfigureAwait(false) ); } public Task PushTimestampChangesToPreviousVersionAsync(long filesetid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .PushTimestampChangesToPreviousVersion(filesetid, cancellationToken) + .PushTimestampChangesToPreviousVersionAsync(filesetid, cancellationToken) .ConfigureAwait(false) ); } public Task UpdateFilesetAndMarkAsFullBackupAsync(long filesetid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .UpdateFullBackupStateInFileset(filesetid, true, cancellationToken) + .UpdateFullBackupStateInFilesetAsync(filesetid, true, cancellationToken) .ConfigureAwait(false) ); } public Task> GetMissingIndexFilesAsync(CancellationToken cancellationToken) { - return RunOnMain(() => m_database.GetMissingIndexFiles(cancellationToken)); + return RunOnMainAsync(() => m_database.GetMissingIndexFilesAsync(cancellationToken)); } public Task UpdateChangeStatisticsAsync(BackupResults result, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .UpdateChangeStatistics(result, cancellationToken) + .UpdateChangeStatisticsAsync(result, cancellationToken) .ConfigureAwait(false) ); } public Task VerifyConsistencyAsync(int blocksize, int blockhashSize, bool verifyfilelists, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .VerifyConsistency(blocksize, blockhashSize, verifyfilelists, cancellationToken) + .VerifyConsistencyAsync(blocksize, blockhashSize, verifyfilelists, cancellationToken) .ConfigureAwait(false) ); } public Task RemoveRemoteVolumeAsync(string remoteFilename, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .RemoveRemoteVolume(remoteFilename, cancellationToken) + .RemoveRemoteVolumeAsync(remoteFilename, cancellationToken) .ConfigureAwait(false) ); } public Task GetRemoteVolumeFromFilesetIDAsync(long filesetID, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .GetRemoteVolumeFromFilesetID(filesetID, cancellationToken) + .GetRemoteVolumeFromFilesetIDAsync(filesetID, cancellationToken) .ConfigureAwait(false) ); } public Task CreateChangeJournalDataAsync(IEnumerable journalData, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .CreateChangeJournalData(journalData, cancellationToken) + .CreateChangeJournalDataAsync(journalData, cancellationToken) .ConfigureAwait(false) ); } public Task UpdateChangeJournalDataAsync(IEnumerable journalData, long lastfilesetid, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .UpdateChangeJournalData(journalData, lastfilesetid, cancellationToken) + .UpdateChangeJournalDataAsync(journalData, lastfilesetid, cancellationToken) .ConfigureAwait(false) ); } public Task IsBlocklistHashKnownAsync(string hash, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_database - .IsBlocklistHashKnown(hash, cancellationToken) + .IsBlocklistHashKnownAsync(hash, cancellationToken) .ConfigureAwait(false) ); } diff --git a/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs b/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs index c759b7712..8b8ce1440 100644 --- a/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs +++ b/Duplicati/Library/Main/Operation/Backup/BackupStatsCollector.cs @@ -39,7 +39,7 @@ namespace Duplicati.Library.Main.Operation.Backup m_res = res; } - private async Task WithLock(Action action) + private async Task WithLockAsync(Action action) { await semaphoreSlim.WaitAsync(); try @@ -52,44 +52,44 @@ namespace Duplicati.Library.Main.Operation.Backup } } - public Task AddOpenedFile(long size) + public Task AddOpenedFileAsync(long size) { - return WithLock(() => + return WithLockAsync(() => { m_res.SizeOfOpenedFiles += size; m_res.OpenedFiles++; }); } - public Task AddTimestampChangedFile() + public Task AddTimestampChangedFileAsync() { - return WithLock(() => + return WithLockAsync(() => { m_res.TimestampChangedFiles++; }); } - public Task AddAddedFile(long size) + public Task AddAddedFileAsync(long size) { - return WithLock(() => + return WithLockAsync(() => { m_res.SizeOfAddedFiles += size; m_res.AddedFiles++; }); } - public Task AddModifiedFile(long size) + public Task AddModifiedFileAsync(long size) { - return WithLock(() => + return WithLockAsync(() => { m_res.SizeOfModifiedFiles += size; m_res.ModifiedFiles++; }); } - public Task AddExaminedFile(long size) + public Task AddExaminedFileAsync(long size) { - return WithLock(() => + return WithLockAsync(() => { m_res.SizeOfExaminedFiles += size; m_res.ExaminedFiles++; diff --git a/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs b/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs index 25e6f91a3..12d2b1367 100644 --- a/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/Backup/CountFilesHandler.cs @@ -30,7 +30,7 @@ namespace Duplicati.Library.Main.Operation.Backup { internal static class CountFilesHandler { - public static async Task Run( + public static async Task RunAsync( ISourceProvider sources, UsnJournalService journalService, BackupResults result, @@ -45,7 +45,7 @@ namespace Duplicati.Library.Main.Operation.Backup using (Logging.Log.StartIsolatingScope(true)) { Channels channels = new(); - var enumeratorTask = FileEnumerationProcess.Run( + var enumeratorTask = FileEnumerationProcess.RunAsync( channels, sources, journalService, options.FileAttributeFilter, filter, options.SymlinkPolicy, options.HardlinkPolicy, options.DisableBackupExclusionXattr, options.ExcludeEmptyFolders, options.IgnoreFilenames, @@ -63,7 +63,7 @@ namespace Duplicati.Library.Main.Operation.Backup try { - while (await taskreader.ProgressRendevouz() && !token.IsCancellationRequested) + while (await taskreader.ProgressRendevouzAsync() && !token.IsCancellationRequested) { var entry = await self.Input.ReadAsync(); if (entry.IsFolder) diff --git a/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs b/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs index aec947f71..b34cd4566 100644 --- a/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs +++ b/Duplicati/Library/Main/Operation/Backup/DataBlockProcessor.cs @@ -36,7 +36,7 @@ namespace Duplicati.Library.Main.Operation.Backup { private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(DataBlockProcessor)); - public static Task Run(Channels channels, BackupDatabase database, IBackendManager backendManager, Options options, ITaskReader taskreader) + public static Task RunAsync(Channels channels, BackupDatabase database, IBackendManager backendManager, Options options, ITaskReader taskreader) { return AutomationExtensions.RunTask( new @@ -116,7 +116,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (newBlock) { - await blockvolume.AddBlock(b.HashKey, b.Data, b.Offset, (int)b.Size, b.Hint) + await blockvolume.AddBlockAsync(b.HashKey, b.Data, b.Offset, (int)b.Size, b.Hint) .ConfigureAwait(false); if (indexvolume != null) indexvolume.AddBlock(b.HashKey, b.Size); @@ -135,7 +135,7 @@ namespace Duplicati.Library.Main.Operation.Backup { // TODO: It is much easier to let the BackendManager deal with index files, // but it adds a bit of strain to the database - indexVolumeCopy = await indexvolume.CreateVolume(blockvolume.RemoteFilename, options, database, taskreader.ProgressToken).ConfigureAwait(false); + indexVolumeCopy = await indexvolume.CreateVolumeAsync(blockvolume.RemoteFilename, options, database, taskreader.ProgressToken).ConfigureAwait(false); // Create link before upload is started, it will be removed later if upload fails await database.AddIndexBlockLinkAsync(indexVolumeCopy.VolumeID, blockvolume.VolumeID, taskreader.ProgressToken).ConfigureAwait(false); } @@ -159,7 +159,7 @@ namespace Duplicati.Library.Main.Operation.Backup } // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); sw_workload.Stop(); } diff --git a/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs b/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs index 3f8868949..014b6c1aa 100644 --- a/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs +++ b/Duplicati/Library/Main/Operation/Backup/FileBlockProcessor.cs @@ -36,7 +36,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string FILELOGTAG = Logging.Log.LogTagFromType(typeof(FileBlockProcessor)) + ".FileEntry"; - public static Task Run(Channels channels, Options options, BackupDatabase database, BackupStatsCollector stats, ITaskReader taskreader) + public static Task RunAsync(Channels channels, Options options, BackupDatabase database, BackupStatsCollector stats, ITaskReader taskreader) { return AutomationExtensions.RunTask( new @@ -52,7 +52,7 @@ namespace Duplicati.Library.Main.Operation.Backup var e = await self.Input.ReadAsync().ConfigureAwait(false); // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); try { @@ -80,9 +80,9 @@ namespace Duplicati.Library.Main.Operation.Backup }); using (var fs = await e.Entry.OpenRead(taskreader.ProgressToken).ConfigureAwait(false)) - filestreamdata = await StreamBlock.ProcessStream(self.StreamBlockChannel, e.Entry.Path, fs, false, hint).ConfigureAwait(false); + filestreamdata = await StreamBlock.ProcessStreamAsync(self.StreamBlockChannel, e.Entry.Path, fs, false, hint).ConfigureAwait(false); - await stats.AddOpenedFile(filestreamdata.Streamlength).ConfigureAwait(false); + await stats.AddOpenedFileAsync(filestreamdata.Streamlength).ConfigureAwait(false); var metadataid = await metatask.ConfigureAwait(false); var filekey = filestreamdata.Streamhash; @@ -97,14 +97,14 @@ namespace Duplicati.Library.Main.Operation.Backup if (e.OldId < 0) { - await stats.AddAddedFile(filesize); + await stats.AddAddedFileAsync(filesize); if (options.Dryrun) Logging.Log.WriteVerboseMessage(FILELOGTAG, "WouldAddNewFile", "Would add new file {0}, size {1}", e.Entry.Path, Library.Utility.Utility.FormatSizeString(filesize)); } else { - await stats.AddModifiedFile(filesize); + await stats.AddModifiedFileAsync(filesize); if (options.Dryrun) Logging.Log.WriteVerboseMessage(FILELOGTAG, "WouldAddChangedFile", "Would add changed file {0}, size {1}", e.Entry.Path, Library.Utility.Utility.FormatSizeString(filesize)); @@ -119,7 +119,7 @@ namespace Duplicati.Library.Main.Operation.Backup } else if (e.TimestampChanged) { - await stats.AddTimestampChangedFile(); + await stats.AddTimestampChangedFileAsync(); Logging.Log.WriteVerboseMessage(FILELOGTAG, "FileTimestampChanged", "File has only timestamp changes {0}", e.Entry.Path); await database.AddUnmodifiedAsync(e.OldId, e.LastWrite, taskreader.ProgressToken).ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs b/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs index b9d1b8c55..ab2436bc2 100644 --- a/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/FileEnumerationProcess.cs @@ -49,7 +49,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string FILTER_LOGTAG = Logging.Log.LogTagFromType(typeof(FileEnumerationProcess)); - public static Task Run( + public static Task RunAsync( Channels channels, ISourceProvider sourceProvider, UsnJournalService? journalService, @@ -101,7 +101,7 @@ namespace Duplicati.Library.Main.Operation.Backup // Shared filter function with bound variables ValueTask FilterEntry(ISourceProviderEntry entry) - => SourceFileEntryFilter(entry, blacklistPaths, hardlinkPolicy, symlinkPolicy, hardlinkmap, fileAttributeFilter, enumeratefilter, ignorenames, mixinqueue, disableBackupExclusionXattr, token); + => SourceFileEntryFilterAsync(entry, blacklistPaths, hardlinkPolicy, symlinkPolicy, hardlinkmap, fileAttributeFilter, enumeratefilter, ignorenames, mixinqueue, disableBackupExclusionXattr, token); // Prepare the work list IAsyncEnumerable worklist; @@ -154,19 +154,19 @@ namespace Duplicati.Library.Main.Operation.Backup // It should be possible to remove RecurseEntries from the GetModifiedSources() // enumeration result. // Such a change requires significant testing as there are many pitfalls with USN. - worklist = RecurseEntries(journalService.GetModifiedSources(FilterEntry, token), + worklist = RecurseEntriesAsync(journalService.GetModifiedSources(FilterEntry, token), FilterEntry, token ) .Concat( - RecurseEntries(journalService.GetFullScanSources(token), + RecurseEntriesAsync(journalService.GetFullScanSources(token), FilterEntry, token) ); } else { - worklist = RecurseEntries(sourceProvider.Enumerate(token), + worklist = RecurseEntriesAsync(sourceProvider.Enumerate(token), FilterEntry, token ); @@ -175,12 +175,12 @@ namespace Duplicati.Library.Main.Operation.Backup if (token.IsCancellationRequested) return; - var source = ExpandWorkList(worklist, mixinqueue, emitfilter, enumeratefilter, token); + var source = ExpandWorkListAsync(worklist, mixinqueue, emitfilter, enumeratefilter, token); // TODO: There was a call to DistinctBy here, but this would cause all paths to be stored in memory //.DistinctBy(x => x.Path, Library.Utility.Utility.IsFSCaseSensitive ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase); if (excludeemptyfolders) - source = ExcludeEmptyFolders(source, token); + source = ExcludeEmptyFoldersAsync(source, token); // Process each path, and dequeue the mixins with symlinks as we go await foreach (var s in source.WithCancellation(token).ConfigureAwait(false)) @@ -195,7 +195,7 @@ namespace Duplicati.Library.Main.Operation.Backup taskreader.TestMethodCallback?.Invoke(s.Path); #endif // Stop if requested - if (token.IsCancellationRequested || !await taskreader.ProgressRendevouz().ConfigureAwait(false)) + if (token.IsCancellationRequested || !await taskreader.ProgressRendevouzAsync().ConfigureAwait(false)) { onStopRequested?.Invoke(); return; @@ -228,7 +228,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// /// The list without empty folders. /// The list with potential empty folders. - private static async IAsyncEnumerable ExcludeEmptyFolders(IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken) + private static async IAsyncEnumerable ExcludeEmptyFoldersAsync(IAsyncEnumerable source, [EnumeratorCancellation] CancellationToken cancellationToken) { var pathstack = new Stack(); @@ -287,7 +287,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// The entries to recurse /// The filter to apply /// - private static async IAsyncEnumerable RecurseEntries(IAsyncEnumerable entries, Func> filter, [EnumeratorCancellation] CancellationToken cancellationToken) + private static async IAsyncEnumerable RecurseEntriesAsync(IAsyncEnumerable entries, Func> filter, [EnumeratorCancellation] CancellationToken cancellationToken) { var work = new Stack(); @@ -340,7 +340,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// The mix in queue. /// The emitfilter. /// The enumeratefilter. - private static async IAsyncEnumerable ExpandWorkList(IAsyncEnumerable worklist, Queue mixinqueue, Library.Utility.IFilter emitfilter, Library.Utility.IFilter? enumeratefilter, [EnumeratorCancellation] CancellationToken cancellationToken) + private static async IAsyncEnumerable ExpandWorkListAsync(IAsyncEnumerable worklist, Queue mixinqueue, Library.Utility.IFilter emitfilter, Library.Utility.IFilter? enumeratefilter, [EnumeratorCancellation] CancellationToken cancellationToken) { // Process each path, and dequeue the mixins with symlinks as we go await foreach (var s in worklist.WithCancellation(cancellationToken).ConfigureAwait(false)) @@ -428,7 +428,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// The ignore names. /// The mixin queue. /// True if the path should be returned, false otherwise. - private static async ValueTask SourceFileEntryFilter(ISourceProviderEntry entry, HashSet blacklistPaths, Options.HardlinkStrategy hardlinkPolicy, Options.SymlinkStrategy symlinkPolicy, Dictionary hardlinkmap, FileAttributes fileAttributeFilter, Duplicati.Library.Utility.IFilter enumeratefilter, string[]? ignorenames, Queue mixinqueue, bool disableBackupExclusionXattr, CancellationToken cancellationToken) + private static async ValueTask SourceFileEntryFilterAsync(ISourceProviderEntry entry, HashSet blacklistPaths, Options.HardlinkStrategy hardlinkPolicy, Options.SymlinkStrategy symlinkPolicy, Dictionary hardlinkmap, FileAttributes fileAttributeFilter, Duplicati.Library.Utility.IFilter enumeratefilter, string[]? ignorenames, Queue mixinqueue, bool disableBackupExclusionXattr, CancellationToken cancellationToken) { // Do the course pre-filtering first if (!PreFilterSourceEntry(entry, blacklistPaths)) @@ -514,7 +514,7 @@ namespace Duplicati.Library.Main.Operation.Backup } // Filter entries that are marked as excluded from backups via filesystem extended attributes - if (!disableBackupExclusionXattr && await HasBackupExclusionAttribute(entry, cancellationToken).ConfigureAwait(false)) + if (!disableBackupExclusionXattr && await HasBackupExclusionAttributeAsync(entry, cancellationToken).ConfigureAwait(false)) { Logging.Log.WriteVerboseMessage(FILTER_LOGTAG, "ExcludingPathFromBackupAttribute", "Excluding path marked as excluded from backups via filesystem attribute: {0}", entry.Path); return false; @@ -595,7 +595,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// /// The entry to check. /// True if the entry has the attribute, false otherwise. - private static async Task HasBackupExclusionAttribute(ISourceProviderEntry entry, CancellationToken token) + private static async Task HasBackupExclusionAttributeAsync(ISourceProviderEntry entry, CancellationToken token) { try { diff --git a/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs b/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs index 86fe323dd..97aa209df 100644 --- a/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/FilePreFilterProcess.cs @@ -40,7 +40,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// private static readonly string FILELOGTAG = Logging.Log.LogTagFromType(typeof(FilePreFilterProcess)) + ".FileEntry"; - public static Task Run(Channels channels, Options options, BackupStatsCollector stats, BackupDatabase database, ITaskReader taskreader) + public static Task RunAsync(Channels channels, Options options, BackupStatsCollector stats, BackupDatabase database, ITaskReader taskreader) { return AutomationExtensions.RunTask( new @@ -80,7 +80,7 @@ namespace Duplicati.Library.Main.Operation.Backup LogExceptionHelper.LogCommonWarning(ex, FILELOGTAG, "FailedToReadSize", e.Entry.Path, "Failed to read size on \"{0}\""); } - await stats.AddExaminedFile(filestatsize); + await stats.AddExaminedFileAsync(filestatsize); // Stop now if the file is too large var tooLargeFile = SKIPFILESLARGERTHAN != 0 && filestatsize >= 0 && filestatsize > SKIPFILESLARGERTHAN; @@ -130,7 +130,7 @@ namespace Duplicati.Library.Main.Operation.Backup } // Compute current metadata - e.MetaHashAndSize = SKIPMETADATA ? EMPTY_METADATA : Utility.WrapMetadata(await MetadataGenerator.GenerateMetadata(e.Entry, e.Attributes, options, taskreader.ProgressToken), options); + e.MetaHashAndSize = SKIPMETADATA ? EMPTY_METADATA : Utility.WrapMetadata(await MetadataGenerator.GenerateMetadataAsync(e.Entry, e.Attributes, options, taskreader.ProgressToken), options); e.MetadataChanged = !SKIPMETADATA && (e.MetaHashAndSize.Blob.Length != e.OldMetaSize || e.MetaHashAndSize.FileHash != e.OldMetaHash); // Check if the file is new, or something indicates a change diff --git a/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs b/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs index 9896bcd58..bb9cc07c0 100644 --- a/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs +++ b/Duplicati/Library/Main/Operation/Backup/MetadataGenerator.cs @@ -34,7 +34,7 @@ namespace Duplicati.Library.Main.Operation.Backup { private static readonly string METALOGTAG = Logging.Log.LogTagFromType(typeof(MetadataGenerator)) + ".Metadata"; - public static async Task> GenerateMetadata(ISourceProviderEntry entry, System.IO.FileAttributes attributes, Options options, CancellationToken token) + public static async Task> GenerateMetadataAsync(ISourceProviderEntry entry, System.IO.FileAttributes attributes, Options options, CancellationToken token) { try { diff --git a/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs b/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs index f1b937bf3..0a484c73b 100644 --- a/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/MetadataPreProcess.cs @@ -64,7 +64,7 @@ namespace Duplicati.Library.Main.Operation.Backup public bool TimestampChanged; } - public static Task Run(Channels channels, Options options, BackupDatabase database, long lastfilesetid, ITaskReader taskreader) + public static Task RunAsync(Channels channels, Options options, BackupDatabase database, long lastfilesetid, ITaskReader taskreader) { return AutomationExtensions.RunTask(new { @@ -86,7 +86,7 @@ namespace Duplicati.Library.Main.Operation.Backup var entry = await self.Input.ReadAsync(); // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); var lastwrite = new DateTime(0, DateTimeKind.Utc); var attributes = entry.IsFolder @@ -112,7 +112,7 @@ namespace Duplicati.Library.Main.Operation.Backup } // If we only have metadata, stop here - if (await ProcessMetadata(entry, attributes, lastwrite, options, emptymetadata, database, self.StreamBlockChannel, taskreader.ProgressToken).ConfigureAwait(false)) + if (await ProcessMetadataAsync(entry, attributes, lastwrite, options, emptymetadata, database, self.StreamBlockChannel, taskreader.ProgressToken).ConfigureAwait(false)) { try { @@ -123,7 +123,7 @@ namespace Duplicati.Library.Main.Operation.Backup prefixid = prevprefix.Value; else { - prefixid = await database.GetOrCreatePathPrefix(split.Key, taskreader.ProgressToken); + prefixid = await database.GetOrCreatePathPrefixAsync(split.Key, taskreader.ProgressToken); prevprefix = new KeyValuePair(split.Key, prefixid); } @@ -188,7 +188,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// The channel to write stream blocks to. /// The cancellation token to use for the operation. /// True if the path should be submitted to more analysis, false if there is nothing else to do - private static async Task ProcessMetadata(ISourceProviderEntry entry, FileAttributes attributes, DateTime lastwrite, Options options, IMetahash emptymetadata, BackupDatabase database, IWriteChannel streamblockchannel, CancellationToken cancellationToken) + private static async Task ProcessMetadataAsync(ISourceProviderEntry entry, FileAttributes attributes, DateTime lastwrite, Options options, IMetahash emptymetadata, BackupDatabase database, IWriteChannel streamblockchannel, CancellationToken cancellationToken) { if (entry.IsSymlink) { @@ -216,7 +216,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (options.SymlinkPolicy == Options.SymlinkStrategy.Store) { - var metadata = await MetadataGenerator.GenerateMetadata(entry, attributes, options, cancellationToken); + var metadata = await MetadataGenerator.GenerateMetadataAsync(entry, attributes, options, cancellationToken); if (!metadata.ContainsKey("CoreSymlinkTarget")) metadata["CoreSymlinkTarget"] = symlinkTarget; @@ -242,7 +242,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (!options.SkipMetadata) { - metahash = Utility.WrapMetadata(await MetadataGenerator.GenerateMetadata(entry, attributes, options, cancellationToken), options); + metahash = Utility.WrapMetadata(await MetadataGenerator.GenerateMetadataAsync(entry, attributes, options, cancellationToken), options); } else { @@ -271,7 +271,7 @@ namespace Duplicati.Library.Main.Operation.Backup { StreamProcessResult res; using (var ms = new MemoryStream(meta.Blob)) - res = await StreamBlock.ProcessStream(streamblockchannel, path, ms, true, CompressionHint.Default); + res = await StreamBlock.ProcessStreamAsync(streamblockchannel, path, ms, true, CompressionHint.Default); return await database.AddMetadatasetAsync(res.Streamhash, res.Streamlength, res.Blocksetid, meta, cancellationToken).ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs b/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs index 1e52e4318..23923ad07 100644 --- a/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs +++ b/Duplicati/Library/Main/Operation/Backup/ProgressHandler.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2026, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2026, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -33,7 +33,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal static class ProgressHandler { - public static Task Run(Channels channels, BackupResults stat) + public static Task RunAsync(Channels channels, BackupResults stat) { return AutomationExtensions.RunTask(new { @@ -48,10 +48,10 @@ namespace Duplicati.Library.Main.Operation.Backup long processedFileSize = 0; string current = null; - while(true) + while (true) { var t = await self.Input.ReadAsync(); - switch(t.Type) + switch (t.Type) { case EventType.FileStarted: filesStarted[t.Filepath] = t.Length; diff --git a/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs b/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs index e45374e40..13ef8503b 100644 --- a/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs +++ b/Duplicati/Library/Main/Operation/Backup/RecreateMissingIndexFiles.cs @@ -31,20 +31,20 @@ internal static class RecreateMissingIndexFiles /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(RecreateMissingIndexFiles)); - public static async Task Run(BackupDatabase database, IBackendManager backendManager, Options options, ITaskReader taskreader) + public static async Task RunAsync(BackupDatabase database, IBackendManager backendManager, Options options, ITaskReader taskreader) { if (options.IndexfilePolicy != Options.IndexFileStrategy.None) { var anyfiles = false; await foreach (var blockfile in (await database.GetMissingIndexFilesAsync(taskreader.ProgressToken).ConfigureAwait(false)).ConfigureAwait(false)) { - if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + if (!await taskreader.ProgressRendevouzAsync().ConfigureAwait(false)) return; Logging.Log.WriteInformationMessage(LOGTAG, "RecreateMissingIndexFile", "Re-creating missing index file for {0}", blockfile); - var w = await Common.IndexVolumeCreator.CreateIndexVolume(blockfile, options, database, taskreader.ProgressToken).ConfigureAwait(false); + var w = await Common.IndexVolumeCreator.CreateIndexVolumeAsync(blockfile, options, database, taskreader.ProgressToken).ConfigureAwait(false); - if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + if (!await taskreader.ProgressRendevouzAsync().ConfigureAwait(false)) return; anyfiles = true; diff --git a/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs b/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs index 918aaa478..47f0ecaf2 100644 --- a/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs +++ b/Duplicati/Library/Main/Operation/Backup/SpillCollectorProcess.cs @@ -48,7 +48,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// internal static class SpillCollectorProcess { - public static Task Run(Channels channels, Options options, BackupDatabase database, IBackendManager backendManager, ITaskReader taskreader) + public static Task RunAsync(Channels channels, Options options, BackupDatabase database, IBackendManager backendManager, ITaskReader taskreader) { return AutomationExtensions.RunTask( new @@ -100,7 +100,7 @@ namespace Duplicati.Library.Main.Operation.Backup while (lst.Count > 1) { // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); SpillVolumeRequest target = null; var source = lst[0]; @@ -123,7 +123,7 @@ namespace Duplicati.Library.Main.Operation.Backup target = await GetNextTarget(source).ConfigureAwait(false); var len = rd.ReadBlock(file.Key, buffer); - await target.BlockVolume.AddBlock(file.Key, buffer, 0, len, Duplicati.Library.Interface.CompressionHint.Default).ConfigureAwait(false); + await target.BlockVolume.AddBlockAsync(file.Key, buffer, 0, len, Duplicati.Library.Interface.CompressionHint.Default).ConfigureAwait(false); await database.MoveBlockToVolumeAsync(file.Key, len, source.BlockVolume.VolumeID, target.BlockVolume.VolumeID, taskreader.ProgressToken).ConfigureAwait(false); if (target.IndexVolume != null) @@ -132,7 +132,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (target.BlockVolume.Filesize > options.VolumeSize - options.Blocksize) { target.BlockVolume.Close(); - await UploadVolumeAndIndex(target, options, database, backendManager, taskreader).ConfigureAwait(false); + await UploadVolumeAndIndexAsync(target, options, database, backendManager, taskreader).ConfigureAwait(false); target = null; } } @@ -150,23 +150,23 @@ namespace Duplicati.Library.Main.Operation.Backup foreach (var n in lst) { // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); n.BlockVolume.Close(); - await UploadVolumeAndIndex(n, options, database, backendManager, taskreader).ConfigureAwait(false); + await UploadVolumeAndIndexAsync(n, options, database, backendManager, taskreader).ConfigureAwait(false); } }); } - private static async Task UploadVolumeAndIndex(SpillVolumeRequest target, Options options, BackupDatabase database, IBackendManager backendManager, ITaskReader taskreader) + private static async Task UploadVolumeAndIndexAsync(SpillVolumeRequest target, Options options, BackupDatabase database, IBackendManager backendManager, ITaskReader taskreader) { IndexVolumeWriter indexVolumeCopy = null; if (target.IndexVolume != null) { // TODO: It is much easier to let the BackendManager deal with index files, // but it adds a bit of strain to the database - indexVolumeCopy = await target.IndexVolume.CreateVolume(target.BlockVolume.RemoteFilename, options, database, taskreader.ProgressToken).ConfigureAwait(false); + indexVolumeCopy = await target.IndexVolume.CreateVolumeAsync(target.BlockVolume.RemoteFilename, options, database, taskreader.ProgressToken).ConfigureAwait(false); // Create link before upload is started, it will be removed later if upload fails await database.AddIndexBlockLinkAsync(indexVolumeCopy.VolumeID, target.BlockVolume.VolumeID, taskreader.ProgressToken).ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs b/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs index fec3f51b0..099340ca2 100644 --- a/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs +++ b/Duplicati/Library/Main/Operation/Backup/StreamBlock.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2026, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2026, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using CoCoL; @@ -41,13 +41,13 @@ namespace Duplicati.Library.Main.Operation.Backup public CompressionHint Hint; public TaskCompletionSource Result; - public static async Task ProcessStream(IWriteChannel channel, string path, Stream stream, bool isMetadata, CompressionHint hint) + public static async Task ProcessStreamAsync(IWriteChannel channel, string path, Stream stream, bool isMetadata, CompressionHint hint) { var tcs = new TaskCompletionSource(); // limit the stream length to that found now, a fixed point in time var limitedStream = new Library.Utility.ReadLimitLengthStream(stream, stream.Length); - + var streamBlock = new StreamBlock { Path = path, @@ -56,9 +56,9 @@ namespace Duplicati.Library.Main.Operation.Backup Hint = hint, Result = tcs }; - + await channel.WriteAsync(streamBlock); - + return await tcs.Task.ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs b/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs index c85edd6d6..c4e24f90c 100644 --- a/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs +++ b/Duplicati/Library/Main/Operation/Backup/StreamBlockSplitter.cs @@ -39,7 +39,7 @@ namespace Duplicati.Library.Main.Operation.Backup private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(StreamBlockSplitter)); private static readonly string FILELOGTAG = LOGTAG + ".FileEntry"; - public static Task Run(Channels channels, Options options, BackupDatabase database, ITaskReader taskreader) + public static Task RunAsync(Channels channels, Options options, BackupDatabase database, ITaskReader taskreader) { return AutomationExtensions.RunTask( new @@ -63,7 +63,7 @@ namespace Duplicati.Library.Main.Operation.Backup while (true) { // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); var send_close = false; var filesize = 0L; diff --git a/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs b/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs index fac2d61d7..e8b16fff2 100644 --- a/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs +++ b/Duplicati/Library/Main/Operation/Backup/UploadRealFilelist.cs @@ -32,10 +32,10 @@ internal static class UploadRealFilelist /// private static readonly string LOGTAG = Logging.Log.LogTagFromType(typeof(UploadRealFilelist)); - public static async Task Run(BackupResults result, BackupDatabase db, IBackendManager backendManager, Options options, FilesetVolumeWriter filesetvolume, long filesetid, Common.ITaskReader taskreader, bool lastWasPartial) + public static async Task RunAsync(BackupResults result, BackupDatabase db, IBackendManager backendManager, Options options, FilesetVolumeWriter filesetvolume, long filesetid, Common.ITaskReader taskreader, bool lastWasPartial) { // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); // Update the reported source and backend changes using (new Logging.Timer(LOGTAG, "UpdateChangeStatistics", "UpdateChangeStatistics")) @@ -58,13 +58,13 @@ internal static class UploadRealFilelist filesetvolume.AddControlFile(p, options.GetCompressionHintFromFilename(p)); // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); await db.WriteFilesetAsync(filesetvolume, filesetid, taskreader.ProgressToken).ConfigureAwait(false); filesetvolume.Close(); // We ignore the stop signal, but not the pause and terminate - await taskreader.ProgressRendevouz().ConfigureAwait(false); + await taskreader.ProgressRendevouzAsync().ConfigureAwait(false); await db.UpdateRemoteVolumeAsync(filesetvolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, false, default, null, taskreader.ProgressToken).ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs b/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs index 563a1e157..f3157cda7 100644 --- a/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs +++ b/Duplicati/Library/Main/Operation/Backup/UploadSyntheticFilelist.cs @@ -48,7 +48,7 @@ namespace Duplicati.Library.Main.Operation.Backup /// The backend manager to use /// The last temporary file list volume /// - public static async Task Run(BackupDatabase database, Options options, BasicResults result, ITaskReader taskreader, IBackendManager backendManager, RemoteVolumeEntry lastTempFilelist) + public static async Task RunAsync(BackupDatabase database, Options options, BasicResults result, ITaskReader taskreader, IBackendManager backendManager, RemoteVolumeEntry lastTempFilelist) { // Check if we should upload a synthetic filelist if (options.DisableSyntheticFilelist || string.IsNullOrWhiteSpace(lastTempFilelist.Name) || lastTempFilelist.ID <= 0) @@ -68,7 +68,7 @@ namespace Duplicati.Library.Main.Operation.Backup if (!incompleteFilesets.Any()) return null; - if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + if (!await taskreader.ProgressRendevouzAsync().ConfigureAwait(false)) return null; result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreviousBackupFinalize); @@ -89,7 +89,7 @@ namespace Duplicati.Library.Main.Operation.Backup FilesetVolumeWriter fsw = null; try { - var fileTime = await FilesetVolumeWriter.ProbeUnusedFilenameName(database, options, incompleteSet.Value, taskreader.ProgressToken).ConfigureAwait(false); + var fileTime = await FilesetVolumeWriter.ProbeUnusedFilenameNameAsync(database, options, incompleteSet.Value, taskreader.ProgressToken).ConfigureAwait(false); syntheticFilelistTime = fileTime; fsw = new FilesetVolumeWriter(options, fileTime); fsw.VolumeID = await database.RegisterRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary, taskreader.ProgressToken).ConfigureAwait(false); @@ -108,7 +108,7 @@ namespace Duplicati.Library.Main.Operation.Backup await database.WriteFilesetAsync(fsw, newFilesetID, taskreader.ProgressToken).ConfigureAwait(false); fsw.Close(); - if (!await taskreader.ProgressRendevouz().ConfigureAwait(false)) + if (!await taskreader.ProgressRendevouzAsync().ConfigureAwait(false)) return null; await database.UpdateRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeState.Uploading, -1, null, false, default, null, taskreader.ProgressToken).ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Operation/BackupHandler.cs b/Duplicati/Library/Main/Operation/BackupHandler.cs index 450f8932f..521ce0697 100644 --- a/Duplicati/Library/Main/Operation/BackupHandler.cs +++ b/Duplicati/Library/Main/Operation/BackupHandler.cs @@ -80,8 +80,8 @@ namespace Duplicati.Library.Main.Operation /// The sources to get providers for /// The options to use /// The source providers - public static async Task GetSourceProvider(IEnumerable sources, Options options, CancellationToken cancellationToken) - => Combiner.Combine(await GetSourceProviders(sources, options, cancellationToken)); + public static async Task GetSourceProviderAsync(IEnumerable sources, Options options, CancellationToken cancellationToken) + => Combiner.Combine(await GetSourceProvidersAsync(sources, options, cancellationToken)); /// /// Gets a snapshot service for the given sources @@ -135,7 +135,7 @@ namespace Duplicati.Library.Main.Operation /// The filter to use /// The last fileset id /// The source providers - private static async Task> GetSourceProviders(IEnumerable sources, Options options, CancellationToken cancellationToken) + private static async Task> GetSourceProvidersAsync(IEnumerable sources, Options options, CancellationToken cancellationToken) { // Group the sources by their type, so we can combine all snapshot paths into a single snapshot var sourceTypes = sources.GroupBy(x => x.StartsWith("@") ? "@" : Library.Utility.Utility.GuessScheme(x) ?? "file", StringComparer.OrdinalIgnoreCase); @@ -233,7 +233,7 @@ namespace Duplicati.Library.Main.Operation // all of the local database classes handle this), as the constructor // is not async and shouldn't be executing "heavy" code, such as // database queries. - var journalData = m_database.GetChangeJournalData(lastfilesetid, m_taskReader.ProgressToken).ToBlockingEnumerable(); + var journalData = m_database.GetChangeJournalDataAsync(lastfilesetid, m_taskReader.ProgressToken).ToBlockingEnumerable(); var service = new UsnJournalService(fileProvider.SnapshotService, filter, m_options.FileAttributeFilter, m_options.SkipFilesLargerThan, journalData, cancellationTokenSource.Token); @@ -305,7 +305,7 @@ namespace Duplicati.Library.Main.Operation /// and returned to the caller in an open state in either case. /// /// Results from the pre-backup verification - private static async Task PreBackupVerify(Options options, BackupResults result, IBackendManager backendManager) + private static async Task PreBackupVerifyAsync(Options options, BackupResults result, IBackendManager backendManager) { result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PreBackupVerify); @@ -321,7 +321,7 @@ namespace Duplicati.Library.Main.Operation { database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); - var lastFilestTime = await database.FilesetTimes(result.TaskControl.ProgressToken) + var lastFilestTime = await database.FilesetTimesAsync(result.TaskControl.ProgressToken) .Select(x => x.Value) .Append(DateTime.UnixEpoch) .Select(Library.Utility.Utility.NormalizeDateTimeToEpochSeconds) @@ -337,7 +337,7 @@ namespace Duplicati.Library.Main.Operation var sleepSeconds = lastFilestTime - currentStamp + 1; Log.WriteVerboseMessage(LOGTAG, "DatabaseTimestampError", "The database has a timestamp of {0}, but the last fileset has a timestamp of {1}. Sleeping for {2} seconds to allow the clock to catch up.", database.OperationTimestamp.ToLocalTime(), Library.Utility.Utility.EPOCH.AddSeconds(lastFilestTime).ToLocalTime(), sleepSeconds); Thread.Sleep(TimeSpan.FromSeconds(sleepSeconds)); - database.Dispose(); + await database.DisposeAsync(); database = await LocalBackupDatabase.CreateAsync(options.Dbpath, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -345,46 +345,46 @@ namespace Duplicati.Library.Main.Operation result.Dryrun = options.Dryrun; // Check the database integrity - await Utility.UpdateOptionsFromDb(database, options, result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(database, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(database, options, result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(database, options, result.TaskControl.ProgressToken) .ConfigureAwait(false); var probe_path = await database - .GetFirstPath(result.TaskControl.ProgressToken) + .GetFirstPathAsync(result.TaskControl.ProgressToken) .ConfigureAwait(false); if (probe_path != null && Util.GuessDirSeparator(probe_path) != Util.DirectorySeparatorString) throw new UserInformationException(string.Format("The backup contains files that belong to another operating system. Proceeding with a backup would cause the database to contain paths from two different operation systems, which is not supported. To proceed without losing remote data, delete all filesets and make sure the --{0} option is set, then run the backup again to re-use the existing data on the remote store.", "no-auto-compact"), "CrossOsDatabaseReuseNotSupported"); - if (await database.PartiallyRecreated(result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await database.PartiallyRecreatedAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsPartiallyRecreated"); - if (await database.RepairInProgress(result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await database.RepairInProgressAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the backup process cannot continue. You may delete the local database and attempt to repair it again.", "DatabaseRepairInProgress"); // Make sure the database is sane await database - .VerifyConsistency(options.Blocksize, options.BlockhashSize, !options.DisableFilelistConsistencyChecks, result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(options.Blocksize, options.BlockhashSize, !options.DisableFilelistConsistencyChecks, result.TaskControl.ProgressToken) .ConfigureAwait(false); // Guard the last filelist if (!options.DisableSyntheticFilelist) lastTempFilelist = await database - .GetLastIncompleteFilesetVolume(result.TaskControl.ProgressToken) + .GetLastIncompleteFilesetVolumeAsync(result.TaskControl.ProgressToken) .ConfigureAwait(false); try { if (options.NoBackendverification) { - await FilelistProcessor.VerifyLocalList(backendManager, database, options.Dryrun, result.TaskControl.ProgressToken).ConfigureAwait(false); - await UpdateStorageStatsFromDatabase(result, database, options, backendManager, result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyLocalListAsync(backendManager, database, options.Dryrun, result.TaskControl.ProgressToken).ConfigureAwait(false); + await UpdateStorageStatsFromDatabaseAsync(result, database, options, backendManager, result.TaskControl.ProgressToken).ConfigureAwait(false); } else { - await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: false, verifyMode: FilelistProcessor.VerifyMode.VerifyAndClean, result.TaskControl.ProgressToken).ConfigureAwait(false); - var updatedLastTemp = string.IsNullOrWhiteSpace(lastTempFilelist.Name) ? default : await database.GetRemoteVolume(lastTempFilelist.Name, result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: false, verifyMode: FilelistProcessor.VerifyMode.VerifyAndClean, result.TaskControl.ProgressToken).ConfigureAwait(false); + var updatedLastTemp = string.IsNullOrWhiteSpace(lastTempFilelist.Name) ? default : await database.GetRemoteVolumeAsync(lastTempFilelist.Name, result.TaskControl.ProgressToken).ConfigureAwait(false); if (!string.IsNullOrWhiteSpace(updatedLastTemp.Name) && updatedLastTemp.State == RemoteVolumeState.Deleting) { // The last temporary filelist was emptied, and scheduled for deletion, so we need to delete it @@ -408,7 +408,7 @@ namespace Duplicati.Library.Main.Operation await backendManager.WaitForEmptyAsync(database, result.TaskControl.ProgressToken).ConfigureAwait(false); // In case the backend delete failed, manually set the state to deleted await database - .UpdateRemoteVolume(updatedLastTemp.Name, RemoteVolumeState.Deleted, updatedLastTemp.Size, updatedLastTemp.Hash, false, TimeSpan.FromHours(2), null, result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(updatedLastTemp.Name, RemoteVolumeState.Deleted, updatedLastTemp.Size, updatedLastTemp.Hash, false, TimeSpan.FromHours(2), null, result.TaskControl.ProgressToken) .ConfigureAwait(false); // The last temporary filelist is no more lastTempFilelist = default; @@ -421,7 +421,7 @@ namespace Duplicati.Library.Main.Operation { Log.WriteWarningMessage(LOGTAG, "BackendVerifyFailedAttemptingCleanup", ex, "Backend verification failed, attempting automatic cleanup"); result.RepairResults = new RepairResults(result); - database.Dispose(); + await database.DisposeAsync(); database = null; await new RepairHandler(options, (RepairResults)result.RepairResults) @@ -433,19 +433,19 @@ namespace Duplicati.Library.Main.Operation .ConfigureAwait(false); Log.WriteInformationMessage(LOGTAG, "BackendCleanupFinished", "Backend cleanup finished, retrying verification"); - await FilelistProcessor.VerifyRemoteList(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, options, database, result.BackendWriter, [lastTempFilelist.Name], [], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, result.TaskControl.ProgressToken).ConfigureAwait(false); } else throw; } // Return the updated version of the fileset, in case it was actually complete - return new PreBackupVerifyResult(database, await database.GetLastIncompleteFilesetVolume(result.TaskControl.ProgressToken).ConfigureAwait(false)); + return new PreBackupVerifyResult(database, await database.GetLastIncompleteFilesetVolumeAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)); } catch { backendManager?.Dispose(); - database?.Dispose(); + await (database?.DisposeAsync().AsTask() ?? Task.CompletedTask); throw; } } @@ -454,7 +454,7 @@ namespace Duplicati.Library.Main.Operation /// /// Performs the bulk of work by starting all relevant processes /// - private static async Task RunMainOperation(Backup.Channels channels, ISourceProvider source, UsnJournalService journalService, Backup.BackupDatabase database, IBackendManager backendManager, Backup.BackupStatsCollector stats, Options options, IFilter filter, BackupResults result, ITaskReader taskreader, long filesetid, long lastfilesetid) + private static async Task RunMainOperationAsync(Backup.Channels channels, ISourceProvider source, UsnJournalService journalService, Backup.BackupDatabase database, IBackendManager backendManager, Backup.BackupStatsCollector stats, Options options, IFilter filter, BackupResults result, ITaskReader taskreader, long filesetid, long lastfilesetid) { using (new Logging.Timer(LOGTAG, "BackupMainOperation", "BackupMainOperation")) { @@ -464,33 +464,33 @@ namespace Duplicati.Library.Main.Operation Task all = Task.WhenAll( new[] { - Backup.DataBlockProcessor.Run(channels, database, backendManager, options, taskreader), - Backup.FileBlockProcessor.Run(channels, options, database, stats, taskreader), - Backup.StreamBlockSplitter.Run(channels, options, database, taskreader), - Backup.FileEnumerationProcess.Run(channels, source, journalService, + Backup.DataBlockProcessor.RunAsync(channels, database, backendManager, options, taskreader), + Backup.FileBlockProcessor.RunAsync(channels, options, database, stats, taskreader), + Backup.StreamBlockSplitter.RunAsync(channels, options, database, taskreader), + Backup.FileEnumerationProcess.RunAsync(channels, source, journalService, options.FileAttributeFilter, filter, options.SymlinkPolicy, options.HardlinkPolicy, options.DisableBackupExclusionXattr, options.ExcludeEmptyFolders, options.IgnoreFilenames, GetBlacklistedPaths(options), options.ChangedFilelist, taskreader, () => result.PartialBackup = true, CancellationToken.None), - Backup.FilePreFilterProcess.Run(channels, options, stats, database, taskreader), - Backup.MetadataPreProcess.Run(channels, options, database, lastfilesetid, taskreader), - Backup.SpillCollectorProcess.Run(channels, options, database, backendManager, taskreader), - Backup.ProgressHandler.Run(channels, result) + Backup.FilePreFilterProcess.RunAsync(channels, options, stats, database, taskreader), + Backup.MetadataPreProcess.RunAsync(channels, options, database, lastfilesetid, taskreader), + Backup.SpillCollectorProcess.RunAsync(channels, options, database, backendManager, taskreader), + Backup.ProgressHandler.RunAsync(channels, result) } // Spawn additional block hashers .Concat( Enumerable.Range(0, options.ConcurrencyBlockHashers - 1).Select(x => - Backup.StreamBlockSplitter.Run(channels, options, database, taskreader)) + Backup.StreamBlockSplitter.RunAsync(channels, options, database, taskreader)) ) // Spawn additional compressors .Concat( Enumerable.Range(0, options.ConcurrencyCompressors - 1).Select(x => - Backup.DataBlockProcessor.Run(channels, database, backendManager, options, taskreader)) + Backup.DataBlockProcessor.RunAsync(channels, database, backendManager, options, taskreader)) ) // Spawn additional file processors .Concat( Enumerable.Range(0, options.ConcurrencyFileprocessors - 1).Select(x => - Backup.FileBlockProcessor.Run(channels, options, database, stats, taskreader) + Backup.FileBlockProcessor.RunAsync(channels, options, database, stats, taskreader) ) ) ); @@ -519,7 +519,7 @@ namespace Duplicati.Library.Main.Operation return true; if (fileSize >= 0) - stats.AddExaminedFile(fileSize); + stats.AddExaminedFileAsync(fileSize).FireAndForget(); return false; }, taskreader.ProgressToken) @@ -559,7 +559,7 @@ namespace Duplicati.Library.Main.Operation } } - private async Task CompactIfRequired(IBackendManager backendManager, long lastVolumeSize) + private async Task CompactIfRequiredAsync(IBackendManager backendManager, long lastVolumeSize) { var currentIsSmall = lastVolumeSize != -1 && lastVolumeSize <= m_options.SmallFileSize; @@ -610,11 +610,11 @@ namespace Duplicati.Library.Main.Operation .ConfigureAwait(false); } - private async Task PostBackupVerification(string currentFilelistVolume, string previousTemporaryFilelist, IBackendManager backendManager) + private async Task PostBackupVerificationAsync(string currentFilelistVolume, string previousTemporaryFilelist, IBackendManager backendManager) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_PostBackupVerify); using (new Logging.Timer(LOGTAG, "AfterBackupVerify", "AfterBackupVerify")) - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, m_database, m_result.BackendWriter, [currentFilelistVolume], [previousTemporaryFilelist], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, m_database, m_result.BackendWriter, [currentFilelistVolume], [previousTemporaryFilelist], logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await backendManager .WaitForEmptyAsync(m_database, m_taskReader.ProgressToken) @@ -622,7 +622,7 @@ namespace Duplicati.Library.Main.Operation // Calculate the number of samples to test, using the largest number of file of a given type var remoteVolumeCount = await m_database - .GetRemoteVolumes(m_result.TaskControl.ProgressToken) + .GetRemoteVolumesAsync(m_result.TaskControl.ProgressToken) .Where(x => x.State == RemoteVolumeState.Verified) .GroupBy(x => x.Type) .Select(x => x.LongCount()) @@ -646,22 +646,22 @@ namespace Duplicati.Library.Main.Operation /// /// Handler for computing backend statistics, without relying on a remote folder listing /// - private static async Task UpdateStorageStatsFromDatabase(BackupResults result, LocalBackupDatabase database, Options options, IBackendManager backendManager, CancellationToken cancelToken) + private static async Task UpdateStorageStatsFromDatabaseAsync(BackupResults result, LocalBackupDatabase database, Options options, IBackendManager backendManager, CancellationToken cancelToken) { if (result.BackendWriter != null) { result.BackendWriter.KnownFileCount = await database - .GetRemoteVolumes(cancelToken) + .GetRemoteVolumesAsync(cancelToken) .CountAsync(cancelToken) .ConfigureAwait(false); result.BackendWriter.KnownFilesets = await database - .GetRemoteVolumes(cancelToken) + .GetRemoteVolumesAsync(cancelToken) .CountAsync(x => x.Type == RemoteVolumeType.Files) .ConfigureAwait(false); result.BackendWriter.KnownFileSize = await database - .GetRemoteVolumes(cancelToken) + .GetRemoteVolumesAsync(cancelToken) .Select(x => Math.Max(0, x.Size)) .SumAsync(cancelToken) .ConfigureAwait(false); @@ -670,13 +670,13 @@ namespace Duplicati.Library.Main.Operation result.BackendWriter.UnknownFileSize = 0; result.BackendWriter.BackupListCount = await database - .FilesetTimes(cancelToken) + .FilesetTimesAsync(cancelToken) .CountAsync(cancelToken) .ConfigureAwait(false); result.BackendWriter.LastBackupDate = ( await database - .FilesetTimes(cancelToken) + .FilesetTimesAsync(cancelToken) .FirstOrDefaultAsync(cancelToken) .ConfigureAwait(false) ) @@ -710,7 +710,7 @@ namespace Duplicati.Library.Main.Operation if (t != null) { if (!t.IsCompleted && !t.IsFaulted && !t.IsCanceled) - t.Wait(500); + t.WaitAsync(TimeSpan.FromMilliseconds(500)).Await(); if (t.IsFaulted && t.Exception != null) ex.Add(t.Exception); @@ -722,7 +722,7 @@ namespace Duplicati.Library.Main.Operation return new AggregateException(ex.First().Message, ex); } - private static async Task FlushBackend(Backup.BackupDatabase database, BackupResults result, IBackendManager backendManager) + private static async Task FlushBackendAsync(Backup.BackupDatabase database, BackupResults result, IBackendManager backendManager) { // Wait for upload completion result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_WaitForUpload); @@ -743,7 +743,7 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_Begin); // Do a remote verification, unless disabled - var (database, lastTempFilelist) = await PreBackupVerify(m_options, m_result, backendManager) + var (database, lastTempFilelist) = await PreBackupVerifyAsync(m_options, m_result, backendManager) .ConfigureAwait(false); var lastTempVolumeIncomplete = false; @@ -765,7 +765,7 @@ namespace Duplicati.Library.Main.Operation // If we crash now, there is a chance we leave behind partial remote files if (!m_options.Dryrun) await database - .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, true) + .TerminatedWithActiveUploadsAsync(m_result.TaskControl.ProgressToken, true) .ConfigureAwait(false); DateTime? syntheticFilesetTimestamp = null; @@ -776,17 +776,17 @@ namespace Duplicati.Library.Main.Operation { long filesetid; using var counterToken = CancellationTokenSource.CreateLinkedTokenSource(m_taskReader.ProgressToken); - using (var source = await GetSourceProvider(sources, m_options, m_taskReader.ProgressToken).ConfigureAwait(false)) + using (var source = await GetSourceProviderAsync(sources, m_options, m_taskReader.ProgressToken).ConfigureAwait(false)) { try { // If the previous backup was interrupted, send a synthetic list - syntheticFilesetTimestamp = await Backup.UploadSyntheticFilelist.Run(db, m_options, m_result, m_result.TaskControl, backendManager, lastTempFilelist) + syntheticFilesetTimestamp = await Backup.UploadSyntheticFilelist.RunAsync(db, m_options, m_result, m_result.TaskControl, backendManager, lastTempFilelist) .ConfigureAwait(false); // Grab the previous backup ID, if any var prevfileset = await m_database - .FilesetTimes(m_result.TaskControl.ProgressToken) + .FilesetTimesAsync(m_result.TaskControl.ProgressToken) .FirstOrDefaultAsync() .ConfigureAwait(false); @@ -796,7 +796,7 @@ namespace Duplicati.Library.Main.Operation var lastfilesetid = prevfileset.Value.Ticks == 0 ? -1 : prevfileset.Key; // Rebuild any index files that are missing - await Backup.RecreateMissingIndexFiles.Run(db, backendManager, m_options, m_result.TaskControl) + await Backup.RecreateMissingIndexFiles.RunAsync(db, backendManager, m_options, m_result.TaskControl) .ConfigureAwait(false); // Prepare the operation by registering the filelist @@ -823,27 +823,27 @@ namespace Duplicati.Library.Main.Operation if (m_options.DisableFileScanner) { var d = await m_database - .GetLastBackupFileCountAndSize(m_result.TaskControl.ProgressToken) + .GetLastBackupFileCountAndSizeAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.OperationProgressUpdater.UpdatefileCount(d.Item1, d.Item2, true); } else { - parallelScanner = Backup.CountFilesHandler.Run(source, journalService, m_result, m_options, m_filter, GetBlacklistedPaths(), m_result.TaskControl, counterToken.Token); + parallelScanner = Backup.CountFilesHandler.RunAsync(source, journalService, m_result, m_options, m_filter, GetBlacklistedPaths(), m_result.TaskControl, counterToken.Token); } // Run the backup operation - if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { var stats = new Backup.BackupStatsCollector(m_result); - await RunMainOperation(channels, source, journalService, db, backendManager, stats, m_options, m_filter, m_result, m_result.TaskControl, filesetid, lastfilesetid).ConfigureAwait(false); + await RunMainOperationAsync(channels, source, journalService, db, backendManager, stats, m_options, m_filter, m_result, m_result.TaskControl, filesetid, lastfilesetid).ConfigureAwait(false); } } finally { //If the scanner is still running for some reason, make sure we kill it now - counterToken.Cancel(); + await counterToken.CancelAsync(); } try @@ -867,21 +867,21 @@ namespace Duplicati.Library.Main.Operation .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, false, m_taskReader.ProgressToken) .ConfigureAwait(false); - await FlushBackend(db, m_result, backendManager) + await FlushBackendAsync(db, m_result, backendManager) .ConfigureAwait(false); // Send the actual filelist - var uploadedNewFileset = await Backup.UploadRealFilelist.Run(m_result, db, backendManager, m_options, filesetvolume, filesetid, m_result.TaskControl, lastTempVolumeIncomplete) + var uploadedNewFileset = await Backup.UploadRealFilelist.RunAsync(m_result, db, backendManager, m_options, filesetvolume, filesetid, m_result.TaskControl, lastTempVolumeIncomplete) .ConfigureAwait(false); // Wait for upload completion m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_WaitForUpload); - await FlushBackend(db, m_result, backendManager) + await FlushBackendAsync(db, m_result, backendManager) .ConfigureAwait(false); if (!m_options.Dryrun) await database - .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, false) + .TerminatedWithActiveUploadsAsync(m_result.TaskControl.ProgressToken, false) .ConfigureAwait(false); // Make sure we have the database up-to-date @@ -896,7 +896,7 @@ namespace Duplicati.Library.Main.Operation // Invoke the after-backup callback, after the backup is complete // and sources are disposed, but before verification - if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false) && m_options.LoadedModules != null) + if (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false) && m_options.LoadedModules != null) { foreach (var mx in m_options.LoadedModules) if (mx is IBackupCallbackModule module) @@ -906,7 +906,7 @@ namespace Duplicati.Library.Main.Operation // If this throws, we should roll back the transaction - if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { var versionsToLock = new List(); if (syntheticFilesetTimestamp.HasValue) @@ -920,20 +920,20 @@ namespace Duplicati.Library.Main.Operation } // If this throws, we should roll back the transaction - if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { var lastVolumeSize = await m_database - .GetLastWrittenDBlockVolumeSize(m_result.TaskControl.ProgressToken) + .GetLastWrittenDBlockVolumeSizeAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await CompactIfRequired(backendManager, lastVolumeSize) + await CompactIfRequiredAsync(backendManager, lastVolumeSize) .ConfigureAwait(false); } - if (m_options.UploadVerificationFile && await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (m_options.UploadVerificationFile && await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Backup_VerificationUpload); - await FilelistProcessor.UploadVerificationFile(backendManager, m_options, m_database, m_result.TaskControl.ProgressToken) + await FilelistProcessor.UploadVerificationFileAsync(backendManager, m_options, m_database, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -943,13 +943,13 @@ namespace Duplicati.Library.Main.Operation .CommitAsync("CommitFinalizingBackup") .ConfigureAwait(false); - if (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { if (m_options.NoBackendverification) - await UpdateStorageStatsFromDatabase(m_result, m_database, m_options, backendManager, m_taskReader.ProgressToken) + await UpdateStorageStatsFromDatabaseAsync(m_result, m_database, m_options, backendManager, m_taskReader.ProgressToken) .ConfigureAwait(false); else - await PostBackupVerification(filesetvolume.RemoteFilename, lastTempFilelist.Name, backendManager) + await PostBackupVerificationAsync(filesetvolume.RemoteFilename, lastTempFilelist.Name, backendManager) .ConfigureAwait(false); } await database.Transaction @@ -973,7 +973,7 @@ namespace Duplicati.Library.Main.Operation finally { if (parallelScanner != null && !parallelScanner.IsCompleted) - parallelScanner.Wait(500); + await parallelScanner.WaitAsync(TimeSpan.FromMilliseconds(500)); } } diff --git a/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs b/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs index 1ddf4f9bb..a8e11ef9a 100644 --- a/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs +++ b/Duplicati/Library/Main/Operation/Common/DatabaseCommon.cs @@ -51,18 +51,18 @@ namespace Duplicati.Library.Main.Operation.Common public Task RegisterRemoteVolumeAsync(string name, RemoteVolumeType type, RemoteVolumeState state, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .RegisterRemoteVolume(name, type, state, cancellationToken) + .RegisterRemoteVolumeAsync(name, type, state, cancellationToken) .ConfigureAwait(false) ); } public Task UpdateRemoteVolumeAsync(string name, RemoteVolumeState state, long size, string hash, bool suppressCleanup, TimeSpan deleteGraceTime, bool? setArchived, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .UpdateRemoteVolume(name, state, size, hash, suppressCleanup, deleteGraceTime, setArchived, cancellationToken) + .UpdateRemoteVolumeAsync(name, state, size, hash, suppressCleanup, deleteGraceTime, setArchived, cancellationToken) .ConfigureAwait(false) ); } @@ -74,11 +74,11 @@ namespace Duplicati.Library.Main.Operation.Common } private Task FlushPendingBackendMessagesAsync(IBackendManager backendManager, CancellationToken cancellationToken) - => RunOnMain(async () => await backendManager.FlushPendingMessagesAsync(m_db, cancellationToken).ConfigureAwait(false)); + => RunOnMainAsync(async () => await backendManager.FlushPendingMessagesAsync(m_db, cancellationToken).ConfigureAwait(false)); public Task CommitTransactionAsync(string message, bool restart, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => { if (m_options.Dryrun) { @@ -98,7 +98,7 @@ namespace Duplicati.Library.Main.Operation.Common public Task RollbackTransactionAsync(CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db.Transaction .RollBackAsync(cancellationToken) .ConfigureAwait(false) @@ -107,18 +107,18 @@ namespace Duplicati.Library.Main.Operation.Common public Task RenameRemoteFileAsync(string oldname, string newname, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .RenameRemoteFile(oldname, newname, cancellationToken) + .RenameRemoteFileAsync(oldname, newname, cancellationToken) .ConfigureAwait(false) ); } public Task LogRemoteOperationAsync(string operation, string path, string data, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .LogRemoteOperation(operation, path, data, cancellationToken) + .LogRemoteOperationAsync(operation, path, data, cancellationToken) .ConfigureAwait(false) ); } @@ -127,9 +127,9 @@ namespace Duplicati.Library.Main.Operation.Common { // TODO: Figure out how to return the enumerable, while keeping the lock // and not creating the entire result in memory - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .GetBlocks(volumeid, cancellationToken) + .GetBlocksAsync(volumeid, cancellationToken) .ToArrayAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); @@ -137,9 +137,9 @@ namespace Duplicati.Library.Main.Operation.Common public Task GetVolumeInfoAsync(string remotename, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .GetRemoteVolume(remotename, cancellationToken) + .GetRemoteVolumeAsync(remotename, cancellationToken) .ConfigureAwait(false) ); } @@ -148,9 +148,9 @@ namespace Duplicati.Library.Main.Operation.Common { // TODO: Figure out how to return the enumerable, while keeping the lock // and not creating the entire result in memory - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .GetBlocklists(volumeid, blocksize, hashsize, cancellationToken) + .GetBlocklistsAsync(volumeid, blocksize, hashsize, cancellationToken) .ToArrayAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); @@ -158,18 +158,18 @@ namespace Duplicati.Library.Main.Operation.Common public Task GetRemoteVolumeIDAsync(string remotename, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .GetRemoteVolumeID(remotename, cancellationToken) + .GetRemoteVolumeIDAsync(remotename, cancellationToken) .ConfigureAwait(false) ); } public Task AddIndexBlockLinkAsync(long indexVolumeID, long blockVolumeID, CancellationToken cancellationToken) { - return RunOnMain(async () => + return RunOnMainAsync(async () => await m_db - .AddIndexBlockLink(indexVolumeID, blockVolumeID, cancellationToken) + .AddIndexBlockLinkAsync(indexVolumeID, blockVolumeID, cancellationToken) .ConfigureAwait(false) ); } diff --git a/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs b/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs index ae8827bf2..bc7618a13 100644 --- a/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs +++ b/Duplicati/Library/Main/Operation/Common/IndexVolumeCreator.cs @@ -73,7 +73,7 @@ namespace Duplicati.Library.Main.Operation.Common /// The database to use. /// The cancellation token to use. /// The index volume. - public async Task CreateVolume(string blockfilename, Options options, DatabaseCommon database, CancellationToken cancellationToken) + public async Task CreateVolumeAsync(string blockfilename, Options options, DatabaseCommon database, CancellationToken cancellationToken) { var w = new IndexVolumeWriter(options); w.VolumeID = await database.RegisterRemoteVolumeAsync(w.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, cancellationToken).ConfigureAwait(false); @@ -160,7 +160,7 @@ namespace Duplicati.Library.Main.Operation.Common /// internal static class IndexVolumeCreator { - public static async Task CreateIndexVolume(string blockname, Options options, Common.DatabaseCommon database, CancellationToken cancellationToken) + public static async Task CreateIndexVolumeAsync(string blockname, Options options, Common.DatabaseCommon database, CancellationToken cancellationToken) { using (var h = HashFactory.CreateHasher(options.BlockHashAlgorithm)) { diff --git a/Duplicati/Library/Main/Operation/Common/SingleRunner.cs b/Duplicati/Library/Main/Operation/Common/SingleRunner.cs index 0d490a7a7..14d704a29 100644 --- a/Duplicati/Library/Main/Operation/Common/SingleRunner.cs +++ b/Duplicati/Library/Main/Operation/Common/SingleRunner.cs @@ -1,22 +1,22 @@ -// Copyright (C) 2026, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// Copyright (C) 2026, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; @@ -35,7 +35,7 @@ namespace Duplicati.Library.Main.Operation.Common protected readonly AsyncLock m_lock = new AsyncLock(); protected readonly CancellationTokenSource m_workerSource = new CancellationTokenSource(); - protected async Task DoRunOnMain(Func> method) + protected async Task DoRunOnMainAsync(Func> method) { m_workerSource.Token.ThrowIfCancellationRequested(); @@ -46,34 +46,35 @@ namespace Duplicati.Library.Main.Operation.Common } } - protected Task RunOnMain(Action method) + protected Task RunOnMainAsync(Action method) { - return DoRunOnMain(() => + return DoRunOnMainAsync(() => { method(); return Task.FromResult(true); }); } - protected Task RunOnMain(Func method) + protected Task RunOnMainAsync(Func method) { - return DoRunOnMain(() => + return DoRunOnMainAsync(() => { return Task.FromResult(method()); }); } - protected Task RunOnMain(Func method) + protected Task RunOnMainAsync(Func method) { - return DoRunOnMain(async () => { + return DoRunOnMainAsync(async () => + { await method().ConfigureAwait(false); return true; }); } - protected Task RunOnMain(Func> method) + protected Task RunOnMainAsync(Func> method) { - return DoRunOnMain(method); + return DoRunOnMainAsync(method); } public void Dispose() diff --git a/Duplicati/Library/Main/Operation/Common/TaskControl.cs b/Duplicati/Library/Main/Operation/Common/TaskControl.cs index bf624cf1e..76693aaf0 100644 --- a/Duplicati/Library/Main/Operation/Common/TaskControl.cs +++ b/Duplicati/Library/Main/Operation/Common/TaskControl.cs @@ -39,7 +39,7 @@ namespace Duplicati.Library.Main.Operation.Common /// Gets the progress state, waiting if the state is paused, throws if terminated /// /// true if the progress should continue, false if it should stop - Task ProgressRendevouz(); + Task ProgressRendevouzAsync(); /// /// A cancellation token that can be used to monitor transfer abort @@ -50,7 +50,7 @@ namespace Duplicati.Library.Main.Operation.Common /// Gets the transfer state, waiting if the state is paused, throws if terminated /// /// true if the transfer should continue, false if it should stop - Task TransferRendevouz(); + Task TransferRendevouzAsync(); /// /// A cancellation token that can be used to monitor stop requests @@ -194,7 +194,7 @@ namespace Duplicati.Library.Main.Operation.Common /// Gets the progress state, waiting if the state is paused /// /// true if the progress should continue, false if it should stop - public async Task ProgressRendevouz() + public async Task ProgressRendevouzAsync() { var res = await m_progress.Task.ConfigureAwait(false); lock (m_lock) @@ -206,7 +206,7 @@ namespace Duplicati.Library.Main.Operation.Common /// Gets the transfer state, waiting if the state is paused /// /// true if the transfer should continue, false if it should stop - public async Task TransferRendevouz() + public async Task TransferRendevouzAsync() { var res = await m_transfer.Task.ConfigureAwait(false); lock (m_lock) diff --git a/Duplicati/Library/Main/Operation/CompactHandler.cs b/Duplicati/Library/Main/Operation/CompactHandler.cs index 506d3e86d..d24575663 100644 --- a/Duplicati/Library/Main/Operation/CompactHandler.cs +++ b/Duplicati/Library/Main/Operation/CompactHandler.cs @@ -56,15 +56,15 @@ namespace Duplicati.Library.Main.Operation await using (var db = await LocalDeleteDatabase.CreateAsync(m_options.Dbpath, "Compact", null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var changed = await DoCompactAsync(db, false, backendManager).ConfigureAwait(false); if (changed && m_options.UploadVerificationFile) - await FilelistProcessor.UploadVerificationFile(backendManager, m_options, db, m_result.TaskControl.ProgressToken); + await FilelistProcessor.UploadVerificationFileAsync(backendManager, m_options, db, m_result.TaskControl.ProgressToken); if (!m_options.Dryrun) { @@ -74,7 +74,7 @@ namespace Duplicati.Library.Main.Operation if (changed) { - await db.WriteResultsAndCommit(m_result, m_result.TaskControl.ProgressToken) + await db.WriteResultsAndCommitAsync(m_result, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (m_options.AutoVacuum) { @@ -89,7 +89,7 @@ namespace Duplicati.Library.Main.Operation internal async Task DoCompactAsync(LocalDeleteDatabase db, bool hasVerifiedBackend, IBackendManager backendManager) { var report = await db - .GetCompactReport(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount, m_result.TaskControl.ProgressToken) + .GetCompactReportAsync(m_options.VolumeSize, m_options.Threshold, m_options.SmallFileSize, m_options.SmallFileMaxCount, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); report.ReportCompactData(); @@ -98,11 +98,11 @@ namespace Duplicati.Library.Main.Operation { // Workaround where we allow a running backendmanager to be used if (!hasVerifiedBackend) - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, true, FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, db, m_result.BackendWriter, true, FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); var newvol = new BlockVolumeWriter(m_options); newvol.VolumeID = await db - .RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) + .RegisterRemoteVolumeAsync(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); IndexVolumeWriter newvolindex = null; @@ -110,18 +110,18 @@ namespace Duplicati.Library.Main.Operation { newvolindex = new IndexVolumeWriter(m_options); newvolindex.VolumeID = await db - .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) + .RegisterRemoteVolumeAsync(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, m_result.TaskControl.ProgressToken) + .AddIndexBlockLinkAsync(newvolindex.VolumeID, newvol.VolumeID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } var blocksInVolume = 0L; var buffer = new byte[m_options.Blocksize]; var remoteList = await db - .GetRemoteVolumes(m_result.TaskControl.ProgressToken) + .GetRemoteVolumesAsync(m_result.TaskControl.ProgressToken) .Where(n => n.State == RemoteVolumeState.Uploaded || n.State == RemoteVolumeState.Verified) .ToArrayAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -152,7 +152,7 @@ namespace Duplicati.Library.Main.Operation .Cast() .ToList(); } - await foreach (var d in DoDelete(db, backendManager, fullyDeleteable, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var d in DoDeleteAsync(db, backendManager, fullyDeleteable, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) deletedVolumes.Add(d); // This list is used to pick up unused volumes, @@ -165,7 +165,7 @@ namespace Duplicati.Library.Main.Operation // If we crash now, we may leave partial files if (!m_options.Dryrun) await db - .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, true) + .TerminatedWithActiveUploadsAsync(m_result.TaskControl.ProgressToken, true) .ConfigureAwait(false); newvolindex?.StartVolume(newvol.RemoteFilename); @@ -191,14 +191,14 @@ namespace Duplicati.Library.Main.Operation .ToList(); } - using (var q = await db.CreateBlockQueryHelper(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + using (var q = await db.CreateBlockQueryHelperAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { await foreach (var (tmpfile, hash, size, name) in backendManager.GetFilesOverlappedAsync(volumesToDownload, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { using (tmpfile) { var entry = new RemoteVolume(name, hash, size); - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); return false; @@ -206,7 +206,7 @@ namespace Duplicati.Library.Main.Operation downloadedVolumes.Add(new KeyValuePair(entry.Name, entry.Size)); var volumeid = await db - .GetRemoteVolumeID(entry.Name, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeIDAsync(entry.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var inst = VolumeBase.ParseFilename(entry.Name); @@ -214,7 +214,7 @@ namespace Duplicati.Library.Main.Operation { foreach (var e in f.Blocks) { - if (await q.UseBlock(e.Key, e.Value, volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await q.UseBlockAsync(e.Key, e.Value, volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { //TODO: How do we get the compression hint? Reverse query for filename in db? var s = f.ReadBlock(e.Key, buffer); @@ -222,37 +222,37 @@ namespace Duplicati.Library.Main.Operation throw new Exception(string.Format("Size mismatch problem for block {0}, {1} vs {2}", e.Key, s, e.Value)); await newvol - .AddBlock(e.Key, buffer, 0, s, Interface.CompressionHint.Compressible) + .AddBlockAsync(e.Key, buffer, 0, s, Interface.CompressionHint.Compressible) .ConfigureAwait(false); if (newvolindex != null) newvolindex.AddBlock(e.Key, e.Value); await db - .RegisterDuplicatedBlock(e.Key, e.Value, newvol.VolumeID, m_result.TaskControl.ProgressToken) + .RegisterDuplicatedBlockAsync(e.Key, e.Value, newvol.VolumeID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); blocksInVolume++; if (newvol.Filesize > (m_options.VolumeSize - m_options.Blocksize)) { - await FinishVolumeAndUpload(db, backendManager, newvol, newvolindex, uploadedVolumes) + await FinishVolumeAndUploadAsync(db, backendManager, newvol, newvolindex, uploadedVolumes) .ConfigureAwait(false); newvol = new BlockVolumeWriter(m_options); newvol.VolumeID = await db - .RegisterRemoteVolume(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) + .RegisterRemoteVolumeAsync(newvol.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (m_options.IndexfilePolicy != Options.IndexFileStrategy.None) { newvolindex = new IndexVolumeWriter(m_options); newvolindex.VolumeID = await db - .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) + .RegisterRemoteVolumeAsync(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .AddIndexBlockLink(newvolindex.VolumeID, newvol.VolumeID, m_result.TaskControl.ProgressToken) + .AddIndexBlockLinkAsync(newvolindex.VolumeID, newvol.VolumeID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); newvolindex.StartVolume(newvol.RemoteFilename); @@ -272,7 +272,7 @@ namespace Duplicati.Library.Main.Operation if (deleteableVolumes.Any()) { // Preserve space by deleting the old volume - await foreach (var d in DoDelete(db, backendManager, deleteableVolumes, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var d in DoDeleteAsync(db, backendManager, deleteableVolumes, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) deletedVolumes.Add(d); deleteableVolumes.Clear(); } @@ -287,18 +287,18 @@ namespace Duplicati.Library.Main.Operation if (blocksInVolume > 0) { - await FinishVolumeAndUpload(db, backendManager, newvol, newvolindex, uploadedVolumes).ConfigureAwait(false); + await FinishVolumeAndUploadAsync(db, backendManager, newvol, newvolindex, uploadedVolumes).ConfigureAwait(false); } else { await db - .RemoveRemoteVolume(newvol.RemoteFilename, m_result.TaskControl.ProgressToken) + .RemoveRemoteVolumeAsync(newvol.RemoteFilename, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (newvolindex != null) { await db - .RemoveRemoteVolume(newvolindex.RemoteFilename, m_result.TaskControl.ProgressToken) + .RemoveRemoteVolumeAsync(newvolindex.RemoteFilename, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); newvolindex.FinishVolume(null, 0); @@ -309,7 +309,7 @@ namespace Duplicati.Library.Main.Operation // The remainder of the operation cannot leave partial files if (!m_options.Dryrun) await db - .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, false) + .TerminatedWithActiveUploadsAsync(m_result.TaskControl.ProgressToken, false) .ConfigureAwait(false); } else @@ -318,7 +318,7 @@ namespace Duplicati.Library.Main.Operation newvol.Dispose(); } - await foreach (var d in DoDelete(db, backendManager, deleteableVolumes, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var d in DoDeleteAsync(db, backendManager, deleteableVolumes, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) deletedVolumes.Add(d); var downloadSize = downloadedVolumes.Where(x => x.Value >= 0).Aggregate(0L, (a, x) => a + x.Value); @@ -375,17 +375,17 @@ namespace Duplicati.Library.Main.Operation } } - private async IAsyncEnumerable> DoDelete(LocalDeleteDatabase db, IBackendManager backend, IEnumerable deleteableVolumes, [EnumeratorCancellation] CancellationToken cancellationToken) + private async IAsyncEnumerable> DoDeleteAsync(LocalDeleteDatabase db, IBackendManager backend, IEnumerable deleteableVolumes, [EnumeratorCancellation] CancellationToken cancellationToken) { // Find volumes that can be deleted var remoteFilesToRemove = await db - .ReOrderDeleteableVolumes(deleteableVolumes, cancellationToken) + .ReOrderDeleteableVolumesAsync(deleteableVolumes, cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); // Make sure we do not re-assign blocks to any of the volumes we are about to delete var toRemoveVolumeIds = await db - .GetRemoteVolumeIDs(remoteFilesToRemove.Select(x => x.Name), cancellationToken) + .GetRemoteVolumeIDsAsync(remoteFilesToRemove.Select(x => x.Name), cancellationToken) .Select(x => x.Value) .Distinct() .ToListAsync(cancellationToken: cancellationToken) @@ -395,11 +395,11 @@ namespace Duplicati.Library.Main.Operation foreach (var f in remoteFilesToRemove) { await db - .PrepareForDelete(f.Name, toRemoveVolumeIds, cancellationToken) + .PrepareForDeleteAsync(f.Name, toRemoveVolumeIds, cancellationToken) .ConfigureAwait(false); await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, cancellationToken) + .UpdateRemoteVolumeAsync(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, cancellationToken) .ConfigureAwait(false); } @@ -411,17 +411,17 @@ namespace Duplicati.Library.Main.Operation .CommitAsync("CommitDelete", token: cancellationToken) .ConfigureAwait(false); - await foreach (var d in PerformDelete(db, backend, remoteFilesToRemove, cancellationToken).ConfigureAwait(false)) + await foreach (var d in PerformDeleteAsync(db, backend, remoteFilesToRemove, cancellationToken).ConfigureAwait(false)) yield return d; } - private async Task FinishVolumeAndUpload(LocalDeleteDatabase db, IBackendManager backendManager, BlockVolumeWriter newvol, IndexVolumeWriter newvolindex, List> uploadedVolumes) + private async Task FinishVolumeAndUploadAsync(LocalDeleteDatabase db, IBackendManager backendManager, BlockVolumeWriter newvol, IndexVolumeWriter newvolindex, List> uploadedVolumes) { Func indexVolumeFinished = null; if (newvolindex != null && m_options.IndexfilePolicy == Options.IndexFileStrategy.Full) indexVolumeFinished = async () => { - await foreach (var blocklist in db.GetBlocklists(newvol.VolumeID, m_options.Blocksize, m_options.BlockhashSize, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var blocklist in db.GetBlocklistsAsync(newvol.VolumeID, m_options.Blocksize, m_options.BlockhashSize, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) newvolindex.WriteBlocklist(blocklist.Hash, blocklist.Buffer, 0, blocklist.Size); }; @@ -433,7 +433,7 @@ namespace Duplicati.Library.Main.Operation // because the transaction is not thread-safe, and shared with the upload await backendManager.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await db - .UpdateRemoteVolume(newvol.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(newvol.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // TODO: The upload here does not flush the database messages, @@ -452,11 +452,11 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteDryrunMessage(LOGTAG, "WouldUploadGeneratedBlockset", "Would upload generated blockset of size {0}", Library.Utility.Utility.FormatSizeString(newvol.Filesize)); } - private async IAsyncEnumerable> PerformDelete(LocalDeleteDatabase db, IBackendManager backendManager, IEnumerable list, [EnumeratorCancellation] CancellationToken cancelToken) + private async IAsyncEnumerable> PerformDeleteAsync(LocalDeleteDatabase db, IBackendManager backendManager, IEnumerable list, [EnumeratorCancellation] CancellationToken cancelToken) { foreach (var f in list) { - var remoteVolume = await db.GetRemoteVolume(f.Name, cancelToken).ConfigureAwait(false); + var remoteVolume = await db.GetRemoteVolumeAsync(f.Name, cancelToken).ConfigureAwait(false); if (HasActiveLock(remoteVolume.LockExpirationTime)) { Logging.Log.WriteWarningMessage(LOGTAG, "SkipDeleteLockedRemoteVolume", null, diff --git a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs index 2ff765038..56636db3f 100644 --- a/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs +++ b/Duplicati/Library/Main/Operation/CreateBugReportHandler.cs @@ -70,9 +70,9 @@ namespace Duplicati.Library.Main.Operation File.Copy(m_options.Dbpath, tmp, true); await using (var db = await LocalBugReportDatabase.CreateAsync(tmp, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await db.Fix(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.ObfuscateAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (m_options.AutoVacuum) - await db.Vacuum(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.VacuumAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); } using (var stream = new FileStream(m_targetpath, FileMode.Create, FileAccess.Write, FileShare.Read)) @@ -84,7 +84,7 @@ namespace Duplicati.Library.Main.Operation using (var cs = new StreamWriter(cm.CreateFile("system-info.txt", CompressionHint.Compressible, DateTime.UtcNow))) foreach (var line in SystemInfoHandler.GetSystemInfo()) - cs.WriteLine(line); + await cs.WriteLineAsync(line); } m_result.TargetPath = m_targetpath; diff --git a/Duplicati/Library/Main/Operation/DeleteHandler.cs b/Duplicati/Library/Main/Operation/DeleteHandler.cs index 9f00a56bb..f62ddac87 100644 --- a/Duplicati/Library/Main/Operation/DeleteHandler.cs +++ b/Duplicati/Library/Main/Operation/DeleteHandler.cs @@ -50,10 +50,10 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseFileMissing"); await using var db = await LocalDeleteDatabase.CreateAsync(m_options.Dbpath, "Delete", null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(db, false, false, backendManager).ConfigureAwait(false); @@ -67,12 +67,12 @@ namespace Duplicati.Library.Main.Operation public async Task DoRunAsync(LocalDeleteDatabase db, bool hasVerifiedBackend, bool forceCompact, IBackendManager backendManager) { if (!hasVerifiedBackend) - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); // We collapse the async enumerablo into a array to avoid multiple // enumerations (and thus multiple database queries) var filesets = await db - .FilesetsWithBackupVersion(m_result.TaskControl.ProgressToken) + .FilesetsWithBackupVersionAsync(m_result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -115,7 +115,7 @@ namespace Duplicati.Library.Main.Operation } // Skip deleting a fileset if the fileset volume itself is currently locked. - var filesetVolume = await db.GetRemoteVolumeFromFilesetID(filesetId, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + var filesetVolume = await db.GetRemoteVolumeFromFilesetIDAsync(filesetId, m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (HasActiveLock(filesetVolume.LockExpirationTime)) { Logging.Log.WriteWarningMessage( @@ -145,7 +145,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "DeleteRemoteFileset", "Deleting {0} remote fileset(s) ...", versionsToDelete.Count); var lst = await db - .DropFilesetsFromTable( + .DropFilesetsFromTableAsync( versionsToDelete .Select(x => x.Time) .ToArray(), @@ -156,7 +156,7 @@ namespace Duplicati.Library.Main.Operation foreach (var f in lst) await db - .UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(f.Key, RemoteVolumeState.Deleting, f.Value, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (!m_options.Dryrun) @@ -166,7 +166,7 @@ namespace Duplicati.Library.Main.Operation foreach (var f in lst) { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); return; diff --git a/Duplicati/Library/Main/Operation/FilelistProcessor.cs b/Duplicati/Library/Main/Operation/FilelistProcessor.cs index 96d3491a8..6a74d6474 100644 --- a/Duplicati/Library/Main/Operation/FilelistProcessor.cs +++ b/Duplicati/Library/Main/Operation/FilelistProcessor.cs @@ -70,9 +70,9 @@ namespace Duplicati.Library.Main.Operation /// If true, no changes will be made. /// The cancellation token to use. /// A task that completes when the verification is done. - public static async Task VerifyLocalList(IBackendManager backendManager, LocalDatabase database, bool dryrun, CancellationToken cancellationToken) + public static async Task VerifyLocalListAsync(IBackendManager backendManager, LocalDatabase database, bool dryrun, CancellationToken cancellationToken) { - var locallist = database.GetRemoteVolumes(cancellationToken); + var locallist = database.GetRemoteVolumesAsync(cancellationToken); await foreach (var i in locallist.ConfigureAwait(false)) { switch (i.State) @@ -125,7 +125,7 @@ namespace Duplicati.Library.Main.Operation /// The mode to use for verification. /// The cancellation token to use. /// An awaitable task. - public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter backendWriter, bool latestVolumesOnly, VerifyMode verifyMode, CancellationToken cancellationToken) + public static async Task VerifyRemoteListAsync(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter backendWriter, bool latestVolumesOnly, VerifyMode verifyMode, CancellationToken cancellationToken) { if (!options.NoBackendverification) { @@ -134,10 +134,10 @@ namespace Duplicati.Library.Main.Operation .ConfigureAwait(false); IEnumerable protectedFiles = await backupDatabase - .GetTemporaryFilelistVolumeNames(latestVolumesOnly, cancellationToken) + .GetTemporaryFilelistVolumeNamesAsync(latestVolumesOnly, cancellationToken) .ConfigureAwait(false); - await VerifyRemoteList(backend, options, database, backendWriter, protectedFiles, [], logErrors: true, verifyMode: verifyMode, cancellationToken).ConfigureAwait(false); + await VerifyRemoteListAsync(backend, options, database, backendWriter, protectedFiles, [], logErrors: true, verifyMode: verifyMode, cancellationToken).ConfigureAwait(false); } } @@ -155,9 +155,9 @@ namespace Duplicati.Library.Main.Operation /// The mode to use for verification. /// The cancellation token to use. /// An awaitable task. - public static async Task VerifyRemoteList(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, bool logErrors, VerifyMode verifyMode, CancellationToken cancellationToken) + public static async Task VerifyRemoteListAsync(IBackendManager backend, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, bool logErrors, VerifyMode verifyMode, CancellationToken cancellationToken) { - var tp = await RemoteListAnalysis(backend, options, database, log, protectedFiles, strictExcemptFiles, verifyMode, cancellationToken).ConfigureAwait(false); + var tp = await RemoteListAnalysisAsync(backend, options, database, log, protectedFiles, strictExcemptFiles, verifyMode, cancellationToken).ConfigureAwait(false); long extraCount = 0; long missingCount = 0; @@ -228,7 +228,7 @@ namespace Duplicati.Library.Main.Operation { var s = new Newtonsoft.Json.JsonSerializer(); s.Serialize(stream, - db.GetRemoteVolumes(cancellationToken) + db.GetRemoteVolumesAsync(cancellationToken) .Where(x => x.State != RemoteVolumeState.Temporary) .ToBlockingEnumerable() .Cast() @@ -244,7 +244,7 @@ namespace Duplicati.Library.Main.Operation /// The attached database. /// The cancellation token to use. /// A task that completes when the upload is done. - public static async Task UploadVerificationFile(IBackendManager backendManager, Options options, LocalDatabase db, CancellationToken cancellationToken) + public static async Task UploadVerificationFileAsync(IBackendManager backendManager, Options options, LocalDatabase db, CancellationToken cancellationToken) { using (var tempfile = new Library.Utility.TempFile()) { @@ -277,10 +277,10 @@ namespace Duplicati.Library.Main.Operation /// The mode to use for verification. /// The cancellation token to use. /// A task that when awaited contains the analysis result. - public static async Task RemoteListAnalysis(IBackendManager backendManager, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, VerifyMode verifyMode, CancellationToken cancellationToken) + public static async Task RemoteListAnalysisAsync(IBackendManager backendManager, Options options, LocalDatabase database, IBackendWriter log, IEnumerable protectedFiles, IEnumerable strictExcemptFiles, VerifyMode verifyMode, CancellationToken cancellationToken) { // If the last operation completed, no cleanup should be required - if (verifyMode == VerifyMode.VerifyAndClean && !await database.TerminatedWithActiveUploads(cancellationToken).ConfigureAwait(false)) + if (verifyMode == VerifyMode.VerifyAndClean && !await database.TerminatedWithActiveUploadsAsync(cancellationToken).ConfigureAwait(false)) verifyMode = VerifyMode.VerifyStrict; // Force cleanup should set the mode to cleanup @@ -319,12 +319,12 @@ namespace Duplicati.Library.Main.Operation log.UnknownFileCount = unknownlist.Count; log.UnknownFileSize = unknownlist.Select(x => Math.Max(0, x.Size)).Sum(); log.BackupListCount = await database - .FilesetTimes(cancellationToken) + .FilesetTimesAsync(cancellationToken) .CountAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); log.LastBackupDate = filesets.Count == 0 ? new DateTime(0) : filesets[0].Time.ToLocalTime(); - await CheckQuota(backendManager, options, log, knownFileSize).ConfigureAwait(false); + await CheckQuotaAsync(backendManager, options, log, knownFileSize).ConfigureAwait(false); foreach (var s in remotelist) lookup[s.File.Name] = s; @@ -335,17 +335,17 @@ namespace Duplicati.Library.Main.Operation var missingUploadingVolumes = new HashSet(); var protectedVolumes = new HashSet(); - await foreach (var e in database.DuplicateRemoteVolumes(cancellationToken).ConfigureAwait(false)) + await foreach (var e in database.DuplicateRemoteVolumesAsync(cancellationToken).ConfigureAwait(false)) { if (e.Value == RemoteVolumeState.Uploading || e.Value == RemoteVolumeState.Temporary) await database - .UnlinkRemoteVolume(e.Key, e.Value, cancellationToken) + .UnlinkRemoteVolumeAsync(e.Key, e.Value, cancellationToken) .ConfigureAwait(false); else throw new RemoteListVerificationException(string.Format("The remote volume {0} appears in the database with state {1} and a deleted state, cannot continue", e.Key, e.Value.ToString()), "AmbiguousStateRemoteFiles"); } - var locallist = database.GetRemoteVolumes(cancellationToken); + var locallist = database.GetRemoteVolumesAsync(cancellationToken); await foreach (var i in locallist.ConfigureAwait(false)) { var remoteFound = lookup.TryGetValue(i.Name, out var r); @@ -355,11 +355,11 @@ namespace Duplicati.Library.Main.Operation correctSize = true; if (archived && i.ArchiveTime <= DateTime.UnixEpoch) await database - .UpdateRemoteVolume(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, true, cancellationToken) + .UpdateRemoteVolumeAsync(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, true, cancellationToken) .ConfigureAwait(false); else if (!archived && i.ArchiveTime > DateTime.UnixEpoch) await database - .UpdateRemoteVolume(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, false, cancellationToken) + .UpdateRemoteVolumeAsync(i.Name, i.State, i.Size, i.Hash, true, TimeSpan.Zero, false, cancellationToken) .ConfigureAwait(false); lookup.Remove(i.Name); @@ -416,7 +416,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "PromotingCompleteFile", "Promoting uploaded complete file from {0} to {2}: {1}", i.State, i.Name, RemoteVolumeState.Uploaded); await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Uploaded, i.Size, i.Hash, cancellationToken) + .UpdateRemoteVolumeAsync(i.Name, RemoteVolumeState.Uploaded, i.Size, i.Hash, cancellationToken) .ConfigureAwait(false); } else if (!remoteFound) @@ -426,7 +426,7 @@ namespace Duplicati.Library.Main.Operation protectedVolumes.Add(i.Name); Logging.Log.WriteInformationMessage(LOGTAG, "KeepIncompleteFile", "Keeping protected incomplete remote file listed as {0}: {1}", i.State, i.Name); await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Temporary, i.Size, i.Hash, false, new TimeSpan(0), null, cancellationToken) + .UpdateRemoteVolumeAsync(i.Name, RemoteVolumeState.Temporary, i.Size, i.Hash, false, new TimeSpan(0), null, cancellationToken) .ConfigureAwait(false); } else if (verifyMode == VerifyMode.VerifyStrict && !strictExcemptFiles.Any(pf => pf == i.Name)) @@ -439,7 +439,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "SchedulingMissingFileForDelete", "Scheduling missing file for deletion, currently listed as {0}: {1}", i.State, i.Name); missingUploadingVolumes.Add(i.Name); await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Deleting, i.Size, i.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) + .UpdateRemoteVolumeAsync(i.Name, RemoteVolumeState.Deleting, i.Size, i.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); } } @@ -479,7 +479,7 @@ namespace Duplicati.Library.Main.Operation missing.Add(i); else if (correctSize) await database - .UpdateRemoteVolume(i.Name, RemoteVolumeState.Verified, i.Size, i.Hash, cancellationToken) + .UpdateRemoteVolumeAsync(i.Name, RemoteVolumeState.Verified, i.Size, i.Hash, cancellationToken) .ConfigureAwait(false); else missingHash.Add(new Tuple(r.File.Size, i)); @@ -508,7 +508,7 @@ namespace Duplicati.Library.Main.Operation else if (verifyMode == VerifyMode.VerifyAndClean || verifyMode == VerifyMode.VerifyStrict) { await database - .RemoveRemoteVolumes( + .RemoveRemoteVolumesAsync( missingUploadingVolumes .Concat(temporaryAndDeletedVolumes), cancellationToken @@ -518,10 +518,10 @@ namespace Duplicati.Library.Main.Operation // Clear the flag after we have cleaned up if (!options.Dryrun && protectedVolumes.Count == 0) await database - .TerminatedWithActiveUploads(cancellationToken, false) + .TerminatedWithActiveUploadsAsync(cancellationToken, false) .ConfigureAwait(false); } - else if (verifyMode == VerifyMode.VerifyOnly && await database.TerminatedWithActiveUploads(cancellationToken).ConfigureAwait(false)) + else if (verifyMode == VerifyMode.VerifyOnly && await database.TerminatedWithActiveUploadsAsync(cancellationToken).ConfigureAwait(false)) { Logging.Log.WriteWarningMessage(LOGTAG, "ActiveUploadsDetected", null, "Active uploads detected, but no cleanup was performed. Run the \"repair\" command to clean up the incomplete files"); } @@ -539,7 +539,7 @@ namespace Duplicati.Library.Main.Operation }; } - private static async Task CheckQuota(IBackendManager backendManager, Options options, IBackendWriter log, long knownFileSize) + private static async Task CheckQuotaAsync(IBackendManager backendManager, Options options, IBackendWriter log, long knownFileSize) { if (options.QuotaDisable) return; diff --git a/Duplicati/Library/Main/Operation/ListAffected.cs b/Duplicati/Library/Main/Operation/ListAffected.cs index 4bc3dab94..d63c45b01 100644 --- a/Duplicati/Library/Main/Operation/ListAffected.cs +++ b/Duplicati/Library/Main/Operation/ListAffected.cs @@ -48,17 +48,17 @@ namespace Duplicati.Library.Main.Operation if (callback == null) { m_result.SetResult( - await db.GetFilesets(args, m_result.TaskControl.ProgressToken) + await db.GetFilesetsAsync(args, m_result.TaskControl.ProgressToken) .OrderByDescending(x => x.Time) .ToArrayAsync() .ConfigureAwait(false), - await db.GetFiles(args, m_result.TaskControl.ProgressToken) + await db.GetFilesAsync(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetLogLines(args, m_result.TaskControl.ProgressToken) + await db.GetLogLinesAsync(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetVolumes(args, m_result.TaskControl.ProgressToken) + await db.GetVolumesAsync(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false) ); @@ -66,17 +66,17 @@ namespace Duplicati.Library.Main.Operation else { m_result.SetResult( - await db.GetFilesets(args, m_result.TaskControl.ProgressToken) + await db.GetFilesetsAsync(args, m_result.TaskControl.ProgressToken) .OrderByDescending(x => x.Time) .ToArrayAsync() .ConfigureAwait(false), - await db.GetFiles(args, m_result.TaskControl.ProgressToken) + await db.GetFilesAsync(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetLogLines(args, m_result.TaskControl.ProgressToken) + await db.GetLogLinesAsync(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false), - await db.GetVolumes(args, m_result.TaskControl.ProgressToken) + await db.GetVolumesAsync(args, m_result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false) ); diff --git a/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs b/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs index 41057940e..95f629636 100644 --- a/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListBrokenFilesHandler.cs @@ -53,30 +53,30 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); await using var db = await Database.LocalListBrokenFilesDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(backendManager, db, filter, callbackhandler).ConfigureAwait(false); await db.Transaction.RollBackAsync().ConfigureAwait(false); } - public static async Task<((DateTime FilesetTime, long FilesetID, long RemoveCount)[]?, List? Missing)> GetBrokenFilesetsFromRemote(IBackendManager backendManager, BasicResults result, Database.LocalListBrokenFilesDatabase db, Options options) + public static async Task<((DateTime FilesetTime, long FilesetID, long RemoveCount)[]?, List? Missing)> GetBrokenFilesetsFromRemoteAsync(IBackendManager backendManager, BasicResults result, Database.LocalListBrokenFilesDatabase db, Options options) { List? missing = null; var brokensets = await db - .GetBrokenFilesets(options.Time, options.Version, result.TaskControl.ProgressToken) + .GetBrokenFilesetsAsync(options.Time, options.Version, result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); if (brokensets.Length == 0) { - if (await db.RepairInProgress(result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await db.RepairInProgressAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("Cannot continue because the database is marked as being under repair, but does not have broken files.", "CannotListOnDatabaseInRepair"); Logging.Log.WriteInformationMessage(LOGTAG, "NoBrokenFilesetsInDatabase", "No broken filesets found in database, checking for missing remote files"); - var remotestate = await FilelistProcessor.RemoteListAnalysis(backendManager, options, db, result.BackendWriter, null, null, FilelistProcessor.VerifyMode.VerifyOnly, result.TaskControl.ProgressToken).ConfigureAwait(false); + var remotestate = await FilelistProcessor.RemoteListAnalysisAsync(backendManager, options, db, result.BackendWriter, null, null, FilelistProcessor.VerifyMode.VerifyOnly, result.TaskControl.ProgressToken).ConfigureAwait(false); if (!remotestate.ParsedVolumes.Any()) throw new UserInformationException("No remote volumes were found, refusing purge", "CannotPurgeWithNoRemoteVolumes"); @@ -90,24 +90,24 @@ namespace Duplicati.Library.Main.Operation // Mark all volumes as disposable foreach (var f in missing) await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteInformationMessage(LOGTAG, "MarkedRemoteFilesForDeletion", "Marked {0} remote files for deletion", missing.Count); // Drop all content from tables await db - .RemoveMissingBlocks(missing.Select(x => x.Name), result.TaskControl.ProgressToken) + .RemoveMissingBlocksAsync(missing.Select(x => x.Name), result.TaskControl.ProgressToken) .ConfigureAwait(false); // Mark all orphaned index files as disposable after removing the missing block files - await foreach (var f in db.GetOrphanedIndexFiles(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var f in db.GetOrphanedIndexFilesAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(f.Name, RemoteVolumeState.Deleting, f.Size, f.Hash, result.TaskControl.ProgressToken) .ConfigureAwait(false); brokensets = await db - .GetBrokenFilesets(options.Time, options.Version, result.TaskControl.ProgressToken) + .GetBrokenFilesetsAsync(options.Time, options.Version, result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false); } @@ -120,10 +120,10 @@ namespace Duplicati.Library.Main.Operation if (filter != null && !filter.Empty) throw new UserInformationException("Filters are not supported for this operation", "FiltersAreNotSupportedForListBrokenFiles"); - if (await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await db.PartiallyRecreatedAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The command does not work on partially recreated databases", "ListBrokenFilesDoesNotWorkOnPartialDatabase"); - (var brokensets, var missing) = await GetBrokenFilesetsFromRemote(backendManager, m_result, db, m_options).ConfigureAwait(false); + (var brokensets, var missing) = await GetBrokenFilesetsFromRemoteAsync(backendManager, m_result, db, m_options).ConfigureAwait(false); if (brokensets == null) return; @@ -142,7 +142,7 @@ namespace Duplicati.Library.Main.Operation } var fstimes = await db - .FilesetTimes(m_result.TaskControl.ProgressToken) + .FilesetTimesAsync(m_result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -165,7 +165,7 @@ namespace Duplicati.Library.Main.Operation x.Timestamp, callbackhandler == null && !m_options.ListSetsOnly ? await db - .GetBrokenFilenames(x.FilesetID, m_result.TaskControl.ProgressToken) + .GetBrokenFilenamesAsync(x.FilesetID, m_result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false) : new MockList>((int)x.BrokenCount) @@ -175,7 +175,7 @@ namespace Duplicati.Library.Main.Operation if (callbackhandler != null) foreach (var bs in brokenfilesets) - await foreach (var fe in db.GetBrokenFilenames(bs.FilesetID, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var fe in db.GetBrokenFilenamesAsync(bs.FilesetID, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) if (!callbackhandler(bs.Version, bs.Timestamp, bs.BrokenCount, fe.Item1, fe.Item2)) break; } diff --git a/Duplicati/Library/Main/Operation/ListChangesHandler.cs b/Duplicati/Library/Main/Operation/ListChangesHandler.cs index a23ee7774..884acb6f4 100644 --- a/Duplicati/Library/Main/Operation/ListChangesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListChangesHandler.cs @@ -81,11 +81,11 @@ namespace Duplicati.Library.Main.Operation using var tmpdb = useLocalDb ? null : new TempFile(); await using var db = await Database.LocalListChangesDatabase.CreateAsync(useLocalDb ? m_options.Dbpath : (string)tmpdb, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await using var storageKeeper = await db.CreateStorageHelper(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await using var storageKeeper = await db.CreateStorageHelperAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (useLocalDb) { var dbtimes = await db - .FilesetTimes(m_result.TaskControl.ProgressToken) + .FilesetTimesAsync(m_result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -101,10 +101,10 @@ namespace Duplicati.Library.Main.Operation SelectTime(compareVersion, times, out compareVersionIndex, out compareVersionTime, out compareVersionId); await storageKeeper - .AddFromDb(baseVersionId, false, filter, m_result.TaskControl.ProgressToken) + .AddFromDbAsync(baseVersionId, false, filter, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await storageKeeper - .AddFromDb(compareVersionId, true, filter, m_result.TaskControl.ProgressToken) + .AddFromDbAsync(compareVersionId, true, filter, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } else @@ -143,7 +143,7 @@ namespace Duplicati.Library.Main.Operation } }; - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; using (new Logging.Timer(LOGTAG, "InsertBaseFiles", "Inserting base files into database")) @@ -152,10 +152,10 @@ namespace Duplicati.Library.Main.Operation foreach (var f in rd.Files) if (FilterExpression.Matches(filter, f.Path)) await storageKeeper - .AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), false, false, m_result.TaskControl.ProgressToken) + .AddElementAsync(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), false, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; using (new Logging.Timer(LOGTAG, "InsertCompareFiles", "Inserting compare files into database")) @@ -164,20 +164,20 @@ namespace Duplicati.Library.Main.Operation foreach (var f in rd.Files) if (FilterExpression.Matches(filter, f.Path)) await storageKeeper - .AddElement(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), true, false, m_result.TaskControl.ProgressToken) + .AddElementAsync(f.Path, f.Hash, f.Metahash, f.Size, conv(f.Type), true, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } var changes = await storageKeeper - .CreateChangeCountReport(m_result.TaskControl.ProgressToken) + .CreateChangeCountReportAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var sizes = await storageKeeper - .CreateChangeSizeReport(m_result.TaskControl.ProgressToken) + .CreateChangeSizeReportAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var lst = (m_options.FullResult || callback != null) ? - (from n in storageKeeper.CreateChangedFileReport(m_result.TaskControl.ProgressToken) + (from n in storageKeeper.CreateChangedFileReportAsync(m_result.TaskControl.ProgressToken) select n) : null; m_result.SetResult( diff --git a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs index 459443f17..e4dc2e93c 100644 --- a/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListControlFilesHandler.cs @@ -57,12 +57,12 @@ namespace Duplicati.Library.Main.Operation foreach (var fileversion in filteredList) try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; var file = fileversion.Value.File; var entry = await db - .GetRemoteVolume(file.Name, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeAsync(file.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var files = new List(); diff --git a/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs b/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs index 682f79db1..53eb6fe26 100644 --- a/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFileVersionsHandler.cs @@ -54,7 +54,7 @@ internal static class ListFileVersionsHandler if (!options.AllVersions) { filesetIds = await db - .GetFilesetIDs(options.Time, options.Version, false, result.TaskControl.ProgressToken) + .GetFilesetIDsAsync(options.Time, options.Version, false, result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -67,7 +67,7 @@ internal static class ListFileVersionsHandler paths = paths.Select(path => Util.AppendDirSeparator(path)).ToArray(); result.FileVersions = await db - .ListFileVersions(paths, filesetIds, offset, limit, result.TaskControl.ProgressToken) + .ListFileVersionsAsync(paths, filesetIds, offset, limit, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Operation/ListFilesHandler.cs b/Duplicati/Library/Main/Operation/ListFilesHandler.cs index 660e2cc4b..a4ec9a5b4 100644 --- a/Duplicati/Library/Main/Operation/ListFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFilesHandler.cs @@ -57,13 +57,13 @@ namespace Duplicati.Library.Main.Operation if (!m_options.NoLocalDb && System.IO.File.Exists(m_options.Dbpath)) await using (var db = await Database.LocalListDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await using var filesets = await db.SelectFileSets(m_options.Time, m_options.Version, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await using var filesets = await db.SelectFileSetsAsync(m_options.Time, m_options.Version, m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (!filter.Empty) { if (simpleList || (m_options.ListFolderContents && !m_options.AllVersions)) { await filesets - .TakeFirst(m_result.TaskControl.ProgressToken) + .TakeFirstAsync(m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -71,11 +71,11 @@ namespace Duplicati.Library.Main.Operation IAsyncEnumerable files; if (m_options.ListFolderContents) { - files = filesets.SelectFolderContents(filter, m_result.TaskControl.ProgressToken); + files = filesets.SelectFolderContentsAsync(filter, m_result.TaskControl.ProgressToken); } else if (m_options.ListPrefixOnly) { - files = filesets.GetLargestPrefix(filter, m_result.TaskControl.ProgressToken); + files = filesets.GetLargestPrefixAsync(filter, m_result.TaskControl.ProgressToken); } else if (filter.Empty) { @@ -83,14 +83,14 @@ namespace Duplicati.Library.Main.Operation } else { - files = filesets.SelectFiles(filter, m_result.TaskControl.ProgressToken); + files = filesets.SelectFilesAsync(filter, m_result.TaskControl.ProgressToken); } if (m_options.ListSetsOnly) { m_result.SetResult( await filesets - .QuickSets(m_result.TaskControl.ProgressToken) + .QuickSetsAsync(m_result.TaskControl.ProgressToken) .Select(x => new ListResultFileset(x.Version, x.IsFullBackup, x.Time, x.FileCount, x.FileSizes)) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false), @@ -101,7 +101,7 @@ namespace Duplicati.Library.Main.Operation { m_result.SetResult( await filesets - .Sets(m_result.TaskControl.ProgressToken) + .SetsAsync(m_result.TaskControl.ProgressToken) .Select(x => new ListResultFileset(x.Version, x.IsFullBackup, x.Time, x.FileCount, x.FileSizes)) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false), @@ -112,7 +112,7 @@ namespace Duplicati.Library.Main.Operation new ListResultFile( n.Path, await n - .Sizes(m_result.TaskControl.ProgressToken) + .SizesAsync(m_result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false) ) @@ -156,7 +156,7 @@ namespace Duplicati.Library.Main.Operation filteredList.RemoveAt(0); Dictionary> res; - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; using (var tmpfile = await backendManager.GetAsync(firstEntry.File.Name, null, firstEntry.File.Size, cancellationToken).ConfigureAwait(false)) @@ -198,7 +198,7 @@ namespace Duplicati.Library.Main.Operation using (tmpfile) using (var rd = new FilesetVolumeReader(flentry.CompressionModule, tmpfile, m_options)) { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; foreach (var p in from n in rd.Files where Library.Utility.FilterExpression.Matches(filter, n.Path) select n) diff --git a/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs b/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs index a02d86f21..a1b78940c 100644 --- a/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFilesetsHandler.cs @@ -54,7 +54,7 @@ internal static class ListFilesetsHandler .ConfigureAwait(false); result.Filesets = await db - .ListFilesetsExtended(result.TaskControl.ProgressToken) + .ListFilesetsExtendedAsync(result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Operation/ListFolderHandler.cs b/Duplicati/Library/Main/Operation/ListFolderHandler.cs index 313f4ace7..aeef09478 100644 --- a/Duplicati/Library/Main/Operation/ListFolderHandler.cs +++ b/Duplicati/Library/Main/Operation/ListFolderHandler.cs @@ -51,7 +51,7 @@ internal static class ListFolderHandler .ConfigureAwait(false); var filesetIds = await db - .GetFilesetIDs(options.Time, options.Version, singleTimeMatch: true, result.TaskControl.ProgressToken) + .GetFilesetIDsAsync(options.Time, options.Version, singleTimeMatch: true, result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -65,7 +65,7 @@ internal static class ListFolderHandler if (folders != null && folders.Length > 1) throw new UserInformationException("When no folder is specified, only one folder can be listed", "MultipleFoldersFound"); var rootFolders = await db - .GetMinimalUniquePrefixEntries(filesetIds[0], result.TaskControl.ProgressToken) + .GetMinimalUniquePrefixEntriesAsync(filesetIds[0], result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); result.Entries = new PaginatedResults(0, rootFolders.Count, 1, rootFolders.Count, rootFolders); @@ -73,8 +73,8 @@ internal static class ListFolderHandler else { var entries = await db - .ListFolder( - db.GetPrefixIds(folders, result.TaskControl.ProgressToken).ToBlockingEnumerable(), + .ListFolderAsync( + db.GetPrefixIdsAsync(folders, result.TaskControl.ProgressToken).ToBlockingEnumerable(), filesetIds[0], offset, limit, @@ -88,7 +88,7 @@ internal static class ListFolderHandler if (extendedData) { var coreentries = result.Entries.Items.Cast().ToArray(); - var metadata = await db.GetMetadataForFilesetIds(coreentries.Select(e => e.FileId), result.TaskControl.ProgressToken).ConfigureAwait(false); + var metadata = await db.GetMetadataForFilesetIdsAsync(coreentries.Select(e => e.FileId), result.TaskControl.ProgressToken).ConfigureAwait(false); for (int i = 0; i < coreentries.Length; i++) { if (metadata.TryGetValue(coreentries[i].FileId, out var dict)) diff --git a/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs b/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs index 68b44916b..0a40560ef 100644 --- a/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/PurgeBrokenFilesHandler.cs @@ -55,15 +55,15 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException("Filters are not supported for this operation", "FiltersNotAllowedOnPurgeBrokenFiles"); await using var db = await LocalListBrokenFilesDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - if (await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await db.PartiallyRecreatedAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The command does not work on partially recreated databases", "CannotPurgeOnPartialDatabase"); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - var (sets, missing) = await ListBrokenFilesHandler.GetBrokenFilesetsFromRemote(backendManager, m_result, db, m_options).ConfigureAwait(false); + var (sets, missing) = await ListBrokenFilesHandler.GetBrokenFilesetsFromRemoteAsync(backendManager, m_result, db, m_options).ConfigureAwait(false); if (sets == null) return; @@ -84,7 +84,7 @@ namespace Duplicati.Library.Main.Operation var pgspan = 0.95f / sets.Length; var filesets = await db - .FilesetTimes(m_result.TaskControl.ProgressToken) + .FilesetTimesAsync(m_result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -95,7 +95,7 @@ namespace Duplicati.Library.Main.Operation RemoveCount = x.RemoveCount, Version = filesets.FindIndex(y => y.Key == x.FilesetID), SetCount = await db - .GetFilesetFileCount(x.FilesetID, m_result.TaskControl.ProgressToken) + .GetFilesetFileCountAsync(x.FilesetID, m_result.TaskControl.ProgressToken) .ConfigureAwait(false) }) .Select(x => x.Result) @@ -106,7 +106,7 @@ namespace Duplicati.Library.Main.Operation { var emptymetadata = Utility.WrapMetadata(new Dictionary(), m_options); replacementMetadataBlocksetId = await db - .GetEmptyMetadataBlocksetId( + .GetEmptyMetadataBlocksetIdAsync( (missing ?? []).Select(x => x.ID), emptymetadata.FileHash, emptymetadata.Blob.Length, @@ -120,7 +120,7 @@ namespace Duplicati.Library.Main.Operation var fully_emptied = compare_list.Where(x => x.RemoveCount == x.SetCount).ToArray(); var to_purge = compare_list.Where(x => x.RemoveCount != x.SetCount).ToArray(); - if (fully_emptied.Length == await db.FilesetTimes(m_result.TaskControl.ProgressToken).CountAsync(cancellationToken: m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (fully_emptied.Length == await db.FilesetTimesAsync(m_result.TaskControl.ProgressToken).CountAsync(cancellationToken: m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("All filesets are fully broken and needs to be removed. To avoid unexpected deletions, you must manually remove the remote files and delete the database.", "AllFilesetsBroken"); if (!m_options.Dryrun) @@ -168,7 +168,7 @@ namespace Duplicati.Library.Main.Operation { // Recompute the version number after we deleted the versions before filesets = await pgdb - .FilesetTimes(m_result.TaskControl.ProgressToken) + .FilesetTimesAsync(m_result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var thisversion = filesets.FindIndex(y => y.Key == bs.FilesetID); @@ -187,9 +187,9 @@ namespace Duplicati.Library.Main.Operation // Update entries that would be removed because of missing metadata var updatedEntries = 0; if (!m_options.DisableReplaceMissingMetadata) - updatedEntries = await db.ReplaceMetadata(filesetid, replacementMetadataBlocksetId, m_result.TaskControl.ProgressToken); + updatedEntries = await db.ReplaceMetadataAsync(filesetid, replacementMetadataBlocksetId, m_result.TaskControl.ProgressToken); - await db.InsertBrokenFileIDsIntoTable(filesetid, tablename, "FileID", m_result.TaskControl.ProgressToken); + await db.InsertBrokenFileIDsIntoTableAsync(filesetid, tablename, "FileID", m_result.TaskControl.ProgressToken); return updatedEntries; }).ConfigureAwait(false); } @@ -202,20 +202,20 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0.95f); - if (!m_options.Dryrun && await db.RepairInProgress(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (!m_options.Dryrun && await db.RepairInProgressAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { Logging.Log.WriteInformationMessage(LOGTAG, "ValidatingDatabase", "Database was previously marked as in-progress, checking if it is valid after purging files"); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteInformationMessage(LOGTAG, "UpdatingDatabase", "Purge completed, and consistency checks completed, marking database as complete"); - await db.RepairInProgress(m_result.TaskControl.ProgressToken, false).ConfigureAwait(false); + await db.RepairInProgressAsync(m_result.TaskControl.ProgressToken, false).ConfigureAwait(false); } else { await db.Transaction.RollBackAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs b/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs index 04938997d..b45a09eaa 100644 --- a/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/PurgeFilesHandler.cs @@ -55,7 +55,7 @@ namespace Duplicati.Library.Main.Operation await using var db = await Database.LocalPurgeDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await DoRunAsync(backendManager, db, filter, null, 0, 1).ConfigureAwait(false); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -69,42 +69,42 @@ namespace Duplicati.Library.Main.Operation var doCompactStep = !m_options.NoAutoCompact && filtercommand == null; - if (await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await db.PartiallyRecreatedAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) throw new UserInformationException("The purge command does not work on partially recreated databases", "PurgeNotAllowedOnPartialDatabase"); - if (await db.RepairInProgress(m_result.TaskControl.ProgressToken).ConfigureAwait(false) && filtercommand == null) + if (await db.RepairInProgressAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false) && filtercommand == null) throw new UserInformationException(string.Format("The purge command does not work on an incomplete database, try the {0} operation.", "purge-broken-files"), "PurgeNotAllowedOnIncompleteDatabase"); var versions = await db - .GetFilesetIDs(m_options.Time, m_options.Version, false, m_result.TaskControl.ProgressToken) + .GetFilesetIDsAsync(m_options.Time, m_options.Version, false, m_result.TaskControl.ProgressToken) .OrderByDescending(x => x) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (versions.Length <= 0) throw new UserInformationException("No filesets matched the supplied time or versions", "NoFilesetFoundForTimeOrVersion"); - var orphans = await db.CountOrphanFiles(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + var orphans = await db.CountOrphanFilesAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (orphans != 0) throw new UserInformationException(string.Format("Unable to start the purge process as there are {0} orphan file(s)", orphans), "CannotPurgeWithOrphans"); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (filtercommand == null) { - await db.VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, false, m_result.TaskControl.ProgressToken) + await db.VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (m_options.NoBackendverification) - await FilelistProcessor.VerifyLocalList(backendManager, db, m_options.Dryrun, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyLocalListAsync(backendManager, db, m_options.Dryrun, m_result.TaskControl.ProgressToken).ConfigureAwait(false); else - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, null, null, logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, db, m_result.BackendWriter, null, null, logErrors: true, verifyMode: FilelistProcessor.VerifyMode.VerifyStrict, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } var filesets = await db - .FilesetTimes(m_result.TaskControl.ProgressToken) + .FilesetTimesAsync(m_result.TaskControl.ProgressToken) .OrderByDescending(x => x.Value) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -118,7 +118,7 @@ namespace Duplicati.Library.Main.Operation // If we crash now, it is possible that the remote storage contains partial files if (!m_options.Dryrun) - await db.TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); + await db.TerminatedWithActiveUploadsAsync(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); // Reverse makes sure we re-write the old versions first foreach (var versionid in versions.Reverse()) @@ -130,21 +130,21 @@ namespace Duplicati.Library.Main.Operation if (ix < 0 || tsOriginal.Ticks == 0) throw new InvalidProgramException(string.Format("Fileset was reported with id {0}, but could not be found?", versionid)); - var ts = await FilesetVolumeWriter.ProbeUnusedFilenameName(db, m_options, tsOriginal, m_result.TaskControl.ProgressToken) + var ts = await FilesetVolumeWriter.ProbeUnusedFilenameNameAsync(db, m_options, tsOriginal, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - var prevfilename = await db.GetRemoteVolumeNameForFileset(filesets[ix].Key, m_result.TaskControl.ProgressToken) + var prevfilename = await db.GetRemoteVolumeNameForFilesetAsync(filesets[ix].Key, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (ix != 0 && filesets[ix - 1].Value <= ts) throw new Exception(string.Format("Unable to create a new fileset for {0} because the resulting timestamp {1} is larger than the next timestamp {2}", prevfilename, ts, filesets[ix - 1].Value)); - await using (var tempset = await db.CreateTemporaryFileset(versionid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await using (var tempset = await db.CreateTemporaryFilesetAsync(versionid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { tempset.ReducedPurgeStatistics = m_options.ReducedPurgeStatistics; if (filtercommand == null) - await tempset.ApplyFilter(filter, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await tempset.ApplyFilterAsync(filter, m_result.TaskControl.ProgressToken).ConfigureAwait(false); else - await tempset.ApplyFilter(filtercommand, m_result.TaskControl.ProgressToken) + await tempset.ApplyFilterAsync(filtercommand, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (tempset.RemovedFileCount + tempset.UpdatedFileCount == 0) @@ -162,10 +162,10 @@ namespace Duplicati.Library.Main.Operation using (var vol = new FilesetVolumeWriter(m_options, ts)) { var isOriginalFilesetFullBackup = await db - .IsFilesetFullBackup(tsOriginal, m_result.TaskControl.ProgressToken) + .IsFilesetFullBackupAsync(tsOriginal, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var newids = await tempset - .ConvertToPermanentFileset(vol.RemoteFilename, ts, isOriginalFilesetFullBackup, m_result.TaskControl.ProgressToken) + .ConvertToPermanentFilesetAsync(vol.RemoteFilename, ts, isOriginalFilesetFullBackup, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); vol.VolumeID = newids.Item1; vol.CreateFilesetFile(isOriginalFilesetFullBackup); @@ -173,7 +173,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "ReplacingFileset", "Replacing fileset {0} with {1} which has with {2} fewer file(s) ({3} reduction)", prevfilename, vol.RemoteFilename, tempset.RemovedFileCount, Library.Utility.Utility.FormatSizeString(tempset.RemovedFileSize)); await db - .WriteFileset(vol, newids.Item2, m_result.TaskControl.ProgressToken) + .WriteFilesetAsync(vol, newids.Item2, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.RemovedFileSize += tempset.RemovedFileSize; @@ -186,7 +186,7 @@ namespace Duplicati.Library.Main.Operation if (m_options.Dryrun || m_options.FullResult) { - await foreach (var fe in tempset.ListAllDeletedFiles(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var fe in tempset.ListAllDeletedFilesAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var msg = string.Format(" Purging file {0} ({1})", fe.Key, Library.Utility.Utility.FormatSizeString(fe.Value)); @@ -213,15 +213,15 @@ namespace Duplicati.Library.Main.Operation else { await db - .UpdateRemoteVolume(vol.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(vol.RemoteFilename, RemoteVolumeState.Uploading, -1, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var lst = await db - .DropFilesetsFromTable(new[] { tsOriginal }, m_result.TaskControl.ProgressToken) + .DropFilesetsFromTableAsync(new[] { tsOriginal }, m_result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); foreach (var f in lst) await db - .UpdateRemoteVolume(f.Key, RemoteVolumeState.Deleting, f.Value, null, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(f.Key, RemoteVolumeState.Deleting, f.Value, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db.Transaction @@ -252,7 +252,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) await db - .TerminatedWithActiveUploads(m_result.TaskControl.ProgressToken, false) + .TerminatedWithActiveUploadsAsync(m_result.TaskControl.ProgressToken, false) .ConfigureAwait(false); if (doCompactStep) diff --git a/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs b/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs index 6ff013786..08be0eec0 100644 --- a/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs +++ b/Duplicati/Library/Main/Operation/ReadLockInfoHandler.cs @@ -88,7 +88,7 @@ namespace Duplicati.Library.Main.Operation var updatedCount = 0; var errorCount = 0; - await foreach (var (name, lockExpiration) in database.GetRemoteVolumesWithLockExpiration(!m_options.RefreshLockInfoComplete, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var (name, lockExpiration) in database.GetRemoteVolumesWithLockExpirationAsync(!m_options.RefreshLockInfoComplete, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { readCount++; try @@ -99,7 +99,7 @@ namespace Duplicati.Library.Main.Operation // Only update if the value has changed if (remoteLockExpiration != lockExpiration) { - await database.UpdateRemoteVolumeLockExpiration(name, remoteLockExpiration, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await database.UpdateRemoteVolumeLockExpirationAsync(name, remoteLockExpiration, m_result.TaskControl.ProgressToken).ConfigureAwait(false); updatedCount++; Log.WriteVerboseMessage(LOGTAG, "UpdatedLockExpiration", "Updated lock expiration for {0} to {1}", name, remoteLockExpiration); diff --git a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs index 57c9a2082..d55cc2035 100644 --- a/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs +++ b/Duplicati/Library/Main/Operation/RecreateDatabaseHandler.cs @@ -71,7 +71,7 @@ namespace Duplicati.Library.Main.Operation // Ensure database is consistent after the recreate await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -90,7 +90,7 @@ namespace Duplicati.Library.Main.Operation await using var db = await LocalDatabase.CreateLocalDatabaseAsync(m_options.Dbpath, "Recreate", true, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - if ((await db.FindMatchingFilesets(m_options.Time, m_options.Version, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Any()) + if ((await db.FindMatchingFilesetsAsync(m_options.Time, m_options.Version, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Any()) { if (m_options.IgnoreUpdateIfVersionExists) { @@ -102,17 +102,17 @@ namespace Duplicati.Library.Main.Operation } // Mark as incomplete - await db.PartiallyRecreated(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); + await db.PartiallyRecreatedAsync(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); var preexistingOptionsInDatabase = - await Utility.ContainsOptionsForVerification(db, m_result.TaskControl.ProgressToken) + await Utility.ContainsOptionsForVerificationAsync(db, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // Make sure the options have not changed between calls, unless there are no previous options if (preexistingOptionsInDatabase) - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await DoRunAsync(backendManager, db, true, filter, filelistfilter, blockprocessor).ConfigureAwait(false); @@ -136,12 +136,12 @@ namespace Duplicati.Library.Main.Operation await using var restoredb = await LocalRecreateDatabase.CreateAsync(dbparent, m_options, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await restoredb.RepairInProgress(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); + await restoredb.RepairInProgressAsync(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); // If a filelist filter is applied, we're only recreating a partial database (e.g., for a specific version restore). // Mark it as partially recreated so it cannot be used for backups or other operations that require a complete database. if (filelistfilter != null) - await restoredb.PartiallyRecreated(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); + await restoredb.PartiallyRecreatedAsync(m_result.TaskControl.ProgressToken, true).ConfigureAwait(false); var expRecreateDb = false; // experimental recreate db code flag var volumeIds = new Dictionary(); @@ -210,9 +210,9 @@ namespace Duplicati.Library.Main.Operation // If we are updating, all files should be accounted for foreach (var fl in remotefiles) volumeIds[fl.File.Name] = updating - ? await restoredb.GetRemoteVolumeID(fl.File.Name, m_result.TaskControl.ProgressToken) + ? await restoredb.GetRemoteVolumeIDAsync(fl.File.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false) - : await restoredb.RegisterRemoteVolume(fl.File.Name, fl.FileType, fl.File.Size, RemoteVolumeState.Uploaded, m_result.TaskControl.ProgressToken) + : await restoredb.RegisterRemoteVolumeAsync(fl.File.Name, fl.FileType, fl.File.Size, RemoteVolumeState.Uploaded, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var hasUpdatedOptions = false; @@ -229,7 +229,7 @@ namespace Duplicati.Library.Main.Operation foreach (var n in filelists) if (volumeIds[n.File.Name] == -1) volumeIds[n.File.Name] = await restoredb - .RegisterRemoteVolume(n.File.Name, n.FileType, RemoteVolumeState.Uploaded, n.File.Size, new TimeSpan(0), m_result.TaskControl.ProgressToken) + .RegisterRemoteVolumeAsync(n.File.Name, n.FileType, RemoteVolumeState.Uploaded, n.File.Size, new TimeSpan(0), m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -239,7 +239,7 @@ namespace Duplicati.Library.Main.Operation var entry = new RemoteVolume(name, hash, size); try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(restoredb, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; @@ -267,7 +267,7 @@ namespace Duplicati.Library.Main.Operation if (!string.IsNullOrWhiteSpace(hash) && size > 0) await restoredb - .UpdateRemoteVolume(entry.Name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(entry.Name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var parsed = VolumeBase.ParseFilename(entry.Name); @@ -285,10 +285,10 @@ namespace Duplicati.Library.Main.Operation // Create timestamped operations based on the file timestamp var filesetid = await restoredb - .CreateFileset(volumeIds[entry.Name], parsed.Time, m_result.TaskControl.ProgressToken) + .CreateFilesetAsync(volumeIds[entry.Name], parsed.Time, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await RecreateFilesetFromRemoteList(restoredb, compressor, filesetid, m_options, filter, m_result.TaskControl.ProgressToken) + await RecreateFilesetFromRemoteListAsync(restoredb, compressor, filesetid, m_options, filter, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -328,7 +328,7 @@ namespace Duplicati.Library.Main.Operation //Make sure we write the config if it has been read from a manifest if (hasUpdatedOptions) - await Utility.VerifyOptionsAndUpdateDatabase(restoredb, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(restoredb, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); using (new Logging.Timer(LOGTAG, "CommitUpdateFilesetFromRemote", "CommitUpdateFilesetFromRemote")) @@ -360,7 +360,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(restoredb, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; @@ -379,7 +379,7 @@ namespace Duplicati.Library.Main.Operation { if (!string.IsNullOrWhiteSpace(hash) && size > 0) await restoredb - .UpdateRemoteVolume(name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); using (var svr = new IndexVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options, hashsize)) @@ -388,13 +388,13 @@ namespace Duplicati.Library.Main.Operation { var filename = a.Filename; var volumeID = await restoredb - .GetRemoteVolumeID(filename, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeIDAsync(filename, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // No such file if (volumeID < 0) (volumeID, filename) = - await ProbeForMatchingFilename(filename, restoredb, m_result.TaskControl.ProgressToken) + await ProbeForMatchingFilenameAsync(filename, restoredb, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var missing = false; @@ -407,7 +407,7 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteWarningMessage(LOGTAG, "MissingFileDetected", null, "Remote file referenced as {0} by {1}, but not found in list, registering a missing remote file", filename, name); missing = true; volumeID = await restoredb - .RegisterRemoteVolume(filename, p.FileType, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) + .RegisterRemoteVolumeAsync(filename, p.FileType, RemoteVolumeState.Temporary, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -415,15 +415,15 @@ namespace Duplicati.Library.Main.Operation //Add all block/volume mappings foreach (var b in a.Blocks) { - anyChange |= (await restoredb.UpdateBlock(b.Key, b.Value, volumeID, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Item1; + anyChange |= (await restoredb.UpdateBlockAsync(b.Key, b.Value, volumeID, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Item1; } await restoredb - .UpdateRemoteVolume(filename, missing ? RemoteVolumeState.Temporary : RemoteVolumeState.Verified, a.Length, a.Hash, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(filename, missing ? RemoteVolumeState.Temporary : RemoteVolumeState.Verified, a.Length, a.Hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await restoredb - .AddIndexBlockLink( - await restoredb.GetRemoteVolumeID(name, m_result.TaskControl.ProgressToken).ConfigureAwait(false), + .AddIndexBlockLinkAsync( + await restoredb.GetRemoteVolumeIDAsync(name, m_result.TaskControl.ProgressToken).ConfigureAwait(false), volumeID, m_result.TaskControl.ProgressToken ) @@ -442,7 +442,7 @@ namespace Duplicati.Library.Main.Operation // done before we add it to the database, since we do not have nested transactions var list = b.Blocklist.ToList(); await restoredb - .AddTempBlockListHash(b.Hash, list, m_result.TaskControl.ProgressToken) + .AddTempBlockListHashAsync(b.Hash, list, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (System.IO.InvalidDataException e) @@ -492,18 +492,18 @@ namespace Duplicati.Library.Main.Operation // if we are lucky and pick the right ones } - await restoredb.CleanupMissingVolumes(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await restoredb.CleanupMissingVolumesAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); // Update the real tables from the temp tables if (expRecreateDb) // add missing blocks and blocksetentry data (at this point // we have not yet anything in the blocksetentry table) await restoredb - .AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize, false, m_result.TaskControl.ProgressToken) + .AddBlockAndBlockSetEntryFromTempAsync(hashsize, m_options.Blocksize, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); else await restoredb - .FindMissingBlocklistHashes(hashsize, m_options.Blocksize, m_result.TaskControl.ProgressToken) + .FindMissingBlocklistHashesAsync(hashsize, m_options.Blocksize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); // We have now grabbed as much information as possible, @@ -513,7 +513,7 @@ namespace Duplicati.Library.Main.Operation { // Grab the list matching the pass type var lst = await restoredb - .GetMissingBlockListVolumes(i, m_options.Blocksize, hashsize, m_options.RepairForceBlockUse, m_result.TaskControl.ProgressToken) + .GetMissingBlockListVolumesAsync(i, m_options.Blocksize, hashsize, m_options.RepairForceBlockUse, m_result.TaskControl.ProgressToken) .ToListAsync() .ConfigureAwait(false); @@ -545,7 +545,7 @@ namespace Duplicati.Library.Main.Operation using (tmpfile) using (var rd = new BlockVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options)) { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(restoredb, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; @@ -561,26 +561,26 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteVerboseMessage(LOGTAG, "ProcessingBlocklistVolumes", "Pass {0} of 3, processing blocklist volume {1} of {2}", (i + 1), progress, lst.Count); var volumeid = await restoredb - .GetRemoteVolumeID(name, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeIDAsync(name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await restoredb - .UpdateRemoteVolume(name, RemoteVolumeState.Uploaded, size, hash, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(name, RemoteVolumeState.Uploaded, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); bool anyChange = false; // Update the block table so we know about the block/volume map foreach (var h in rd.Blocks) { - anyChange |= (await restoredb.UpdateBlock(h.Key, h.Value, volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Item1; + anyChange |= (await restoredb.UpdateBlockAsync(h.Key, h.Value, volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)).Item1; } // now that we have the blocks/volume relationships, we can go from the (already known from dlist step) blocklisthashes // to the needed list blocks in the volume, so grab them from the database // read the blocks list hashes from the volume data (the handled file) and insert them into the temp blocklisthash table - await foreach (var blocklisthash in restoredb.GetBlockLists(volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var blocklisthash in restoredb.GetBlockListsAsync(volumeid, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - if (await restoredb.AddTempBlockListHash(blocklisthash, rd.ReadBlocklist(blocklisthash, hashsize), m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await restoredb.AddTempBlockListHashAsync(blocklisthash, rd.ReadBlocklist(blocklisthash, hashsize), m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { anyChange = true; } @@ -599,11 +599,11 @@ namespace Duplicati.Library.Main.Operation } if (expRecreateDb) await restoredb - .AddBlockAndBlockSetEntryFromTemp(hashsize, m_options.Blocksize, false, m_result.TaskControl.ProgressToken) + .AddBlockAndBlockSetEntryFromTempAsync(hashsize, m_options.Blocksize, false, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); else await restoredb - .FindMissingBlocklistHashes(hashsize, m_options.Blocksize, m_result.TaskControl.ProgressToken) + .FindMissingBlocklistHashesAsync(hashsize, m_options.Blocksize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -640,10 +640,10 @@ namespace Duplicati.Library.Main.Operation // All blocks are collected and added into the Block table // Find out which blocks are deleted and move them into DeletedBlock, // so that compact can calculate the unused space - await restoredb.CleanupDeletedBlocks(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await restoredb.CleanupDeletedBlocksAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); } - await restoredb.CleanupMissingVolumes(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await restoredb.CleanupMissingVolumesAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); if (!m_options.RepairOnlyPaths && m_options.StoreMetadataContentInDatabase) { @@ -673,7 +673,7 @@ namespace Duplicati.Library.Main.Operation await using (var lbfdb = await LocalListBrokenFilesDatabase.CreateAsync(restoredb, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { var broken = await lbfdb - .GetBrokenFilesets(new DateTime(0), null, m_result.TaskControl.ProgressToken) + .GetBrokenFilesetsAsync(new DateTime(0), null, m_result.TaskControl.ProgressToken) .CountAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -688,19 +688,19 @@ namespace Duplicati.Library.Main.Operation if (filelistfilter != null) { await restoredb - .VerifyConsistencyForRepair(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) + .VerifyConsistencyForRepairAsync(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } else { await restoredb - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, true, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } Logging.Log.WriteInformationMessage(LOGTAG, "RecreateCompleted", "Recreate completed, and consistency checks completed, marking database as complete"); - await restoredb.RepairInProgress(m_result.TaskControl.ProgressToken, false).ConfigureAwait(false); + await restoredb.RepairInProgressAsync(m_result.TaskControl.ProgressToken, false).ConfigureAwait(false); } m_result.EndTime = DateTime.UtcNow; @@ -735,7 +735,7 @@ namespace Duplicati.Library.Main.Operation var volumeInfo = new Dictionary(StringComparer.Ordinal); var metaStates = new Dictionary(); - await foreach (var info in restoredb.GetMissingMetadataBlocks(cancellationToken).ConfigureAwait(false)) + await foreach (var info in restoredb.GetMissingMetadataBlocksAsync(cancellationToken).ConfigureAwait(false)) { var metadataId = info.MetadataId; var metaLength = info.MetaLength; @@ -772,7 +772,7 @@ namespace Duplicati.Library.Main.Operation { using (tmpfile) { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; if (!volumeBlocks.TryGetValue(name, out var blocksForVolume) || blocksForVolume.Count == 0) @@ -820,7 +820,7 @@ namespace Duplicati.Library.Main.Operation var remaining = state.MetaLength - ms.Length; var take = (int)Math.Min(remaining, kvp.Value.LongLength); - ms.Write(kvp.Value, 0, take); + await ms.WriteAsync(kvp.Value, 0, take); } if (ms.Length != state.MetaLength) @@ -830,7 +830,7 @@ namespace Duplicati.Library.Main.Operation else { var content = Library.Utility.Utility.GetStringWithoutBOM(ms, false); - await restoredb.SetMetadataContent(state.MetadataId, content, cancellationToken).ConfigureAwait(false); + await restoredb.SetMetadataContentAsync(state.MetadataId, content, cancellationToken).ConfigureAwait(false); } } catch (Exception ex) @@ -864,7 +864,7 @@ namespace Duplicati.Library.Main.Operation /// The filter to apply to the files. /// The cancellation token to monitor for cancellation requests. /// A task that completes when the fileset has been recreated. - public static async Task RecreateFilesetFromRemoteList(LocalRecreateDatabase restoredb, ICompression compressor, long filesetid, Options options, IFilter filter, CancellationToken cancellationToken) + public static async Task RecreateFilesetFromRemoteListAsync(LocalRecreateDatabase restoredb, ICompression compressor, long filesetid, Options options, IFilter filter, CancellationToken cancellationToken) { var blocksize = options.Blocksize; var hashes_pr_block = blocksize / options.BlockhashSize; @@ -874,11 +874,11 @@ namespace Duplicati.Library.Main.Operation // update fileset using filesetData await restoredb - .UpdateFullBackupStateInFileset(filesetid, filesetData.IsFullBackup, cancellationToken) + .UpdateFullBackupStateInFilesetAsync(filesetid, filesetData.IsFullBackup, cancellationToken) .ConfigureAwait(false); // clear any existing fileset entries - await restoredb.ClearFilesetEntries(filesetid, cancellationToken).ConfigureAwait(false); + await restoredb.ClearFilesetEntriesAsync(filesetid, cancellationToken).ConfigureAwait(false); using (var filelistreader = new FilesetVolumeReader(compressor, options)) foreach (var fe in filelistreader.Files.Where(x => Library.Utility.FilterExpression.Matches(filter, x.Path))) @@ -919,17 +919,17 @@ namespace Duplicati.Library.Main.Operation var metadataid = long.MinValue; var split = LocalDatabase.SplitIntoPrefixAndName(fe.Path); var prefixid = await restoredb - .GetOrCreatePathPrefix(split.Key, cancellationToken) + .GetOrCreatePathPrefixAsync(split.Key, cancellationToken) .ConfigureAwait(false); switch (fe.Type) { case FilelistEntryType.Folder: metadataid = await restoredb - .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) + .AddMetadatasetAsync(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) .ConfigureAwait(false); await restoredb - .AddDirectoryEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, cancellationToken) + .AddDirectoryEntryAsync(filesetid, prefixid, split.Value, fe.Time, metadataid, cancellationToken) .ConfigureAwait(false); break; @@ -939,24 +939,24 @@ namespace Duplicati.Library.Main.Operation if (expectedblocks <= 1) expectedblocklisthashes = 0; var blocksetid = await restoredb - .AddBlockset(fe.Hash, fe.Size, fe.BlocklistHashes, expectedblocklisthashes, cancellationToken) + .AddBlocksetAsync(fe.Hash, fe.Size, fe.BlocklistHashes, expectedblocklisthashes, cancellationToken) .ConfigureAwait(false); metadataid = await restoredb - .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) + .AddMetadatasetAsync(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) .ConfigureAwait(false); await restoredb - .AddFileEntry(filesetid, prefixid, split.Value, fe.Time, blocksetid, metadataid, cancellationToken) + .AddFileEntryAsync(filesetid, prefixid, split.Value, fe.Time, blocksetid, metadataid, cancellationToken) .ConfigureAwait(false); if (fe.Size <= blocksize && fe.Size > 0) { if (!string.IsNullOrWhiteSpace(fe.Blockhash)) await restoredb - .AddSmallBlocksetLink(fe.Hash, fe.Blockhash, fe.Blocksize, cancellationToken) + .AddSmallBlocksetLinkAsync(fe.Hash, fe.Blockhash, fe.Blocksize, cancellationToken) .ConfigureAwait(false); else if (options.BlockHashAlgorithm == options.FileHashAlgorithm) await restoredb - .AddSmallBlocksetLink(fe.Hash, fe.Hash, fe.Size, cancellationToken) + .AddSmallBlocksetLinkAsync(fe.Hash, fe.Hash, fe.Size, cancellationToken) .ConfigureAwait(false); else Logging.Log.WriteWarningMessage(LOGTAG, "MissingBlockHash", null, "No block hash found for file: {0}", fe.Path); @@ -965,10 +965,10 @@ namespace Duplicati.Library.Main.Operation break; case FilelistEntryType.Symlink: metadataid = await restoredb - .AddMetadataset(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) + .AddMetadatasetAsync(fe.Metahash, fe.Metasize, fe.MetaBlocklistHashes, expectedmetablocklisthashes, cancellationToken) .ConfigureAwait(false); await restoredb - .AddSymlinkEntry(filesetid, prefixid, split.Value, fe.Time, metadataid, cancellationToken) + .AddSymlinkEntryAsync(filesetid, prefixid, split.Value, fe.Time, metadataid, cancellationToken) .ConfigureAwait(false); break; default: @@ -980,11 +980,11 @@ namespace Duplicati.Library.Main.Operation { if (!string.IsNullOrWhiteSpace(fe.Metablockhash)) await restoredb - .AddSmallBlocksetLink(fe.Metahash, fe.Metablockhash, fe.Metasize, cancellationToken) + .AddSmallBlocksetLinkAsync(fe.Metahash, fe.Metablockhash, fe.Metasize, cancellationToken) .ConfigureAwait(false); else if (options.BlockHashAlgorithm == options.FileHashAlgorithm) await restoredb - .AddSmallBlocksetLink(fe.Metahash, fe.Metahash, fe.Metasize, cancellationToken) + .AddSmallBlocksetLinkAsync(fe.Metahash, fe.Metahash, fe.Metasize, cancellationToken) .ConfigureAwait(false); else Logging.Log.WriteWarningMessage(LOGTAG, "MissingMetadataBlockHash", null, "No block hash found for file metadata: {0}", fe.Path); @@ -1006,7 +1006,7 @@ namespace Duplicati.Library.Main.Operation /// The database to query. /// A cancellation token to cancel the operation. /// The volume id of the item. - public static async Task<(long, string)> ProbeForMatchingFilename(string filename, LocalRecreateDatabase restoredb, CancellationToken cancellationToken) + public static async Task<(long, string)> ProbeForMatchingFilenameAsync(string filename, LocalRecreateDatabase restoredb, CancellationToken cancellationToken) { var p = VolumeBase.ParseFilename(filename); if (p != null) @@ -1016,7 +1016,7 @@ namespace Duplicati.Library.Main.Operation { var testfilename = VolumeBase.GenerateFilename(p.FileType, p.Prefix, p.Guid, p.Time, compmodule, encmodule); var tvid = await restoredb - .GetRemoteVolumeID(testfilename, cancellationToken) + .GetRemoteVolumeIDAsync(testfilename, cancellationToken) .ConfigureAwait(false); if (tvid >= 0) diff --git a/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs b/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs index 12b1c5546..12ed23780 100644 --- a/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs +++ b/Duplicati/Library/Main/Operation/RemoteSynchronization/RemoteSynchronizationRunner.cs @@ -87,7 +87,7 @@ public static class RemoteSynchronizationRunner { var config_with_args = config with { Dst = backend_dst, Src = backend_src }; - return Run(config_with_args, token); + return RunAsync(config_with_args, token); }); return await root_cmd.InvokeAsync(args).ConfigureAwait(false); @@ -101,7 +101,7 @@ public static class RemoteSynchronizationRunner /// Optional progress updater for reporting file count and transfer progress to the UI. /// Optional backend progress updater for reporting transfer speed to the UI. /// The return code (0 on success). - internal static async Task Run(RemoteSynchronizationConfig config, CancellationToken token, IOperationProgressUpdater? progressUpdater = null, IBackendProgressUpdater? backendProgressUpdater = null, IBasicResults? results = null) + internal static async Task RunAsync(RemoteSynchronizationConfig config, CancellationToken token, IOperationProgressUpdater? progressUpdater = null, IBackendProgressUpdater? backendProgressUpdater = null, IBasicResults? results = null) { // Parse the log level var log_level_parsed = Enum.TryParse(config.LogLevel, true, out var log_level_enum); @@ -131,7 +131,7 @@ public static class RemoteSynchronizationRunner // Start the logging scope with both file logging and message capture using var scope = Duplicati.Library.Logging.Log.StartScope(multi_log_target.WriteMessage); - return await RunCore(config, token, progressUpdater, backendProgressUpdater, rsyncResults).ConfigureAwait(false); + return await RunCoreAsync(config, token, progressUpdater, backendProgressUpdater, rsyncResults).ConfigureAwait(false); } else { @@ -140,7 +140,7 @@ public static class RemoteSynchronizationRunner // Start the logging scope using var _ = Duplicati.Library.Logging.Log.StartScope(multi_sink, log_level_enum); - return await RunCore(config, token, progressUpdater, backendProgressUpdater).ConfigureAwait(false); + return await RunCoreAsync(config, token, progressUpdater, backendProgressUpdater).ConfigureAwait(false); } } else @@ -151,11 +151,11 @@ public static class RemoteSynchronizationRunner using var log_target = new ControllerMultiLogTarget(rsyncResults, log_level_enum, null, null); using var scope = Duplicati.Library.Logging.Log.StartScope(log_target.WriteMessage); - return await RunCore(config, token, progressUpdater, backendProgressUpdater, rsyncResults).ConfigureAwait(false); + return await RunCoreAsync(config, token, progressUpdater, backendProgressUpdater, rsyncResults).ConfigureAwait(false); } else { - return await RunCore(config, token, progressUpdater, backendProgressUpdater).ConfigureAwait(false); + return await RunCoreAsync(config, token, progressUpdater, backendProgressUpdater).ConfigureAwait(false); } } } @@ -168,7 +168,7 @@ public static class RemoteSynchronizationRunner /// Optional progress updater for reporting file count and transfer progress to the UI. /// Optional backend progress updater for reporting transfer speed to the UI. /// The return code (0 on success). - private static async Task RunCore(RemoteSynchronizationConfig config, CancellationToken token, IOperationProgressUpdater? progressUpdater = null, IBackendProgressUpdater? backendProgressUpdater = null, RemoteSynchronizationResults? results = null) + private static async Task RunCoreAsync(RemoteSynchronizationConfig config, CancellationToken token, IOperationProgressUpdater? progressUpdater = null, IBackendProgressUpdater? backendProgressUpdater = null, RemoteSynchronizationResults? results = null) { // Unpack and parse the multi token options var global_options = ParseOptions(config.GlobalOptions); @@ -196,7 +196,7 @@ public static class RemoteSynchronizationRunner using var b2m = new LightWeightBackendManager(config.Dst, dst_opts, config.BackendRetries, config.BackendRetryDelay, config.BackendRetryWithExponentialBackoff, progressUpdater: progressUpdater, backendProgressUpdater: backendProgressUpdater); // Prepare the operations - var (to_copy, to_delete, to_verify) = await PrepareFileLists(b1m, b2m, config, token).ConfigureAwait(false); + var (to_copy, to_delete, to_verify) = await PrepareFileListsAsync(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. @@ -684,7 +684,7 @@ public static class RemoteSynchronizationRunner /// The parsed configuration for the tool. /// The cancellation token to use for the asynchronous operations. /// 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, RemoteSynchronizationConfig config, CancellationToken token) + private static async Task<(IEnumerable, IEnumerable, IEnumerable)> PrepareFileListsAsync(LightWeightBackendManager b_src, LightWeightBackendManager b_dst, RemoteSynchronizationConfig config, CancellationToken token) { IEnumerable files_src, files_dst; diff --git a/Duplicati/Library/Main/Operation/RemoteSynchronizationHandler.cs b/Duplicati/Library/Main/Operation/RemoteSynchronizationHandler.cs index 3b9965c97..aed7502e5 100644 --- a/Duplicati/Library/Main/Operation/RemoteSynchronizationHandler.cs +++ b/Duplicati/Library/Main/Operation/RemoteSynchronizationHandler.cs @@ -332,7 +332,7 @@ internal class RemoteSynchronizationHandler : IDisposable { Destination = Duplicati.Library.Utility.Utility.GetUrlWithoutCredentials(config.Dst), }; - var exitCode = RemoteSynchronizationRunner.Run(config, CancellationToken.None, m_results.OperationProgressUpdater, m_results.BackendProgressUpdater, syncResult).ConfigureAwait(false).GetAwaiter().GetResult(); + var exitCode = RemoteSynchronizationRunner.RunAsync(config, CancellationToken.None, m_results.OperationProgressUpdater, m_results.BackendProgressUpdater, syncResult).ConfigureAwait(false).GetAwaiter().GetResult(); var date_end = DateTime.UtcNow; syncResult.BeginTime = date_start; syncResult.EndTime = date_end; diff --git a/Duplicati/Library/Main/Operation/RepairHandler.cs b/Duplicati/Library/Main/Operation/RepairHandler.cs index ba4206adc..9e6c80a59 100644 --- a/Duplicati/Library/Main/Operation/RepairHandler.cs +++ b/Duplicati/Library/Main/Operation/RepairHandler.cs @@ -57,7 +57,7 @@ namespace Duplicati.Library.Main.Operation if (!File.Exists(m_options.Dbpath)) { await RunRepairLocalAsync(backendManager, filter).ConfigureAwait(false); - await RunRepairCommon().ConfigureAwait(false); + await RunRepairCommonAsync().ConfigureAwait(false); // Optionally refresh lock information from the backend if (m_options.RepairRefreshLockInfo && backendManager.SupportsObjectLocking) @@ -71,10 +71,10 @@ namespace Duplicati.Library.Main.Operation try { await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, null, m_result.TaskControl.ProgressToken) + await LocalRepairDatabase.CreateRepairDatabaseAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); knownRemotes = await db - .GetRemoteVolumes(m_result.TaskControl.ProgressToken) + .GetRemoteVolumesAsync(m_result.TaskControl.ProgressToken) .CountAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -101,12 +101,12 @@ namespace Duplicati.Library.Main.Operation } await RunRepairLocalAsync(backendManager, filter).ConfigureAwait(false); - await RunRepairCommon().ConfigureAwait(false); + await RunRepairCommonAsync().ConfigureAwait(false); } else { - await RunRepairCommon().ConfigureAwait(false); - await RunRepairBrokenFilesets(backendManager).ConfigureAwait(false); + await RunRepairCommonAsync().ConfigureAwait(false); + await RunRepairBrokenFilesetsAsync(backendManager).ConfigureAwait(false); await RunRepairRemoteAsync(backendManager, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } @@ -143,33 +143,33 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0); await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, null, cancellationToken) + await LocalRepairDatabase.CreateRepairDatabaseAsync(m_options.Dbpath, null, cancellationToken) .ConfigureAwait(false); try { - await Utility.UpdateOptionsFromDb(db, m_options, cancellationToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, cancellationToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, cancellationToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, cancellationToken) .ConfigureAwait(false); - if (await db.PartiallyRecreated(cancellationToken).ConfigureAwait(false)) + if (await db.PartiallyRecreatedAsync(cancellationToken).ConfigureAwait(false)) throw new UserInformationException("The database was only partially recreated. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsPartiallyRecreated"); - if (await db.RepairInProgress(cancellationToken).ConfigureAwait(false)) + if (await db.RepairInProgressAsync(cancellationToken).ConfigureAwait(false)) throw new UserInformationException("The database was attempted repaired, but the repair did not complete. This database may be incomplete and the repair process is not allowed to alter remote files as that could result in data loss.", "DatabaseIsInRepairState"); // Ensure the database is consistent before we start fixing the remote await db - .VerifyConsistencyForRepair(m_options.Blocksize, m_options.BlockhashSize, true, cancellationToken) + .VerifyConsistencyForRepairAsync(m_options.Blocksize, m_options.BlockhashSize, true, cancellationToken) .ConfigureAwait(false); // If the last backup failed, guard the incomplete fileset, so we can create a synthetic filelist var lastTempFilelist = await db - .GetLastIncompleteFilesetVolume(cancellationToken) + .GetLastIncompleteFilesetVolumeAsync(cancellationToken) .ConfigureAwait(false); - var tp = await FilelistProcessor.RemoteListAnalysis(backendManager, m_options, db, m_result.BackendWriter, [lastTempFilelist.Name], null, FilelistProcessor.VerifyMode.VerifyAndCleanForced, cancellationToken).ConfigureAwait(false); + var tp = await FilelistProcessor.RemoteListAnalysisAsync(backendManager, m_options, db, m_result.BackendWriter, [lastTempFilelist.Name], null, FilelistProcessor.VerifyMode.VerifyAndCleanForced, cancellationToken).ConfigureAwait(false); await db.Transaction .CommitAsync("CommitRemoteListAnalysisTransaction", token: cancellationToken) .ConfigureAwait(false); @@ -178,15 +178,15 @@ namespace Duplicati.Library.Main.Operation var hashsize = m_options.BlockhashSize; var missingRemoteFilesets = await db - .MissingRemoteFilesets(cancellationToken) + .MissingRemoteFilesetsAsync(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); var missingLocalFilesets = await db - .MissingLocalFilesets(cancellationToken) + .MissingLocalFilesetsAsync(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); var emptyIndexFiles = await db - .EmptyIndexFiles(cancellationToken) + .EmptyIndexFilesAsync(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -201,10 +201,10 @@ namespace Duplicati.Library.Main.Operation // Find the most recent timestamp from either a fileset or a remote volume var mostRecentLocal = await db - .GetRemoteVolumes(cancellationToken) + .GetRemoteVolumesAsync(cancellationToken) .Where(x => x.Type == RemoteVolumeType.Files) .Select(x => VolumeBase.ParseFilename(x.Name).Time.ToLocalTime()) - .Concat(db.FilesetTimes(cancellationToken).Select(x => x.Value.ToLocalTime())) + .Concat(db.FilesetTimesAsync(cancellationToken).Select(x => x.Value.ToLocalTime())) .Append(DateTime.MinValue) .MaxAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -246,7 +246,7 @@ namespace Duplicati.Library.Main.Operation foreach (var n in tp.VerificationRequiredVolumes) try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(testdb, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) @@ -267,7 +267,7 @@ namespace Duplicati.Library.Main.Operation var (tf, hash, size) = await backendManager.GetWithInfoAsync(n.Name, n.Hash, n.Size, cancellationToken).ConfigureAwait(false); using (tf) res = - await TestHandler.TestVolumeInternals(testdb, n, tf, m_options, 1, cancellationToken) + await TestHandler.TestVolumeInternalsAsync(testdb, n, tf, m_options, 1, cancellationToken) .ConfigureAwait(false); if (res.Value.Any()) @@ -277,7 +277,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "CapturedRemoteFileHash", "Sucessfully captured hash for {0}, updating database", n.Name); await db - .UpdateRemoteVolume(n.Name, RemoteVolumeState.Verified, size, hash, cancellationToken) + .UpdateRemoteVolumeAsync(n.Name, RemoteVolumeState.Verified, size, hash, cancellationToken) .ConfigureAwait(false); } @@ -298,7 +298,7 @@ namespace Duplicati.Library.Main.Operation foreach (var n in tp.ExtraVolumes) try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) @@ -328,7 +328,7 @@ namespace Duplicati.Library.Main.Operation foreach (var rv in ifr.Volumes) { var entry = await db - .GetRemoteVolume(rv.Filename, cancellationToken) + .GetRemoteVolumeAsync(rv.Filename, cancellationToken) .ConfigureAwait(false); if (entry.ID < 0) throw new Exception(string.Format("Unknown remote file {0} detected", rv.Filename)); @@ -340,31 +340,31 @@ namespace Duplicati.Library.Main.Operation throw new Exception(string.Format("Volume {0} hash/size mismatch ({1} - {2}) vs ({3} - {4})", rv.Filename, entry.Hash, entry.Size, rv.Hash, rv.Length)); await db - .CheckAllBlocksAreInVolume(rv.Filename, rv.Blocks, cancellationToken) + .CheckAllBlocksAreInVolumeAsync(rv.Filename, rv.Blocks, cancellationToken) .ConfigureAwait(false); } var blocksize = m_options.Blocksize; foreach (var ixb in ifr.BlockLists) await db - .CheckBlocklistCorrect(ixb.Hash, ixb.Length, ixb.Blocklist, blocksize, hashsize, cancellationToken) + .CheckBlocklistCorrectAsync(ixb.Hash, ixb.Length, ixb.Blocklist, blocksize, hashsize, cancellationToken) .ConfigureAwait(false); // Register the new index file and link it to the block files var selfid = await db - .RegisterRemoteVolume(n.File.Name, RemoteVolumeType.Index, RemoteVolumeState.Uploading, size, new TimeSpan(0), cancellationToken) + .RegisterRemoteVolumeAsync(n.File.Name, RemoteVolumeType.Index, RemoteVolumeState.Uploading, size, new TimeSpan(0), cancellationToken) .ConfigureAwait(false); foreach (var rv in ifr.Volumes) { // Guard against unknown block files long id = await db - .GetRemoteVolumeID(rv.Filename, cancellationToken) + .GetRemoteVolumeIDAsync(rv.Filename, cancellationToken) .ConfigureAwait(false); if (id == -1) Logging.Log.WriteWarningMessage(LOGTAG, "UnknownBlockFile", null, "Index file {0} references unknown block file: {1}", n.File.Name, rv.Filename); else await db - .AddIndexBlockLink(selfid, id, cancellationToken) + .AddIndexBlockLinkAsync(selfid, id, cancellationToken) .ConfigureAwait(false); } @@ -377,7 +377,7 @@ namespace Duplicati.Library.Main.Operation // All checks fine, we accept the new index file Logging.Log.WriteInformationMessage(LOGTAG, "AcceptNewIndexFile", "Accepting new index file {0}", n.File.Name); await db - .UpdateRemoteVolume(n.File.Name, RemoteVolumeState.Verified, size, hash, cancellationToken) + .UpdateRemoteVolumeAsync(n.File.Name, RemoteVolumeState.Verified, size, hash, cancellationToken) .ConfigureAwait(false); continue; @@ -391,7 +391,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) { await db - .RegisterRemoteVolume(n.File.Name, n.FileType, n.File.Size, RemoteVolumeState.Deleting, cancellationToken) + .RegisterRemoteVolumeAsync(n.File.Name, n.FileType, n.File.Size, RemoteVolumeState.Deleting, cancellationToken) .ConfigureAwait(false); await backendManager.DeleteAsync(n.File.Name, n.File.Size, false, cancellationToken).ConfigureAwait(false); } @@ -412,7 +412,7 @@ namespace Duplicati.Library.Main.Operation var anyDlistUploads = false; foreach (var (filesetId, timestamp, isfull) in missingRemoteFilesets) { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) @@ -429,7 +429,7 @@ namespace Duplicati.Library.Main.Operation progress++; m_result.OperationProgressUpdater.UpdateProgress((float)progress / targetProgess); var fileTime = - await FilesetVolumeWriter.ProbeUnusedFilenameName(db, m_options, timestamp, cancellationToken) + await FilesetVolumeWriter.ProbeUnusedFilenameNameAsync(db, m_options, timestamp, cancellationToken) .ConfigureAwait(false); var fsw = new FilesetVolumeWriter(m_options, fileTime); @@ -441,17 +441,17 @@ namespace Duplicati.Library.Main.Operation fsw.CreateFilesetFile(isfull); await db - .WriteFileset(fsw, filesetId, cancellationToken) + .WriteFilesetAsync(fsw, filesetId, cancellationToken) .ConfigureAwait(false); fsw.Close(); fsw.VolumeID = await db - .RegisterRemoteVolume(fsw.RemoteFilename, RemoteVolumeType.Files, -1, RemoteVolumeState.Temporary, cancellationToken) + .RegisterRemoteVolumeAsync(fsw.RemoteFilename, RemoteVolumeType.Files, -1, RemoteVolumeState.Temporary, cancellationToken) .ConfigureAwait(false); await db - .LinkFilesetToVolume(filesetId, fsw.VolumeID, cancellationToken) + .LinkFilesetToVolumeAsync(filesetId, fsw.VolumeID, cancellationToken) .ConfigureAwait(false); if (m_options.Dryrun) @@ -476,7 +476,7 @@ namespace Duplicati.Library.Main.Operation foreach (var volumename in missingLocalFilesets) { var remoteVolume = await db - .GetRemoteVolume(volumename, cancellationToken) + .GetRemoteVolumeAsync(volumename, cancellationToken) .ConfigureAwait(false); using var tmpfile = await backendManager.GetAsync(remoteVolume.Name, remoteVolume.Hash, remoteVolume.Size, cancellationToken).ConfigureAwait(false); var parsed = VolumeBase.ParseFilename(remoteVolume.Name); @@ -487,10 +487,10 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(string.Format("Failed to load compression module: {0}", parsed.CompressionModule), "FailedToLoadCompressionModule"); var filesetid = await db - .CreateFileset(remoteVolume.ID, parsed.Time, cancellationToken) + .CreateFilesetAsync(remoteVolume.ID, parsed.Time, cancellationToken) .ConfigureAwait(false); - await RecreateDatabaseHandler.RecreateFilesetFromRemoteList(recreatedb, compressor, filesetid, m_options, new FilterExpression(), cancellationToken) + await RecreateDatabaseHandler.RecreateFilesetFromRemoteListAsync(recreatedb, compressor, filesetid, m_options, new FilterExpression(), cancellationToken) .ConfigureAwait(false); if (!m_options.Dryrun) @@ -501,7 +501,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun && tp.MissingVolumes.Any()) await db - .TerminatedWithActiveUploads(cancellationToken, true) + .TerminatedWithActiveUploadsAsync(cancellationToken, true) .ConfigureAwait(false); if (tp.MissingVolumes.Any(x => x.Type != RemoteVolumeType.Index && x.Type != RemoteVolumeType.Files && x.Type != RemoteVolumeType.Blocks)) @@ -517,7 +517,7 @@ namespace Duplicati.Library.Main.Operation } // Blocks are recreated with the entire list of missing files to handle cases where partial recreation is needed - await RunRepairDblocks(backendManager, db, missingDblocks, incrementProgress, cancellationToken).ConfigureAwait(false); + await RunRepairDblocksAsync(backendManager, db, missingDblocks, incrementProgress, cancellationToken).ConfigureAwait(false); // Filesets are recreated one at a time foreach (var n in tp.MissingVolumes.Where(x => x.Type == RemoteVolumeType.Files)) @@ -529,11 +529,11 @@ namespace Duplicati.Library.Main.Operation { var timestamp = VolumeBase.ParseFilename(n.Name).Time; var fileTime = - await FilesetVolumeWriter.ProbeUnusedFilenameName(db, m_options, timestamp, cancellationToken) + await FilesetVolumeWriter.ProbeUnusedFilenameNameAsync(db, m_options, timestamp, cancellationToken) .ConfigureAwait(false); var volumeWriter = newEntry = new FilesetVolumeWriter(m_options, fileTime); - await RunRepairDlist(backendManager, db, volumeWriter, n, fileTime, cancellationToken).ConfigureAwait(false); + await RunRepairDlistAsync(backendManager, db, volumeWriter, n, fileTime, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -557,12 +557,12 @@ namespace Duplicati.Library.Main.Operation try { // Check if the index file has already been deleted, beause the dblock was recreated - var currentState = (await db.GetRemoteVolume(n.Name, cancellationToken).ConfigureAwait(false)).State; + var currentState = (await db.GetRemoteVolumeAsync(n.Name, cancellationToken).ConfigureAwait(false)).State; if (currentState == RemoteVolumeState.Deleted || currentState == RemoteVolumeState.Temporary || currentState == RemoteVolumeState.Deleting) continue; var w = newEntry = new IndexVolumeWriter(m_options); - await RunRepairDindex(backendManager, db, w, n, m_options, cancellationToken).ConfigureAwait(false); + await RunRepairDindexAsync(backendManager, db, w, n, m_options, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -581,7 +581,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); // Implicit rollback @@ -637,7 +637,7 @@ namespace Duplicati.Library.Main.Operation if (!m_options.Dryrun) { await db - .TerminatedWithActiveUploads(cancellationToken, false) + .TerminatedWithActiveUploadsAsync(cancellationToken, false) .ConfigureAwait(false); await db.Transaction .CommitAsync("CommitRepairTransaction", true, cancellationToken) @@ -668,13 +668,13 @@ namespace Duplicati.Library.Main.Operation /// The time of the new fileset to create. /// Cancellation token to cancel the operation. /// A task representing the asynchronous operation. - private async Task RunRepairDlist(IBackendManager backendManager, LocalRepairDatabase db, FilesetVolumeWriter volumeWriter, RemoteVolumeEntry originalVolume, DateTime filesetTime, CancellationToken cancellationToken) + private async Task RunRepairDlistAsync(IBackendManager backendManager, LocalRepairDatabase db, FilesetVolumeWriter volumeWriter, RemoteVolumeEntry originalVolume, DateTime filesetTime, CancellationToken cancellationToken) { volumeWriter.VolumeID = await db - .RegisterRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) + .RegisterRemoteVolumeAsync(volumeWriter.RemoteFilename, RemoteVolumeType.Files, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); (var prevFilesetId, var _, var isPrevFull) = await db - .GetFilesetFromRemotename(originalVolume.Name, cancellationToken) + .GetFilesetFromRemotenameAsync(originalVolume.Name, cancellationToken) .ConfigureAwait(false); if (!string.IsNullOrEmpty(m_options.ControlFiles)) @@ -683,28 +683,28 @@ namespace Duplicati.Library.Main.Operation volumeWriter.CreateFilesetFile(isPrevFull); var newFilesetID = await db - .CreateFileset(volumeWriter.VolumeID, filesetTime, cancellationToken) + .CreateFilesetAsync(volumeWriter.VolumeID, filesetTime, cancellationToken) .ConfigureAwait(false); await db - .UpdateFullBackupStateInFileset(newFilesetID, isPrevFull, cancellationToken) + .UpdateFullBackupStateInFilesetAsync(newFilesetID, isPrevFull, cancellationToken) .ConfigureAwait(false); await db - .LinkFilesetToVolume(newFilesetID, volumeWriter.VolumeID, cancellationToken) + .LinkFilesetToVolumeAsync(newFilesetID, volumeWriter.VolumeID, cancellationToken) .ConfigureAwait(false); await db - .MoveFilesFromFileset(newFilesetID, prevFilesetId, cancellationToken) + .MoveFilesFromFilesetAsync(newFilesetID, prevFilesetId, cancellationToken) .ConfigureAwait(false); await db - .WriteFileset(volumeWriter, newFilesetID, cancellationToken) + .WriteFilesetAsync(volumeWriter, newFilesetID, cancellationToken) .ConfigureAwait(false); volumeWriter.Close(); await db - .UpdateRemoteVolume(volumeWriter.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) + .UpdateRemoteVolumeAsync(volumeWriter.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) .ConfigureAwait(false); await db - .UpdateRemoteVolume(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) + .UpdateRemoteVolumeAsync(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); await backendManager.FlushPendingMessagesAsync(db, cancellationToken).ConfigureAwait(false); @@ -735,29 +735,29 @@ namespace Duplicati.Library.Main.Operation /// The options for the current operation. /// Cancellation token to cancel the operation. /// A task representing the asynchronous operation. - public static async Task RunRepairDindex(IBackendManager backendManager, LocalRepairDatabase db, IndexVolumeWriter indexWriter, IRemoteVolume originalVolume, Options options, CancellationToken cancellationToken) + public static async Task RunRepairDindexAsync(IBackendManager backendManager, LocalRepairDatabase db, IndexVolumeWriter indexWriter, IRemoteVolume originalVolume, Options options, CancellationToken cancellationToken) { indexWriter.VolumeID = await db - .RegisterRemoteVolume(indexWriter.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Uploading, -1, TimeSpan.Zero, cancellationToken) + .RegisterRemoteVolumeAsync(indexWriter.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Uploading, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); var blockvolumeids = new List(); using var h = HashFactory.CreateHasher(options.BlockHashAlgorithm); - await foreach (var blockvolume in db.GetBlockVolumesFromIndexName(originalVolume.Name, cancellationToken).ConfigureAwait(false)) + await foreach (var blockvolume in db.GetBlockVolumesFromIndexNameAsync(originalVolume.Name, cancellationToken).ConfigureAwait(false)) { indexWriter.StartVolume(blockvolume.Name); var volumeid = await db - .GetRemoteVolumeID(blockvolume.Name, cancellationToken) + .GetRemoteVolumeIDAsync(blockvolume.Name, cancellationToken) .ConfigureAwait(false); - await foreach (var b in db.GetBlocks(volumeid, cancellationToken).ConfigureAwait(false)) + await foreach (var b in db.GetBlocksAsync(volumeid, cancellationToken).ConfigureAwait(false)) indexWriter.AddBlock(b.Hash, b.Size); indexWriter.FinishVolume(blockvolume.Hash, blockvolume.Size); blockvolumeids.Add(volumeid); if (options.IndexfilePolicy == Options.IndexFileStrategy.Full) - await foreach (var b in db.GetBlocklists(volumeid, options.Blocksize, options.BlockhashSize, cancellationToken).ConfigureAwait(false)) + await foreach (var b in db.GetBlocklistsAsync(volumeid, options.Blocksize, options.BlockhashSize, cancellationToken).ConfigureAwait(false)) { var bh = Convert.ToBase64String(h.ComputeHash(b.Buffer, 0, b.Size)); if (bh != b.Hash) @@ -769,7 +769,7 @@ namespace Duplicati.Library.Main.Operation foreach (var blockvolumeid in blockvolumeids) await db - .AddIndexBlockLink(indexWriter.VolumeID, blockvolumeid, cancellationToken) + .AddIndexBlockLinkAsync(indexWriter.VolumeID, blockvolumeid, cancellationToken) .ConfigureAwait(false); indexWriter.Close(); @@ -779,7 +779,7 @@ namespace Duplicati.Library.Main.Operation else { await db - .UpdateRemoteVolume(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) + .UpdateRemoteVolumeAsync(originalVolume.Name, RemoteVolumeState.Deleted, originalVolume.Size, originalVolume.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); await backendManager.FlushPendingMessagesAsync(db, cancellationToken).ConfigureAwait(false); await backendManager.PutAsync(indexWriter, null, null, false, null, cancellationToken).ConfigureAwait(false); @@ -850,7 +850,7 @@ namespace Duplicati.Library.Main.Operation { var ipdv = new InProgressDblockVolumes(options, db); ipdv.m_activeWriter.VolumeID = await db - .RegisterRemoteVolume(ipdv.m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) + .RegisterRemoteVolumeAsync(ipdv.m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); return ipdv; } @@ -891,14 +891,14 @@ namespace Duplicati.Library.Main.Operation /// The compression hint for the block. /// Cancellation token to cancel the operation. /// A task that completes when the block is added. - public async Task AddBlock(string hash, byte[] buffer, int offset, int size, CompressionHint hint, CancellationToken cancellationToken) + public async Task AddBlockAsync(string hash, byte[] buffer, int offset, int size, CompressionHint hint, CancellationToken cancellationToken) { await m_activeWriter - .AddBlock(hash, buffer, offset, size, hint) + .AddBlockAsync(hash, buffer, offset, size, hint) .ConfigureAwait(false); m_anyData = true; if (IsFull) - await StartNewVolume(cancellationToken).ConfigureAwait(false); + await StartNewVolumeAsync(cancellationToken).ConfigureAwait(false); } /// @@ -906,7 +906,7 @@ namespace Duplicati.Library.Main.Operation /// /// Cancellation token to cancel the operation. /// A task that completes when the new volume is started. - public async Task StartNewVolume(CancellationToken cancellationToken) + public async Task StartNewVolumeAsync(CancellationToken cancellationToken) { if (!m_anyData) return; @@ -915,7 +915,7 @@ namespace Duplicati.Library.Main.Operation m_activeWriter = new BlockVolumeWriter(m_options); m_activeWriter.VolumeID = await m_database - .RegisterRemoteVolume(m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) + .RegisterRemoteVolumeAsync(m_activeWriter.RemoteFilename, RemoteVolumeType.Blocks, RemoteVolumeState.Temporary, -1, TimeSpan.Zero, cancellationToken) .ConfigureAwait(false); m_anyData = false; } @@ -930,7 +930,7 @@ namespace Duplicati.Library.Main.Operation /// /// Cancellation token to cancel the operation. /// >A task that completes when the repair is finished. - private async Task RunRepairDblocks(IBackendManager backendManager, LocalRepairDatabase db, IEnumerable missingDblockFiles, Action incrementProgress, CancellationToken cancellationToken) + private async Task RunRepairDblocksAsync(IBackendManager backendManager, LocalRepairDatabase db, IEnumerable missingDblockFiles, Action incrementProgress, CancellationToken cancellationToken) { var currentVolume = await InProgressDblockVolumes.CreateAsync(m_options, db, cancellationToken) @@ -940,7 +940,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, cancellationToken).ConfigureAwait(false); if (!m_options.Dryrun) @@ -952,9 +952,9 @@ namespace Duplicati.Library.Main.Operation incrementProgress(); - await RunRepairDblock(backendManager, db, n, currentVolume, cancellationToken).ConfigureAwait(false); + await RunRepairDblockAsync(backendManager, db, n, currentVolume, cancellationToken).ConfigureAwait(false); if (currentVolume.CompletedWriters.Count > 0) - await UploadCompletedBlockVolumes(backendManager, currentVolume, db, cancellationToken).ConfigureAwait(false); + await UploadCompletedBlockVolumesAsync(backendManager, currentVolume, db, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -966,13 +966,13 @@ namespace Duplicati.Library.Main.Operation // If we have any data in the volume, complete it so it will be uploaded if (currentVolume.AnyData) - await currentVolume.StartNewVolume(cancellationToken).ConfigureAwait(false); + await currentVolume.StartNewVolumeAsync(cancellationToken).ConfigureAwait(false); // Complete any pending uploads - await UploadCompletedBlockVolumes(backendManager, currentVolume, db, cancellationToken).ConfigureAwait(false); + await UploadCompletedBlockVolumesAsync(backendManager, currentVolume, db, cancellationToken).ConfigureAwait(false); // After reset, we need to remove the temporary volume from the database await db - .RemoveRemoteVolume(currentVolume.RemoteFilename, cancellationToken) + .RemoveRemoteVolumeAsync(currentVolume.RemoteFilename, cancellationToken) .ConfigureAwait(false); } @@ -984,12 +984,12 @@ namespace Duplicati.Library.Main.Operation /// The database to use for the upload. /// Cancellation token to cancel the operation. /// >A task that completes when the upload is finished. - async Task UploadCompletedBlockVolumes(IBackendManager backendManager, InProgressDblockVolumes activeVolume, LocalRepairDatabase db, CancellationToken cancellationToken) + async Task UploadCompletedBlockVolumesAsync(IBackendManager backendManager, InProgressDblockVolumes activeVolume, LocalRepairDatabase db, CancellationToken cancellationToken) { foreach (var completedVolume in activeVolume.CompletedWriters) { await db - .UpdateRemoteVolume(completedVolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) + .UpdateRemoteVolumeAsync(completedVolume.RemoteFilename, RemoteVolumeState.Uploading, -1, null, cancellationToken) .ConfigureAwait(false); // Create a new index file that points to the new volume @@ -999,19 +999,19 @@ namespace Duplicati.Library.Main.Operation { newvolindex = new IndexVolumeWriter(m_options); newvolindex.VolumeID = await db - .RegisterRemoteVolume(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, cancellationToken) + .RegisterRemoteVolumeAsync(newvolindex.RemoteFilename, RemoteVolumeType.Index, RemoteVolumeState.Temporary, cancellationToken) .ConfigureAwait(false); newvolindex.StartVolume(completedVolume.RemoteFilename); - await foreach (var b in db.GetBlocks(completedVolume.VolumeID, cancellationToken).ConfigureAwait(false)) + await foreach (var b in db.GetBlocksAsync(completedVolume.VolumeID, cancellationToken).ConfigureAwait(false)) newvolindex.AddBlock(b.Hash, b.Size); await db - .AddIndexBlockLink(newvolindex.VolumeID, completedVolume.VolumeID, cancellationToken) + .AddIndexBlockLinkAsync(newvolindex.VolumeID, completedVolume.VolumeID, cancellationToken) .ConfigureAwait(false); if (m_options.IndexfilePolicy == Options.IndexFileStrategy.Full) indexVolumeFinished = async () => { - await foreach (var blocklist in db.GetBlocklists(completedVolume.VolumeID, m_options.Blocksize, m_options.BlockhashSize, cancellationToken).ConfigureAwait(false)) + await foreach (var blocklist in db.GetBlocklistsAsync(completedVolume.VolumeID, m_options.Blocksize, m_options.BlockhashSize, cancellationToken).ConfigureAwait(false)) newvolindex.WriteBlocklist(blocklist.Item1, blocklist.Item2, 0, blocklist.Item3); }; } @@ -1035,7 +1035,7 @@ namespace Duplicati.Library.Main.Operation // Prepare for deleting the old stuff foreach (var vol in activeVolume.VolumesToDelete) await db - .UpdateRemoteVolume(vol.Name, RemoteVolumeState.Deleting, vol.Size, vol.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) + .UpdateRemoteVolumeAsync(vol.Name, RemoteVolumeState.Deleting, vol.Size, vol.Hash, false, TimeSpan.FromHours(2), null, cancellationToken) .ConfigureAwait(false); // Persist desired state prior to deleting the old files @@ -1071,24 +1071,24 @@ namespace Duplicati.Library.Main.Operation /// The in-progress dblock volume to use for the recreate. /// Cancellation token to cancel the operation. /// >A task that completes when the repair is finished. - private async Task RunRepairDblock(IBackendManager backendManager, LocalRepairDatabase db, RemoteVolumeEntry originalVolume, InProgressDblockVolumes pendingVolume, CancellationToken cancellationToken) + private async Task RunRepairDblockAsync(IBackendManager backendManager, LocalRepairDatabase db, RemoteVolumeEntry originalVolume, InProgressDblockVolumes pendingVolume, CancellationToken cancellationToken) { // The dblock files are the most complex to recreate // as data can be either file contents, metadata or blocklist hashes // We attempt to recover all three source parts in the steps below await using var mbl = await db - .CreateBlockList(originalVolume.Name, cancellationToken) + .CreateBlockListAsync(originalVolume.Name, cancellationToken) .ConfigureAwait(false); var originalMissingBlockCount = await mbl - .GetMissingBlockCount(cancellationToken) + .GetMissingBlockCountAsync(cancellationToken) .ConfigureAwait(false); var recoveredSourceBlocks = 0L; // First we grab all known blocks from local files string lastRestoredHash = null; long lastRestoredSize = -1; - await foreach (var block in mbl.GetSourceFilesWithBlocks(m_options.Blocksize, cancellationToken).ConfigureAwait(false)) + await foreach (var block in mbl.GetSourceFilesWithBlocksAsync(m_options.Blocksize, cancellationToken).ConfigureAwait(false)) { if (block.Hash == lastRestoredHash && block.Size == lastRestoredSize) continue; @@ -1124,11 +1124,11 @@ namespace Duplicati.Library.Main.Operation // Found it, no need to look again lastRestoredHash = block.Hash; lastRestoredSize = block.Size; - if (await mbl.SetBlockRestored(block.Hash, block.Size, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) + if (await mbl.SetBlockRestoredAsync(block.Hash, block.Size, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) { recoveredSourceBlocks++; await pendingVolume - .AddBlock(block.Hash, buffer, 0, size, CompressionHint.Default, cancellationToken) + .AddBlockAsync(block.Hash, buffer, 0, size, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } } @@ -1139,7 +1139,7 @@ namespace Duplicati.Library.Main.Operation } // Then grab all blocks with metadata - await foreach (var block in mbl.GetSourceItemsWithMetadataBlocks(cancellationToken).ConfigureAwait(false)) + await foreach (var block in mbl.GetSourceItemsWithMetadataBlocksAsync(cancellationToken).ConfigureAwait(false)) { if (block.Hash == lastRestoredHash && block.Size == lastRestoredSize) continue; @@ -1164,7 +1164,7 @@ namespace Duplicati.Library.Main.Operation continue; } - var metadata = await MetadataGenerator.GenerateMetadata(entry, entry.Attributes, m_options, cancellationToken); + var metadata = await MetadataGenerator.GenerateMetadataAsync(entry, entry.Attributes, m_options, cancellationToken); var metahash = Utility.WrapMetadata(metadata, m_options); if (metahash.FileHash != block.Hash || metahash.Blob.Length != size) { @@ -1175,11 +1175,11 @@ namespace Duplicati.Library.Main.Operation // Found it, no need to look again lastRestoredHash = block.Hash; lastRestoredSize = block.Size; - if (await mbl.SetBlockRestored(block.Hash, block.Size, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) + if (await mbl.SetBlockRestoredAsync(block.Hash, block.Size, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) { recoveredSourceBlocks++; await pendingVolume - .AddBlock(block.Hash, metahash.Blob, 0, metahash.Blob.Length, CompressionHint.Default, cancellationToken) + .AddBlockAsync(block.Hash, metahash.Blob, 0, metahash.Blob.Length, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } } @@ -1214,9 +1214,9 @@ namespace Duplicati.Library.Main.Operation if (resulthash == lastBlocklist.BlocklistHash && targetsize == blocklistHash.Length) { - if (await mbl.SetBlockRestored(resulthash, blocklistHash.Length, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) + if (await mbl.SetBlockRestoredAsync(resulthash, blocklistHash.Length, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) await pendingVolume - .AddBlock(resulthash, blocklistHash.ToArray(), 0, (int)blocklistHash.Length, CompressionHint.Default, cancellationToken) + .AddBlockAsync(resulthash, blocklistHash.ToArray(), 0, (int)blocklistHash.Length, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } else @@ -1227,7 +1227,7 @@ namespace Duplicati.Library.Main.Operation blocklistHash.SetLength(0); } - await foreach (var blocklist in mbl.GetBlocklistHashes(hashesPerBlock, cancellationToken).ConfigureAwait(false)) + await foreach (var blocklist in mbl.GetBlocklistHashesAsync(hashesPerBlock, cancellationToken).ConfigureAwait(false)) { lastBlocklist ??= blocklist; @@ -1235,7 +1235,7 @@ namespace Duplicati.Library.Main.Operation await EmitBlockListBlock().ConfigureAwait(false); var data = Convert.FromBase64String(blocklist.Hash); - blocklistHash.Write(data, 0, data.Length); + await blocklistHash.WriteAsync(data, 0, data.Length); lastBlocklist = blocklist; } @@ -1244,7 +1244,7 @@ namespace Duplicati.Library.Main.Operation await EmitBlockListBlock().ConfigureAwait(false); //Then we grab all remote volumes that have the missing blocks - await foreach (var (tmpfile, _, _, name) in backendManager.GetFilesOverlappedAsync(await mbl.GetMissingBlockSources(cancellationToken).ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false)) + await foreach (var (tmpfile, _, _, name) in backendManager.GetFilesOverlappedAsync(await mbl.GetMissingBlockSourcesAsync(cancellationToken).ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false), cancellationToken).ConfigureAwait(false)) { try { @@ -1252,10 +1252,10 @@ namespace Duplicati.Library.Main.Operation using (tmpfile) using (var f = new BlockVolumeReader(RestoreHandler.GetCompressionModule(name), tmpfile, m_options)) foreach (var b in f.Blocks) - if (await mbl.SetBlockRestored(b.Key, b.Value, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) + if (await mbl.SetBlockRestoredAsync(b.Key, b.Value, pendingVolume.VolumeID, cancellationToken).ConfigureAwait(false)) if (f.ReadBlock(b.Key, buffer) == b.Value) await pendingVolume - .AddBlock(b.Key, buffer, 0, (int)b.Value, CompressionHint.Default, cancellationToken) + .AddBlockAsync(b.Key, buffer, 0, (int)b.Value, CompressionHint.Default, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) @@ -1266,13 +1266,13 @@ namespace Duplicati.Library.Main.Operation } var missingBlocks = await mbl - .GetMissingBlockCount(cancellationToken) + .GetMissingBlockCountAsync(cancellationToken) .ConfigureAwait(false); var recoveredBlocks = originalMissingBlockCount - missingBlocks; if (recoveredBlocks == 0 || (m_options.DisablePartialDblockRecovery && missingBlocks > 0)) { Logging.Log.WriteInformationMessage(LOGTAG, "RepairMissingBlocks", "Repair cannot acquire {0} required blocks for volume {1}, which are required by the following filesets: ", missingBlocks, originalVolume.Name); - await foreach (var f in mbl.GetFilesetsUsingMissingBlocks(cancellationToken).ConfigureAwait(false)) + await foreach (var f in mbl.GetFilesetsUsingMissingBlocksAsync(cancellationToken).ConfigureAwait(false)) Logging.Log.WriteInformationMessage(LOGTAG, "AffectedFilesetName", f.Name); var recoverymsg = string.Format("If you want to continue working with the database, you can use the \"{0}\" and \"{1}\" commands to purge the missing data from the database and the remote storage.", "list-broken-files", "purge-broken-files"); @@ -1293,7 +1293,7 @@ namespace Duplicati.Library.Main.Operation { pendingVolume.VolumesToDelete.Add(originalVolume); var oldIndexFiles = await db - .GetIndexFilesReferencingBlockFile(originalVolume.ID, cancellationToken) + .GetIndexFilesReferencingBlockFileAsync(originalVolume.ID, cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -1301,12 +1301,12 @@ namespace Duplicati.Library.Main.Operation foreach (var oldIndexFile in oldIndexFiles) { var oldVolume = await db - .GetRemoteVolume(oldIndexFile, cancellationToken) + .GetRemoteVolumeAsync(oldIndexFile, cancellationToken) .ConfigureAwait(false); if (oldVolume.State == RemoteVolumeState.Uploading || oldVolume.State == RemoteVolumeState.Uploaded || oldVolume.State == RemoteVolumeState.Verified) { var blockVolumesReferenced = await db - .GetBlockVolumesFromIndexName(oldIndexFile, cancellationToken) + .GetBlockVolumesFromIndexNameAsync(oldIndexFile, cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); if (blockVolumesReferenced.Any(x => x.Name != originalVolume.Name)) @@ -1329,17 +1329,17 @@ namespace Duplicati.Library.Main.Operation /// The backend manager to use for accessing remote files. /// >A task that completes when the repair is finished. /// If the database doesn't exist. - public async Task RunRepairBrokenFilesets(IBackendManager backendManager) + public async Task RunRepairBrokenFilesetsAsync(IBackendManager backendManager) { if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, null, m_result.TaskControl.ProgressToken) + await LocalRepairDatabase.CreateRepairDatabaseAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var sets = await db - .GetFilesetsWithMissingFiles(m_result.TaskControl.ProgressToken) + .GetFilesetsWithMissingFilesAsync(m_result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -1353,7 +1353,7 @@ namespace Duplicati.Library.Main.Operation ix++; Logging.Log.WriteInformationMessage(LOGTAG, "RepairingBrokenFileset", "Repairing broken fileset {0} of {1}: {2}", ix, sets.Count, entry.Value); var volume = await db - .GetRemoteVolumeFromFilesetID(entry.Key, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeFromFilesetIDAsync(entry.Key, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var parsed = VolumeBase.ParseFilename(volume.Name); using var tmpfile = await backendManager.GetAsync(volume.Name, volume.Hash, volume.Size, CancellationToken.None).ConfigureAwait(false); @@ -1364,11 +1364,11 @@ namespace Duplicati.Library.Main.Operation // Clear out the old fileset await db - .DeleteFilesetEntries(entry.Key, m_result.TaskControl.ProgressToken) + .DeleteFilesetEntriesAsync(entry.Key, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await using (var rdb = await LocalRecreateDatabase.CreateAsync(db, m_options, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await RecreateDatabaseHandler.RecreateFilesetFromRemoteList(rdb, compressor, entry.Key, m_options, new FilterExpression(), m_result.TaskControl.ProgressToken) + await RecreateDatabaseHandler.RecreateFilesetFromRemoteListAsync(rdb, compressor, entry.Key, m_options, new FilterExpression(), m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } @@ -1384,7 +1384,7 @@ namespace Duplicati.Library.Main.Operation /// /// A task that completes when the repair is finished. /// If the database file does not exist. - public async Task RunRepairCommon() + public async Task RunRepairCommonAsync() { if (!File.Exists(m_options.Dbpath)) throw new UserInformationException(string.Format("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); @@ -1392,26 +1392,26 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdateProgress(0); await using var db = - await LocalRepairDatabase.CreateRepairDatabase(m_options.Dbpath, null, m_result.TaskControl.ProgressToken) + await LocalRepairDatabase.CreateRepairDatabaseAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - if (await db.RepairInProgress(m_result.TaskControl.ProgressToken).ConfigureAwait(false) || await db.PartiallyRecreated(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + if (await db.RepairInProgressAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false) || await db.PartiallyRecreatedAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false)) Logging.Log.WriteWarningMessage(LOGTAG, "InProgressDatabase", null, "The database is marked as \"in-progress\" and may be incomplete."); - await db.FixDuplicateMetahash(m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await db.FixDuplicateFileentries(m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await db.FixDuplicatePathsInFilesets(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.FixDuplicateMetahashAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.FixDuplicateFileentriesAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.FixDuplicatePathsInFilesetsAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); await db - .FixDuplicateBlocklistHashes(m_options.Blocksize, m_options.BlockhashSize, m_result.TaskControl.ProgressToken) + .FixDuplicateBlocklistHashesAsync(m_options.Blocksize, m_options.BlockhashSize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .FixMissingBlocklistHashes(m_options.BlockHashAlgorithm, m_options.Blocksize, m_result.TaskControl.ProgressToken) + .FixMissingBlocklistHashesAsync(m_options.BlockHashAlgorithm, m_options.Blocksize, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .FixEmptyMetadatasets(m_options, m_result.TaskControl.ProgressToken) + .FixEmptyMetadatasetsAsync(m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } diff --git a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs index 2383d5ab2..97e33c792 100644 --- a/Duplicati/Library/Main/Operation/Restore/BlockManager.cs +++ b/Duplicati/Library/Main/Operation/Restore/BlockManager.cs @@ -203,7 +203,7 @@ namespace Duplicati.Library.Main.Operation.Restore { var sd = new SleepableDictionary(volume_request, options, readers); - await foreach (var (block_id, volume_id) in db.GetBlocksAndVolumeIDs(options.SkipMetadata, cancellationToken).ConfigureAwait(false)) + await foreach (var (block_id, volume_id) in db.GetBlocksAndVolumeIDsAsync(options.SkipMetadata, cancellationToken).ConfigureAwait(false)) { var bc = sd.m_blockcount.TryGetValue(block_id, out var c); sd.m_blockcount[block_id] = bc ? c + 1 : 1; @@ -481,7 +481,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The channels for writing block responses back to the `FileProcessor`. /// The restore options. /// The results of the restore operation. - public static Task Run(Channels channels, LocalRestoreDatabase db, IChannel[] fp_requests, IChannel>[] fp_responses, Options options, RestoreResults results) + public static Task RunAsync(Channels channels, LocalRestoreDatabase db, IChannel[] fp_requests, IChannel>[] fp_responses, Options options, RestoreResults results) { return AutomationExtensions.RunTask( new diff --git a/Duplicati/Library/Main/Operation/Restore/FileLister.cs b/Duplicati/Library/Main/Operation/Restore/FileLister.cs index 36b3f9323..2d24ffd7e 100644 --- a/Duplicati/Library/Main/Operation/Restore/FileLister.cs +++ b/Duplicati/Library/Main/Operation/Restore/FileLister.cs @@ -52,7 +52,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The restore options /// The restore results /// The restore destination provider, used to get priority files. - public static Task Run(Channels channels, LocalRestoreDatabase db, Options options, RestoreResults result, IList priorityFiles) + public static Task RunAsync(Channels channels, LocalRestoreDatabase db, Options options, RestoreResults result, IList priorityFiles) { return AutomationExtensions.RunTask( new @@ -73,7 +73,7 @@ namespace Duplicati.Library.Main.Operation.Restore sw_get_files?.Start(); // The enumerables are cast to arrays to force the query to be executed and release the database lock. var files = await db - .GetFilesAndSymlinksToRestore(result.TaskControl.ProgressToken) + .GetFilesAndSymlinksToRestoreAsync(result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false); @@ -114,7 +114,7 @@ namespace Duplicati.Library.Main.Operation.Restore sw_get_folders?.Start(); // The enumerables are cast to arrays to force the query to be executed and release the database lock. var folders = await db - .GetFolderMetadataToRestore(result.TaskControl.ProgressToken) + .GetFolderMetadataToRestoreAsync(result.TaskControl.ProgressToken) .ToArrayAsync() .ConfigureAwait(false); sw_get_folders?.Stop(); diff --git a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs index a030389a3..42899859f 100644 --- a/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs +++ b/Duplicati/Library/Main/Operation/Restore/FileProcessor.cs @@ -80,7 +80,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The channel to receive blocks from the block manager. /// The restore options. /// The restore results. - public static Task Run(Channels channels, LocalRestoreDatabase db, IChannel block_request, IChannel> block_response, IRestoreDestinationProvider restoreDestination, Options options, RestoreResults results) + public static Task RunAsync(Channels channels, LocalRestoreDatabase db, IChannel block_request, IChannel> block_response, IRestoreDestinationProvider restoreDestination, Options options, RestoreResults results) { return AutomationExtensions.RunTask( new @@ -125,7 +125,7 @@ namespace Duplicati.Library.Main.Operation.Restore sw_file?.Stop(); // Check if this is a priority file and wait for all priority files to complete before processing non-priority files - await WaitForPriorityFilesIfNeeded(file).ConfigureAwait(false); + await WaitForPriorityFilesIfNeededAsync(file).ConfigureAwait(false); Logging.Log.WriteExplicitMessage(LOGTAG, "FileRestored", null, "{0} Restoring file {1}", my_id, file.TargetPath); @@ -137,12 +137,12 @@ namespace Duplicati.Library.Main.Operation.Restore if (file_processors_restoring_files <= 0 && !file_processor_continue.Task.IsCompleted) file_processor_continue.SetResult(); else - await RendezvousBeforeProcessingFolderMetadata() + await RendezvousBeforeProcessingFolderMetadataAsync() .ConfigureAwait(false); decremented = true; } - await RestoreMetadata(db, file, restoreDestination, block_request, block_response, options, sw_meta, sw_work_meta, sw_req, sw_resp, results.TaskControl.ProgressToken) + await RestoreMetadataAsync(db, file, restoreDestination, block_request, block_response, options, sw_meta, sw_work_meta, sw_req, sw_resp, results.TaskControl.ProgressToken) .ConfigureAwait(false); continue; @@ -152,7 +152,7 @@ namespace Duplicati.Library.Main.Operation.Restore // TODO rather than keeping all of the blocks in memory, we could do a single pass over the blocks using a cursor, only keeping the relevant block requests in memory. Maybe even only a single block request at a time. sw_block?.Start(); var blocks = await db - .GetBlocksFromFile(file.BlocksetID, results.TaskControl.ProgressToken) + .GetBlocksFromFileAsync(file.BlocksetID, results.TaskControl.ProgressToken) .ToArrayAsync(results.TaskControl.ProgressToken) .ConfigureAwait(false); sw_block?.Stop(); @@ -163,7 +163,7 @@ namespace Duplicati.Library.Main.Operation.Restore List missing_blocks, verified_blocks; try { - (bytes_verified, missing_blocks, verified_blocks) = await VerifyTargetBlocks(file, restoreDestination, blocks, filehasher, blockhasher, buffer, options, results, results.TaskControl.ProgressToken).ConfigureAwait(false); + (bytes_verified, missing_blocks, verified_blocks) = await VerifyTargetBlocksAsync(file, restoreDestination, blocks, filehasher, blockhasher, buffer, options, results, results.TaskControl.ProgressToken).ConfigureAwait(false); } catch (Exception ex) { @@ -182,7 +182,7 @@ namespace Duplicati.Library.Main.Operation.Restore // Check if the target file needs to be retargeted if (missing_blocks.Count > 0 && !options.Overwrite && await restoreDestination.FileExists(file.TargetPath, results.TaskControl.ProgressToken).ConfigureAwait(false)) { - var new_name = await GenerateNewName(file, db, restoreDestination, filehasher, results.TaskControl.ProgressToken).ConfigureAwait(false); + var new_name = await GenerateNewNameAsync(file, db, restoreDestination, filehasher, results.TaskControl.ProgressToken).ConfigureAwait(false); if (await restoreDestination.FileExists(new_name, results.TaskControl.ProgressToken).ConfigureAwait(false)) { // The file already exists, which it only does when it matches the target hash. So we can skip the file. @@ -203,7 +203,7 @@ namespace Duplicati.Library.Main.Operation.Restore { try { - await CopyOldTargetBlocksToNewTarget(file, new_file, restoreDestination, buffer, verified_blocks, results.TaskControl.ProgressToken).ConfigureAwait(false); + await CopyOldTargetBlocksToNewTargetAsync(file, new_file, restoreDestination, buffer, verified_blocks, results.TaskControl.ProgressToken).ConfigureAwait(false); } catch (Exception ex) { @@ -245,7 +245,7 @@ namespace Duplicati.Library.Main.Operation.Restore if (missing_blocks.Count > 0 && options.UseLocalBlocks) { // Verify the local blocks at the original restore path that may be used to restore the file. - (bytes_written, missing_blocks) = await VerifyLocalBlocks(file, restoreDestination, missing_blocks, blocks.Length, filehasher, blockhasher, buffer, options, results, block_request, results.TaskControl.ProgressToken).ConfigureAwait(false); + (bytes_written, missing_blocks) = await VerifyLocalBlocksAsync(file, restoreDestination, missing_blocks, blocks.Length, filehasher, blockhasher, buffer, options, results, block_request, results.TaskControl.ProgressToken).ConfigureAwait(false); } sw_work_verify_local?.Stop(); @@ -490,13 +490,13 @@ namespace Duplicati.Library.Main.Operation.Restore } finally { - fs?.Dispose(); + await (fs?.DisposeAsync().AsTask() ?? Task.CompletedTask).ConfigureAwait(false); } } if (!options.SkipMetadata) { - empty_file_or_symlink |= await RestoreMetadata(db, file, restoreDestination, block_request, block_response, options, sw_meta, sw_work_meta, sw_req, sw_resp, results.TaskControl.ProgressToken).ConfigureAwait(false); + empty_file_or_symlink |= await RestoreMetadataAsync(db, file, restoreDestination, block_request, block_response, options, sw_meta, sw_work_meta, sw_req, sw_resp, results.TaskControl.ProgressToken).ConfigureAwait(false); sw_work_meta?.Stop(); } @@ -567,7 +567,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The restore destination provider. /// The blocks in the old file that were verified. /// The cancellation token. - private static async Task CopyOldTargetBlocksToNewTarget(FileRequest old_file, FileRequest new_file, IRestoreDestinationProvider restoreDestination, byte[] buffer, List verified_blocks, CancellationToken cancellationToken) + private static async Task CopyOldTargetBlocksToNewTargetAsync(FileRequest old_file, FileRequest new_file, IRestoreDestinationProvider restoreDestination, byte[] buffer, List verified_blocks, CancellationToken cancellationToken) { using var fs_old = await restoreDestination.OpenRead(old_file.TargetPath, cancellationToken).ConfigureAwait(false); using var fs_new = await restoreDestination.OpenWrite(new_file.TargetPath, cancellationToken).ConfigureAwait(false); @@ -596,7 +596,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The file hasher used to verify whether any of the /// The cancellation token. /// The new filename. If the file already exists, its file hash matches the target hash. - private static async Task GenerateNewName(FileRequest request, LocalRestoreDatabase database, IRestoreDestinationProvider restoreDestination, System.Security.Cryptography.HashAlgorithm filehasher, CancellationToken cancellationToken) + private static async Task GenerateNewNameAsync(FileRequest request, LocalRestoreDatabase database, IRestoreDestinationProvider restoreDestination, System.Security.Cryptography.HashAlgorithm filehasher, CancellationToken cancellationToken) { var ext = SystemIO.IO_OS.PathGetExtension(request.TargetPath) ?? ""; if (!string.IsNullOrEmpty(ext) && !ext.StartsWith(".", StringComparison.Ordinal)) @@ -648,7 +648,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// Rendezvous with the other FileProcessor's before processing folder metadata. /// /// An awaitable task that completes once all of the FileProcessor's have rendezvoused. - private static async Task RendezvousBeforeProcessingFolderMetadata() + private static async Task RendezvousBeforeProcessingFolderMetadataAsync() { var should_wait = false; lock (file_processor_continue_lock) @@ -671,7 +671,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// If the current file is not a priority file, it waits until all priority files have been processed. /// /// The file request being processed. - private static async Task WaitForPriorityFilesIfNeeded(FileRequest file) + private static async Task WaitForPriorityFilesIfNeededAsync(FileRequest file) { if (file.IsPriorityFile) { @@ -721,7 +721,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The stopwatch for internal profiling of the block responses. /// The cancellation token to cancel the operation. /// An awaitable `Task`, which returns `true` if the metadata was restored successfully, `false` otherwise. - private static async Task RestoreMetadata(LocalRestoreDatabase db, FileRequest file, IRestoreDestinationProvider restoreDestination, IChannel block_request, IChannel> block_response, Options options, Stopwatch? sw_meta, Stopwatch? sw_work, Stopwatch? sw_req, Stopwatch? sw_resp, CancellationToken cancellationToken) + private static async Task RestoreMetadataAsync(LocalRestoreDatabase db, FileRequest file, IRestoreDestinationProvider restoreDestination, IChannel block_request, IChannel> block_response, Options options, Stopwatch? sw_meta, Stopwatch? sw_work, Stopwatch? sw_req, Stopwatch? sw_resp, CancellationToken cancellationToken) { sw_meta?.Start(); // Since each FileProcessor should have its own connection, it's ok @@ -729,7 +729,7 @@ namespace Duplicati.Library.Main.Operation.Restore // time, as the other processes should still be able to read. // Therefore, we don't need to read the entire result set into // memory. - var blocks = db.GetMetadataBlocksFromFile(file.ID, cancellationToken); + var blocks = db.GetMetadataBlocksFromFileAsync(file.ID, cancellationToken); sw_meta?.Stop(); using var ms = new MemoryStream(); @@ -759,7 +759,7 @@ namespace Duplicati.Library.Main.Operation.Restore sw_resp?.Stop(); sw_work?.Start(); - ms.Write(datablock.Data, 0, (int)block.BlockSize); + await ms.WriteAsync(datablock.Data, 0, (int)block.BlockSize); sw_work?.Stop(); sw_req?.Start(); @@ -779,7 +779,7 @@ namespace Duplicati.Library.Main.Operation.Restore } ms.Seek(0, SeekOrigin.Begin); - return await RestoreHandler.ApplyMetadata(file.TargetPath, ms, options, restoreDestination, cancellationToken); + return await RestoreHandler.ApplyMetadataAsync(file.TargetPath, ms, options, restoreDestination, cancellationToken); } /// @@ -795,7 +795,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The restoration results. /// The cancellation token. /// An awaitable `Task`, which returns a collection of data blocks that are missing. - private static async Task<(long, List, List)> VerifyTargetBlocks(FileRequest file, IRestoreDestinationProvider restoreDestination, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, byte[] buffer, Options options, RestoreResults results, CancellationToken cancellationToken) + private static async Task<(long, List, List)> VerifyTargetBlocksAsync(FileRequest file, IRestoreDestinationProvider restoreDestination, BlockRequest[] blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, byte[] buffer, Options options, RestoreResults results, CancellationToken cancellationToken) { long bytes_read = 0; List missing_blocks = []; @@ -911,7 +911,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The channel to request blocks from the block manager. Used to inform the block manager which blocks are already present. /// The cancellation token. /// An awaitable `Task`, which returns a collection of data blocks that are missing. - private static async Task<(long, List)> VerifyLocalBlocks(FileRequest file, IRestoreDestinationProvider restoreDestination, List blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, byte[] buffer, Options options, RestoreResults results, IChannel block_request, CancellationToken cancellationToken) + private static async Task<(long, List)> VerifyLocalBlocksAsync(FileRequest file, IRestoreDestinationProvider restoreDestination, List blocks, long total_blocks, System.Security.Cryptography.HashAlgorithm filehasher, System.Security.Cryptography.HashAlgorithm blockhasher, byte[] buffer, Options options, RestoreResults results, IChannel block_request, CancellationToken cancellationToken) { if (file.TargetPath == file.OriginalPath) { diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs index 1e1533f67..259e5c1ef 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDecompressor.cs @@ -47,7 +47,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// /// The named channels for the restore operation. /// The restore options - public static Task Run(Channels channels, Options options) + public static Task RunAsync(Channels channels, Options options) { return AutomationExtensions.RunTask( new diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs index 16ad2a8cd..3dd423f45 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDecryptor.cs @@ -48,7 +48,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The named channels for the restore operation. /// The backend manager. /// The restore options. - public static Task Run(Channels channels, IBackendManager backend, Options options) + public static Task RunAsync(Channels channels, IBackendManager backend, Options options) { return AutomationExtensions.RunTask( new diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs index a4a61cb41..1b38abea2 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeDownloader.cs @@ -51,7 +51,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// The backend to use for downloading the volumes. /// The restore options. /// The restore results. - public static Task Run(Channels channels, LocalRestoreDatabase db, IBackendManager backend, Options options, RestoreResults results) + public static Task RunAsync(Channels channels, LocalRestoreDatabase db, IBackendManager backend, Options options, RestoreResults results) { return AutomationExtensions.RunTask( new @@ -79,7 +79,7 @@ namespace Duplicati.Library.Main.Operation.Restore sw_wait?.Start(); TempFile f; var (volume_name, size, hash) = await db - .GetVolumeInfo(volume_id, results.TaskControl.ProgressToken) + .GetVolumeInfoAsync(volume_id, results.TaskControl.ProgressToken) .FirstAsync() .ConfigureAwait(false); try diff --git a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs index 1aacb074a..0d53ce043 100644 --- a/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs +++ b/Duplicati/Library/Main/Operation/Restore/VolumeManager.cs @@ -100,7 +100,7 @@ namespace Duplicati.Library.Main.Operation.Restore /// /// The named channels for the restore operation. /// The restore options. - public static Task Run(Channels channels, Options options, RestoreResults results) + public static Task RunAsync(Channels channels, Options options, RestoreResults results) { return AutomationExtensions.RunTask( new @@ -183,7 +183,7 @@ namespace Duplicati.Library.Main.Operation.Restore return volume_id; } - await results.TaskControl.ProgressRendevouz().ConfigureAwait(false); + await results.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false); var rfa = new ReadFromEither(self.VolumeResponse, self.VolumeRequest); try diff --git a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs index 767790399..42b39ee2f 100644 --- a/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreControlFilesHandler.cs @@ -66,7 +66,7 @@ namespace Duplicati.Library.Main.Operation foreach (var fileversion in filteredList) try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); return; @@ -74,7 +74,7 @@ namespace Duplicati.Library.Main.Operation var file = fileversion.Value.File; var entry = await db - .GetRemoteVolume(file.Name, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeAsync(file.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var res = new List(); diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index a14fca78f..90a96e38d 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -183,7 +183,7 @@ namespace Duplicati.Library.Main.Operation .ConfigureAwait(false); if (!m_options.SkipMetadata) - await ApplyStoredMetadata(m_options, new RestoreHandlerMetadataStorage(), restoreDestination, m_result.TaskControl.ProgressToken); + await ApplyStoredMetadataAsync(m_options, new RestoreHandlerMetadataStorage(), restoreDestination, m_result.TaskControl.ProgressToken); //If we have --version set, we need to adjust, as the db has only the required versions //TODO: Bit of a hack to set options that way @@ -204,7 +204,7 @@ namespace Duplicati.Library.Main.Operation } } - private static async Task PatchWithBlocklist(LocalRestoreDatabase database, BlockVolumeReader blocks, Options options, RestoreResults result, byte[] blockbuffer, RestoreHandlerMetadataStorage metadatastorage, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) + private static async Task PatchWithBlocklistAsync(LocalRestoreDatabase database, BlockVolumeReader blocks, Options options, RestoreResults result, byte[] blockbuffer, RestoreHandlerMetadataStorage metadatastorage, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) { var blocksize = options.Blocksize; var updateCounter = 0L; @@ -212,8 +212,8 @@ namespace Duplicati.Library.Main.Operation using var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm); await using var blockmarker = await database.CreateBlockMarkerAsync(cancellationToken).ConfigureAwait(false); - await using var volumekeeper = await database.GetMissingBlockData(blocks, options.Blocksize, cancellationToken).ConfigureAwait(false); - await foreach (var restorelist in volumekeeper.FilesWithMissingBlocks(cancellationToken).ConfigureAwait(false)) + await using var volumekeeper = await database.GetMissingBlockDataAsync(blocks, options.Blocksize, cancellationToken).ConfigureAwait(false); + await foreach (var restorelist in volumekeeper.FilesWithMissingBlocksAsync(cancellationToken).ConfigureAwait(false)) { var targetpath = restorelist.Path; @@ -238,7 +238,7 @@ namespace Duplicati.Library.Main.Operation // TODO: Much faster if we iterate the volume and checks what blocks are used, // because the compressors usually like sequential reading using (var file = await restoreDestination.OpenWrite(targetpath, cancellationToken).ConfigureAwait(false)) - await foreach (var targetblock in restorelist.Blocks(cancellationToken).ConfigureAwait(false)) + await foreach (var targetblock in restorelist.BlocksAsync(cancellationToken).ConfigureAwait(false)) { file.Position = targetblock.Offset; var size = blocks.ReadBlock(targetblock.Key, blockbuffer); @@ -256,9 +256,9 @@ namespace Duplicati.Library.Main.Operation if (valid) { - file.Write(blockbuffer, 0, size); + await file.WriteAsync(blockbuffer, 0, size); await blockmarker - .SetBlockRestored(restorelist.FileID, targetblock.Offset / blocksize, targetblock.Key, size, false, cancellationToken) + .SetBlockRestoredAsync(restorelist.FileID, targetblock.Offset / blocksize, targetblock.Key, size, false, cancellationToken) .ConfigureAwait(false); result.SizeOfRestoredData += size; } @@ -271,7 +271,7 @@ namespace Duplicati.Library.Main.Operation if ((++updateCounter) % 20 == 0) await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, cancellationToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) @@ -285,7 +285,7 @@ namespace Duplicati.Library.Main.Operation if (!options.SkipMetadata) { - await foreach (var restoremetadata in volumekeeper.MetadataWithMissingBlocks(cancellationToken).ConfigureAwait(false)) + await foreach (var restoremetadata in volumekeeper.MetadataWithMissingBlocksAsync(cancellationToken).ConfigureAwait(false)) { var targetpath = restoremetadata.Path; Logging.Log.WriteVerboseMessage(LOGTAG, "RecordingMetadata", "Recording metadata from remote data: {0}", targetpath); @@ -295,15 +295,15 @@ namespace Duplicati.Library.Main.Operation // TODO: When we support multi-block metadata this needs to deal with it using (var ms = new System.IO.MemoryStream()) { - await foreach (var targetblock in restoremetadata.Blocks(cancellationToken).ConfigureAwait(false)) + await foreach (var targetblock in restoremetadata.BlocksAsync(cancellationToken).ConfigureAwait(false)) { ms.Position = targetblock.Offset; var size = blocks.ReadBlock(targetblock.Key, blockbuffer); if (targetblock.Size == size) { - ms.Write(blockbuffer, 0, size); + await ms.WriteAsync(blockbuffer, 0, size); await blockmarker - .SetBlockRestored(restoremetadata.FileID, targetblock.Offset / blocksize, targetblock.Key, size, true, cancellationToken) + .SetBlockRestoredAsync(restoremetadata.FileID, targetblock.Offset / blocksize, targetblock.Key, size, true, cancellationToken) .ConfigureAwait(false); } } @@ -322,12 +322,12 @@ namespace Duplicati.Library.Main.Operation } } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, cancellationToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, cancellationToken) .ConfigureAwait(false); await blockmarker.CommitAsync(cancellationToken).ConfigureAwait(false); } - private static async Task ApplyStoredMetadata(Options options, RestoreHandlerMetadataStorage metadatastorage, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) + private static async Task ApplyStoredMetadataAsync(Options options, RestoreHandlerMetadataStorage metadatastorage, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) { foreach (var metainfo in metadatastorage.Records) { @@ -355,7 +355,7 @@ namespace Duplicati.Library.Main.Operation } } - await ApplyMetadata(targetpath, metainfo.Value, options, restoreDestination, cancellationToken).ConfigureAwait(false); + await ApplyMetadataAsync(targetpath, metainfo.Value, options, restoreDestination, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { @@ -378,9 +378,9 @@ namespace Duplicati.Library.Main.Operation private async Task DoRunNewAsync(IBackendManager backendManager, LocalRestoreDatabase database, IFilter filter, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) { // Perform initial setup - await Utility.UpdateOptionsFromDb(database, m_options, cancellationToken) + await Utility.UpdateOptionsFromDbAsync(database, m_options, cancellationToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(database, m_options, cancellationToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(database, m_options, cancellationToken) .ConfigureAwait(false); if (m_options.RestoreChannelBufferSize < 0) @@ -396,17 +396,17 @@ namespace Duplicati.Library.Main.Operation if (!m_options.NoBackendverification) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, cancellationToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, cancellationToken).ConfigureAwait(false); } // Prepare the block and file list and create the directory structure m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateFileList); using (new Logging.Timer(LOGTAG, "PrepareBlockList", "PrepareBlockList")) - await PrepareBlockAndFileList(database, m_options, filter, restoreDestination, m_result) + await PrepareBlockAndFileListAsync(database, m_options, filter, restoreDestination, m_result) .ConfigureAwait(false); m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateTargetFolders); using (new Logging.Timer(LOGTAG, "CreateDirectory", "CreateDirectory")) - await CreateDirectoryStructure(database, restoreDestination, string.IsNullOrEmpty(restoreDestination.TargetDestination), m_options, m_result).ConfigureAwait(false); + await CreateDirectoryStructureAsync(database, restoreDestination, string.IsNullOrEmpty(restoreDestination.TargetDestination), m_options, m_result).ConfigureAwait(false); // At this point, there should be no more writes to the database, so we have to unlock the database: await database.Transaction @@ -431,13 +431,13 @@ namespace Duplicati.Library.Main.Operation Restore.FileProcessor.priority_files_completed = new TaskCompletionSource(); // Create the process network - var filelister = Restore.FileLister.Run(channels, database, m_options, m_result, priorityFiles); - var fileprocessors = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(i => Restore.FileProcessor.Run(channels, database, fileprocessor_requests[i], fileprocessor_responses[i], restoreDestination, m_options, m_result)).ToArray(); - var blockmanager = Restore.BlockManager.Run(channels, database, fileprocessor_requests, fileprocessor_responses, m_options, m_result); - var volumecache = Restore.VolumeManager.Run(channels, m_options, m_result); - var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.Run(channels, database, backendManager, m_options, m_result)).ToArray(); - var volumedecryptors = Enumerable.Range(0, m_options.RestoreVolumeDecryptors).Select(i => Restore.VolumeDecryptor.Run(channels, backendManager, m_options)).ToArray(); - var volumedecompressors = Enumerable.Range(0, m_options.RestoreVolumeDecompressors).Select(i => Restore.VolumeDecompressor.Run(channels, m_options)).ToArray(); + var filelister = Restore.FileLister.RunAsync(channels, database, m_options, m_result, priorityFiles); + var fileprocessors = Enumerable.Range(0, m_options.RestoreFileProcessors).Select(i => Restore.FileProcessor.RunAsync(channels, database, fileprocessor_requests[i], fileprocessor_responses[i], restoreDestination, m_options, m_result)).ToArray(); + var blockmanager = Restore.BlockManager.RunAsync(channels, database, fileprocessor_requests, fileprocessor_responses, m_options, m_result); + var volumecache = Restore.VolumeManager.RunAsync(channels, m_options, m_result); + var volumedownloaders = Enumerable.Range(0, m_options.RestoreVolumeDownloaders).Select(i => Restore.VolumeDownloader.RunAsync(channels, database, backendManager, m_options, m_result)).ToArray(); + var volumedecryptors = Enumerable.Range(0, m_options.RestoreVolumeDecryptors).Select(i => Restore.VolumeDecryptor.RunAsync(channels, backendManager, m_options)).ToArray(); + var volumedecompressors = Enumerable.Range(0, m_options.RestoreVolumeDecompressors).Select(i => Restore.VolumeDecompressor.RunAsync(channels, m_options)).ToArray(); setup_log_timer.Dispose(); @@ -467,7 +467,7 @@ namespace Duplicati.Library.Main.Operation }, kill_updater.Token); await Task.WhenAll(all).ConfigureAwait(false); - kill_updater.Cancel(); + await kill_updater.CancelAsync(); } await database.Transaction @@ -476,7 +476,7 @@ namespace Duplicati.Library.Main.Operation await database.DisposePoolAsync().ConfigureAwait(false); - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PostRestoreVerify); @@ -507,7 +507,7 @@ namespace Duplicati.Library.Main.Operation } // Drop the temp tables - await database.DropRestoreTable(cancellationToken).ConfigureAwait(false); + await database.DropRestoreTableAsync(cancellationToken).ConfigureAwait(false); await backendManager.WaitForEmptyAsync(database, m_result.TaskControl.ProgressToken).ConfigureAwait(false); // Report that the restore is complete @@ -556,16 +556,16 @@ namespace Duplicati.Library.Main.Operation using (var metadatastorage = new RestoreHandlerMetadataStorage()) { // One-time setup - await Utility.UpdateOptionsFromDb(database, m_options, cancellationToken) + await Utility.UpdateOptionsFromDbAsync(database, m_options, cancellationToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(database, m_options, cancellationToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(database, m_options, cancellationToken) .ConfigureAwait(false); m_blockbuffer = new byte[m_options.Blocksize]; if (!m_options.NoBackendverification) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PreRestoreVerify); - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, cancellationToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, database, m_result.BackendWriter, latestVolumesOnly: false, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, cancellationToken).ConfigureAwait(false); } // Get priority files from restore destination @@ -632,13 +632,13 @@ namespace Duplicati.Library.Main.Operation //Figure out what files are to be patched, and what blocks are needed m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateFileList); using (new Logging.Timer(LOGTAG, "PrepareBlockList", "PrepareBlockList")) - await PrepareBlockAndFileList(database, m_options, filter, restoreDestination, m_result) + await PrepareBlockAndFileListAsync(database, m_options, filter, restoreDestination, m_result) .ConfigureAwait(false); //Make the entire output setup m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_CreateTargetFolders); using (new Logging.Timer(LOGTAG, "CreateDirectory", "CreateDirectory")) - await CreateDirectoryStructure(database, restoreDestination, string.IsNullOrEmpty(restoreDestination.TargetDestination), m_options, m_result) + await CreateDirectoryStructureAsync(database, restoreDestination, string.IsNullOrEmpty(restoreDestination.TargetDestination), m_options, m_result) .ConfigureAwait(false); //If we are patching an existing target folder, do not touch stuff that is already updated @@ -646,7 +646,7 @@ namespace Duplicati.Library.Main.Operation using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) using (new Logging.Timer(LOGTAG, "ScanForExistingTargetBlocks", "ScanForExistingTargetBlocks")) - await ScanForExistingTargetBlocks(database, m_blockbuffer, blockhasher, filehasher, m_options, restoreDestination, m_result).ConfigureAwait(false); + await ScanForExistingTargetBlocksAsync(database, m_blockbuffer, blockhasher, filehasher, m_options, restoreDestination, m_result).ConfigureAwait(false); //Look for existing blocks in the original source files only if (m_options.UseLocalBlocks && !string.IsNullOrEmpty(restoreDestination.TargetDestination)) @@ -655,11 +655,11 @@ namespace Duplicati.Library.Main.Operation using (new Logging.Timer(LOGTAG, "ScanForExistingSourceBlocksFast", "ScanForExistingSourceBlocksFast")) { m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_ScanForLocalBlocks); - await ScanForExistingSourceBlocksFast(database, m_options, m_blockbuffer, blockhasher, restoreDestination, m_result).ConfigureAwait(false); + await ScanForExistingSourceBlocksFastAsync(database, m_options, m_blockbuffer, blockhasher, restoreDestination, m_result).ConfigureAwait(false); } } - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(database, cancellationToken).ConfigureAwait(false); return; @@ -671,10 +671,10 @@ namespace Duplicati.Library.Main.Operation m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PatchWithLocalBlocks); using (var blockhasher = HashFactory.CreateHasher(m_options.BlockHashAlgorithm)) using (new Logging.Timer(LOGTAG, "PatchWithLocalBlocks", "PatchWithLocalBlocks")) - await ScanForExistingSourceBlocks(database, m_options, m_blockbuffer, blockhasher, m_result, metadatastorage, restoreDestination).ConfigureAwait(false); + await ScanForExistingSourceBlocksAsync(database, m_options, m_blockbuffer, blockhasher, m_result, metadatastorage, restoreDestination).ConfigureAwait(false); } - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(database, cancellationToken).ConfigureAwait(false); return; @@ -684,7 +684,7 @@ namespace Duplicati.Library.Main.Operation List volumes; using (new Logging.Timer(LOGTAG, "GetMissingVolumes", "GetMissingVolumes")) volumes = await database - .GetMissingVolumes(cancellationToken) + .GetMissingVolumesAsync(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false); @@ -701,7 +701,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(database, cancellationToken).ConfigureAwait(false); return; @@ -709,7 +709,7 @@ namespace Duplicati.Library.Main.Operation using (tmpfile) using (var blocks = new BlockVolumeReader(GetCompressionModule(name), tmpfile, m_options)) - await PatchWithBlocklist(database, blocks, m_options, m_result, m_blockbuffer, metadatastorage, restoreDestination, cancellationToken) + await PatchWithBlocklistAsync(database, blocks, m_options, m_result, m_blockbuffer, metadatastorage, restoreDestination, cancellationToken) .ConfigureAwait(false); } catch (Exception ex) @@ -724,7 +724,7 @@ namespace Duplicati.Library.Main.Operation var fileErrors = 0L; // Restore empty files. They might not have any blocks so don't appear in any volume. - await foreach (var file in database.GetFilesToRestore(true, cancellationToken).Where(item => item.Length == 0).ConfigureAwait(false)) + await foreach (var file in database.GetFilesToRestoreAsync(true, cancellationToken).Where(item => item.Length == 0).ConfigureAwait(false)) { Logging.Log.WriteVerboseMessage(LOGTAG, "RestoreEmptyFile", "Restoring empty file \"{0}\"", file.Path); @@ -762,9 +762,9 @@ namespace Duplicati.Library.Main.Operation // Apply metadata if (!m_options.SkipMetadata) - await ApplyStoredMetadata(m_options, metadatastorage, restoreDestination, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await ApplyStoredMetadataAsync(m_options, metadatastorage, restoreDestination, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Restore_PostRestoreVerify); @@ -774,11 +774,11 @@ namespace Duplicati.Library.Main.Operation // After all blocks in the files are restored, verify the file hash using (var filehasher = HashFactory.CreateHasher(m_options.FileHashAlgorithm)) using (new Logging.Timer(LOGTAG, "RestoreVerification", "RestoreVerification")) - await foreach (var file in database.GetFilesToRestore(true, cancellationToken).ConfigureAwait(false)) + await foreach (var file in database.GetFilesToRestoreAsync(true, cancellationToken).ConfigureAwait(false)) { try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backendManager.WaitForEmptyAsync(database, cancellationToken).ConfigureAwait(false); return; @@ -815,11 +815,11 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteInformationMessage(LOGTAG, "RestoreFailures", "Failed to restore {0} files", fileErrors); // Drop the temp tables - await database.DropRestoreTable(cancellationToken).ConfigureAwait(false); + await database.DropRestoreTableAsync(cancellationToken).ConfigureAwait(false); await backendManager.WaitForEmptyAsync(database, cancellationToken).ConfigureAwait(false); } - public static async Task ApplyMetadata(string path, System.IO.Stream stream, Options options, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) + public static async Task ApplyMetadataAsync(string path, System.IO.Stream stream, Options options, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) { // TODO This has been modified to return a bool indicating if anything was written to properly report the number of files restored. The legacy restore doesn't check this, which produces an error in the CI where it reports that no files have been restored, even though one have. It's in Duplicati/UnitTests/SymLinkTests.cs the test SymLinkTests.SymLinkExists() that fails on the very last assert that there are 0 warnings. using (var tr = new System.IO.StreamReader(stream)) @@ -834,12 +834,12 @@ namespace Duplicati.Library.Main.Operation } } - private static async Task ScanForExistingSourceBlocksFast(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, IRestoreDestinationProvider restoreDestination, RestoreResults result) + private static async Task ScanForExistingSourceBlocksFastAsync(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, IRestoreDestinationProvider restoreDestination, RestoreResults result) { // Fill BLOCKS with data from known local source files await using var blockmarker = await database.CreateBlockMarkerAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); var updateCount = 0L; - await foreach (var entry in database.GetFilesAndSourceBlocksFast(options.Blocksize, result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var entry in database.GetFilesAndSourceBlocksFastAsync(options.Blocksize, result.TaskControl.ProgressToken).ConfigureAwait(false)) { var targetpath = entry.TargetPath; var targetfileid = entry.TargetFileID; @@ -865,9 +865,9 @@ namespace Duplicati.Library.Main.Operation try { using var sourcestream = SystemIO.IO_OS.FileOpenRead(sourcepath); - await foreach (var block in entry.Blocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var block in entry.BlocksAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) { - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; //TODO: Handle metadata @@ -886,10 +886,10 @@ namespace Duplicati.Library.Main.Operation if (!options.Dryrun) { targetstream.Position = block.Offset; - targetstream.Write(blockbuffer, 0, size); + await targetstream.WriteAsync(blockbuffer, 0, size); } - await blockmarker.SetBlockRestored(targetfileid, block.Index, key, block.Size, false, result.TaskControl.ProgressToken) + await blockmarker.SetBlockRestoredAsync(targetfileid, block.Index, key, block.Size, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -907,9 +907,9 @@ namespace Duplicati.Library.Main.Operation if ((++updateCount) % 20 == 0) { await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; } @@ -938,24 +938,24 @@ namespace Duplicati.Library.Main.Operation } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); await blockmarker.CommitAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); } - private static async Task ScanForExistingSourceBlocks(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result, RestoreHandlerMetadataStorage metadatastorage, IRestoreDestinationProvider restoreDestination) + private static async Task ScanForExistingSourceBlocksAsync(LocalRestoreDatabase database, Options options, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm hasher, RestoreResults result, RestoreHandlerMetadataStorage metadatastorage, IRestoreDestinationProvider restoreDestination) { // Fill BLOCKS with data from known local source files await using var blockmarker = await database.CreateBlockMarkerAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); var updateCount = 0L; - await foreach (var restorelist in database.GetFilesAndSourceBlocks(options.SkipMetadata, options.Blocksize, result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var restorelist in database.GetFilesAndSourceBlocksAsync(options.SkipMetadata, options.Blocksize, result.TaskControl.ProgressToken).ConfigureAwait(false)) { var targetpath = restorelist.TargetPath; var targetfileid = restorelist.TargetFileID; var patched = false; try { - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; if (!options.Dryrun) @@ -969,13 +969,13 @@ namespace Duplicati.Library.Main.Operation } using (var file = options.Dryrun ? null : await restoreDestination.OpenWrite(targetpath, result.TaskControl.ProgressToken).ConfigureAwait(false)) - await foreach (var targetblock in restorelist.Blocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var targetblock in restorelist.BlocksAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) { - await foreach (var source in targetblock.BlockSources(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var source in targetblock.BlockSourcesAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) { try { - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; if (SystemIO.IO_OS.FileExists(source.Path)) @@ -1004,12 +1004,12 @@ namespace Duplicati.Library.Main.Operation else { file.Position = targetblock.Offset; - file.Write(blockbuffer, 0, size); + await file.WriteAsync(blockbuffer, 0, size); } } await blockmarker - .SetBlockRestored(targetfileid, targetblock.Index, key, targetblock.Size, false, result.TaskControl.ProgressToken) + .SetBlockRestoredAsync(targetfileid, targetblock.Index, key, targetblock.Size, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); patched = true; break; @@ -1029,7 +1029,7 @@ namespace Duplicati.Library.Main.Operation if ((++updateCount) % 20 == 0) await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (Exception ex) @@ -1049,19 +1049,19 @@ namespace Duplicati.Library.Main.Operation } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); await blockmarker.CommitAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); } - private static async Task PrepareBlockAndFileList(LocalRestoreDatabase database, Options options, Library.Utility.IFilter filter, IRestoreDestinationProvider restoreDestination, RestoreResults result) + private static async Task PrepareBlockAndFileListAsync(LocalRestoreDatabase database, Options options, Library.Utility.IFilter filter, IRestoreDestinationProvider restoreDestination, RestoreResults result) { // Create a temporary table FILES by selecting the files from fileset that matches a specific operation id // Delete all entries from the temp table that are excluded by the filter(s) using (new Logging.Timer(LOGTAG, "PrepareRestoreFileList", "PrepareRestoreFileList")) { var c = await database - .PrepareRestoreFilelist(options.Time, options.Version, filter, result.TaskControl.ProgressToken) + .PrepareRestoreFilelistAsync(options.Time, options.Version, filter, result.TaskControl.ProgressToken) .ConfigureAwait(false); result.OperationProgressUpdater.UpdatefileCount(c.Item1, c.Item2, true); } @@ -1072,35 +1072,35 @@ namespace Duplicati.Library.Main.Operation // Find the largest common prefix var largest_prefix = options.DontCompressRestorePaths ? "" : - await database.GetLargestPrefix(result.TaskControl.ProgressToken).ConfigureAwait(false); + await database.GetLargestPrefixAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); Logging.Log.WriteVerboseMessage(LOGTAG, "MappingRestorePath", "Mapping restore path prefix to \"{0}\" to \"{1}\"", largest_prefix, Util.AppendDirSeparator(restoreDestination.TargetDestination)); // Set the target paths, special care with C:\ and / await database - .SetTargetPaths(largest_prefix, Util.AppendDirSeparator(restoreDestination.TargetDestination), result.TaskControl.ProgressToken) + .SetTargetPathsAsync(largest_prefix, Util.AppendDirSeparator(restoreDestination.TargetDestination), result.TaskControl.ProgressToken) .ConfigureAwait(false); } else { - await database.SetTargetPaths("", "", result.TaskControl.ProgressToken).ConfigureAwait(false); + await database.SetTargetPathsAsync("", "", result.TaskControl.ProgressToken).ConfigureAwait(false); } // Create a temporary table BLOCKS that lists all blocks that needs to be recovered using (new Logging.Timer(LOGTAG, "FindMissingBlocks", "FindMissingBlocks")) await database - .FindMissingBlocks(options.SkipMetadata, result.TaskControl.ProgressToken) + .FindMissingBlocksAsync(options.SkipMetadata, result.TaskControl.ProgressToken) .ConfigureAwait(false); // Create temporary tables and triggers that automatically track progress using (new Logging.Timer(LOGTAG, "CreateProgressTracker", "CreateProgressTracker")) await database - .CreateProgressTracker(false, result.TaskControl.ProgressToken) + .CreateProgressTrackerAsync(false, result.TaskControl.ProgressToken) .ConfigureAwait(false); } - private static async Task CreateDirectoryStructure(LocalRestoreDatabase database, IRestoreDestinationProvider restoreDestination, bool restoreToOriginalLocation, Options options, RestoreResults result) + private static async Task CreateDirectoryStructureAsync(LocalRestoreDatabase database, IRestoreDestinationProvider restoreDestination, bool restoreToOriginalLocation, Options options, RestoreResults result) { // This part is not protected by try/catch as we need the target folder to exist if (!string.IsNullOrEmpty(restoreDestination.TargetDestination)) @@ -1116,11 +1116,11 @@ namespace Duplicati.Library.Main.Operation } } - await foreach (var folder in database.GetTargetFolders(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var folder in database.GetTargetFoldersAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) { try { - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; if (options.Dryrun) @@ -1140,7 +1140,7 @@ namespace Duplicati.Library.Main.Operation } } - private static async Task ResetReadOnlyAttributeIfNeeded(string path, Options options, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) + private static async Task ResetReadOnlyAttributeIfNeededAsync(string path, Options options, IRestoreDestinationProvider restoreDestination, CancellationToken cancellationToken) { if (await restoreDestination.HasReadOnlyAttribute(path, cancellationToken).ConfigureAwait(false)) { @@ -1151,12 +1151,12 @@ namespace Duplicati.Library.Main.Operation } } - private static async Task ScanForExistingTargetBlocks(LocalRestoreDatabase database, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm blockhasher, System.Security.Cryptography.HashAlgorithm filehasher, Options options, IRestoreDestinationProvider restoreDestination, RestoreResults result) + private static async Task ScanForExistingTargetBlocksAsync(LocalRestoreDatabase database, byte[] blockbuffer, System.Security.Cryptography.HashAlgorithm blockhasher, System.Security.Cryptography.HashAlgorithm filehasher, Options options, IRestoreDestinationProvider restoreDestination, RestoreResults result) { // Scan existing files for existing BLOCKS await using var blockmarker = await database.CreateBlockMarkerAsync(result.TaskControl.ProgressToken).ConfigureAwait(false); var updateCount = 0L; - await foreach (var restorelist in database.GetExistingFilesWithBlocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var restorelist in database.GetExistingFilesWithBlocksAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) { var rename = !options.Overwrite; var targetpath = restorelist.TargetPath; @@ -1167,7 +1167,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; var currentfilelength = await restoreDestination.GetFileLength(targetpath, result.TaskControl.ProgressToken).ConfigureAwait(false); @@ -1178,7 +1178,7 @@ namespace Duplicati.Library.Main.Operation // be truncated (i.e. forthwritten log files). if (!rename && currentfilelength > targetfilelength) { - await ResetReadOnlyAttributeIfNeeded(targetpath, options, restoreDestination, result.TaskControl.ProgressToken).ConfigureAwait(false); + await ResetReadOnlyAttributeIfNeededAsync(targetpath, options, restoreDestination, result.TaskControl.ProgressToken).ConfigureAwait(false); if (options.Dryrun) Logging.Log.WriteDryrunMessage(LOGTAG, "WouldTruncateFile", "Would truncate file '{0}' to length of {1:N0} bytes", targetpath, targetfilelength); else @@ -1203,7 +1203,7 @@ namespace Duplicati.Library.Main.Operation using (var file = await restoreDestination.OpenRead(targetpath, result.TaskControl.ProgressToken).ConfigureAwait(false)) using (var block = new Blockprocessor(file, blockbuffer)) - await foreach (var targetblock in restorelist.Blocks(result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach (var targetblock in restorelist.BlocksAsync(result.TaskControl.ProgressToken).ConfigureAwait(false)) { var size = block.Readblock(); if (size <= 0) @@ -1229,7 +1229,7 @@ namespace Duplicati.Library.Main.Operation if (key == targetblock.Hash) { await blockmarker - .SetBlockRestored(targetfileid, targetblock.Index, key, size, false, result.TaskControl.ProgressToken) + .SetBlockRestoredAsync(targetfileid, targetblock.Index, key, size, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); blockhashmatch = true; } @@ -1252,13 +1252,13 @@ namespace Duplicati.Library.Main.Operation } if (!rename && !fullfilehashmatch && !wasTruncated) // Reset read-only attribute (if set) to overwrite - await ResetReadOnlyAttributeIfNeeded(targetpath, options, restoreDestination, result.TaskControl.ProgressToken).ConfigureAwait(false); + await ResetReadOnlyAttributeIfNeededAsync(targetpath, options, restoreDestination, result.TaskControl.ProgressToken).ConfigureAwait(false); if (fullfilehashmatch) { //TODO: Check metadata to trigger rename? If metadata changed, it will still be restored for the file in-place. await blockmarker - .SetFileDataVerified(targetfileid, result.TaskControl.ProgressToken) + .SetFileDataVerifiedAsync(targetfileid, result.TaskControl.ProgressToken) .ConfigureAwait(false); Logging.Log.WriteVerboseMessage(LOGTAG, "TargetExistsInCorrectVersion", "Target file exists{1} and is correct version: {0}", targetpath, wasTruncated ? " (but was truncated)" : ""); rename = false; @@ -1271,7 +1271,7 @@ namespace Duplicati.Library.Main.Operation // The new file will have none of the correct blocks, // even if the scanned file had some await blockmarker - .SetAllBlocksMissing(targetfileid, result.TaskControl.ProgressToken) + .SetAllBlocksMissingAsync(targetfileid, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -1279,9 +1279,9 @@ namespace Duplicati.Library.Main.Operation if ((++updateCount) % 20 == 0) { await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); - if (!await result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) return; } } @@ -1328,10 +1328,10 @@ namespace Duplicati.Library.Main.Operation //TODO: Also needs metadata check to make correct decision. // We stick to the policy to restore metadata in place, if data ok. So, metadata block may be restored. await blockmarker - .SetAllBlocksRestored(targetfileid, false, result.TaskControl.ProgressToken) + .SetAllBlocksRestoredAsync(targetfileid, false, result.TaskControl.ProgressToken) .ConfigureAwait(false); await blockmarker - .SetFileDataVerified(targetfileid, result.TaskControl.ProgressToken) + .SetFileDataVerifiedAsync(targetfileid, result.TaskControl.ProgressToken) .ConfigureAwait(false); break; } @@ -1349,14 +1349,14 @@ namespace Duplicati.Library.Main.Operation Logging.Log.WriteVerboseMessage(LOGTAG, "TargetFileRetargeted", "Target file exists and will be restored to: {0}", newname); await database - .UpdateTargetPath(targetfileid, newname, result.TaskControl.ProgressToken) + .UpdateTargetPathAsync(targetfileid, newname, result.TaskControl.ProgressToken) .ConfigureAwait(false); } } await blockmarker - .UpdateProcessed(result.OperationProgressUpdater, result.TaskControl.ProgressToken) + .UpdateProcessedAsync(result.OperationProgressUpdater, result.TaskControl.ProgressToken) .ConfigureAwait(false); await blockmarker .CommitAsync(result.TaskControl.ProgressToken) diff --git a/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs b/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs index b94f85833..07b7a9e06 100644 --- a/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs +++ b/Duplicati/Library/Main/Operation/SearchEntriesHandler.cs @@ -57,7 +57,7 @@ internal static class SearchEntriesHandler if (!options.AllVersions) { filesetIds = await db - .GetFilesetIDs(options.Time, options.Version, false, result.TaskControl.ProgressToken) + .GetFilesetIDsAsync(options.Time, options.Version, false, result.TaskControl.ProgressToken) .ToArrayAsync(cancellationToken: result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -69,13 +69,13 @@ internal static class SearchEntriesHandler paths = paths.Select(path => Util.AppendDirSeparator(path)).ToArray(); result.FileVersions = await db - .SearchEntries(paths, filter, filesetIds, offset, limit, result.TaskControl.ProgressToken) + .SearchEntriesAsync(paths, filter, filesetIds, offset, limit, result.TaskControl.ProgressToken) .ConfigureAwait(false); if (extendedData) { var coreentries = result.FileVersions.Items.Cast().ToArray(); - var metadata = await db.GetMetadataForFilesetIds(coreentries.Select(e => e.FileId), result.TaskControl.ProgressToken).ConfigureAwait(false); + var metadata = await db.GetMetadataForFilesetIdsAsync(coreentries.Select(e => e.FileId), result.TaskControl.ProgressToken).ConfigureAwait(false); for (int i = 0; i < coreentries.Length; i++) { diff --git a/Duplicati/Library/Main/Operation/SetLocksHandler.cs b/Duplicati/Library/Main/Operation/SetLocksHandler.cs index 9c78aa1b7..f684ee9c6 100644 --- a/Duplicati/Library/Main/Operation/SetLocksHandler.cs +++ b/Duplicati/Library/Main/Operation/SetLocksHandler.cs @@ -86,7 +86,7 @@ namespace Duplicati.Library.Main.Operation foreach (var filesetId in filesetIds) { - await foreach ((var volumeName, var lockUntil) in database.GetRemoteVolumesDependingOnFilesets([filesetId], m_result.TaskControl.ProgressToken).ConfigureAwait(false)) + await foreach ((var volumeName, var lockUntil) in database.GetRemoteVolumesDependingOnFilesetsAsync([filesetId], m_result.TaskControl.ProgressToken).ConfigureAwait(false)) { readCount++; try @@ -106,7 +106,7 @@ namespace Duplicati.Library.Main.Operation Log.WriteInformationMessage(LOGTAG, "SetObjectLock", "Setting object lock for {0} in fileset {1} until {2:u}", volumeName, filesetId, lockUntilUtc); await backendManager.SetObjectLockUntilAsync(volumeName, lockUntilUtc, m_result.TaskControl.ProgressToken).ConfigureAwait(false); // Update the lock expiration time in the database - await database.UpdateRemoteVolumeLockExpiration(volumeName, lockUntilUtc, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await database.UpdateRemoteVolumeLockExpirationAsync(volumeName, lockUntilUtc, m_result.TaskControl.ProgressToken).ConfigureAwait(false); } updatedCount++; } @@ -141,7 +141,7 @@ namespace Duplicati.Library.Main.Operation foreach (var versionTime in versions!) { var matched = await db - .GetFilesetIDs(versionTime, null, true, token) + .GetFilesetIDsAsync(versionTime, null, true, token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); @@ -154,7 +154,7 @@ namespace Duplicati.Library.Main.Operation else if (m_options.AllVersions) { filesetIds.AddRange(await db - .FilesetTimes(token) + .FilesetTimesAsync(token) .Select(x => x.Key) .ToArrayAsync(token) .ConfigureAwait(false)); @@ -162,7 +162,7 @@ namespace Duplicati.Library.Main.Operation else { var matched = await db - .GetFilesetIDs(m_options.Time, m_options.Version, false, token) + .GetFilesetIDsAsync(m_options.Time, m_options.Version, false, token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); diff --git a/Duplicati/Library/Main/Operation/TestFilterHandler.cs b/Duplicati/Library/Main/Operation/TestFilterHandler.cs index a1b1566cf..92712ebf3 100644 --- a/Duplicati/Library/Main/Operation/TestFilterHandler.cs +++ b/Duplicati/Library/Main/Operation/TestFilterHandler.cs @@ -48,10 +48,10 @@ namespace Duplicati.Library.Main.Operation { var stopToken = m_result.TaskControl.ProgressToken; - using (var provider = await BackupHandler.GetSourceProvider(sources, m_options, stopToken).ConfigureAwait(false)) + using (var provider = await BackupHandler.GetSourceProviderAsync(sources, m_options, stopToken).ConfigureAwait(false)) { Backup.Channels channels = new(); - var source = Backup.FileEnumerationProcess.Run(channels, provider, null, + var source = Backup.FileEnumerationProcess.RunAsync(channels, provider, null, m_options.FileAttributeFilter, filter, m_options.SymlinkPolicy, m_options.HardlinkPolicy, m_options.DisableBackupExclusionXattr, m_options.ExcludeEmptyFolders, m_options.IgnoreFilenames, BackupHandler.GetBlacklistedPaths(m_options), null, m_result.TaskControl, null, stopToken); @@ -62,7 +62,7 @@ namespace Duplicati.Library.Main.Operation }, async self => { - while (await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + while (await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { var entry = await self.source.ReadAsync(); var fa = entry.IsFolder diff --git a/Duplicati/Library/Main/Operation/TestHandler.cs b/Duplicati/Library/Main/Operation/TestHandler.cs index 66d5fe5e6..abf930b1b 100644 --- a/Duplicati/Library/Main/Operation/TestHandler.cs +++ b/Duplicati/Library/Main/Operation/TestHandler.cs @@ -54,16 +54,16 @@ namespace Duplicati.Library.Main.Operation throw new UserInformationException(LC.L("Database file does not exist: {0}", m_options.Dbpath), "DatabaseDoesNotExist"); await using var db = await LocalTestDatabase.CreateAsync(m_options.Dbpath, null, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await Utility.UpdateOptionsFromDb(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.UpdateOptionsFromDbAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await Utility.VerifyOptionsAndUpdateDatabase(db, m_options, m_result.TaskControl.ProgressToken) + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, m_options, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); await db - .VerifyConsistency(m_options.Blocksize, m_options.BlockhashSize, !m_options.DisableFilelistConsistencyChecks, m_result.TaskControl.ProgressToken) + .VerifyConsistencyAsync(m_options.Blocksize, m_options.BlockhashSize, !m_options.DisableFilelistConsistencyChecks, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); - await FilelistProcessor.VerifyRemoteList(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await FilelistProcessor.VerifyRemoteListAsync(backendManager, m_options, db, m_result.BackendWriter, latestVolumesOnly: true, verifyMode: FilelistProcessor.VerifyMode.VerifyOnly, m_result.TaskControl.ProgressToken).ConfigureAwait(false); await DoRunAsync(samples, db, backendManager).ConfigureAwait(false); await db.Transaction .CommitAsync("TestHandlerComplete") @@ -73,7 +73,7 @@ namespace Duplicati.Library.Main.Operation public async Task DoRunAsync(long samples, LocalTestDatabase db, IBackendManager backend) { var files = await db - .SelectTestTargets(samples, m_options, m_result.TaskControl.ProgressToken) + .SelectTestTargetsAsync(samples, m_options, m_result.TaskControl.ProgressToken) .ToListAsync(cancellationToken: m_result.TaskControl.ProgressToken) .ConfigureAwait(false); @@ -89,7 +89,7 @@ namespace Duplicati.Library.Main.Operation var vol = new RemoteVolume(name, hash, size); try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { await backend.WaitForEmptyAsync(db, m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; @@ -101,7 +101,7 @@ namespace Duplicati.Library.Main.Operation KeyValuePair>> res; using (tf) - res = await TestVolumeInternals(db, vol, tf, m_options, m_options.FullBlockVerification ? 1.0 : 0.2, m_result.TaskControl.ProgressToken) + res = await TestVolumeInternalsAsync(db, vol, tf, m_options, m_options.FullBlockVerification ? 1.0 : 0.2, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); var parsedInfo = VolumeBase.ParseFilename(vol.Name); @@ -127,7 +127,7 @@ namespace Duplicati.Library.Main.Operation if (res.Value == null || !res.Value.Any()) { var rv = await db - .GetRemoteVolume(vol.Name, m_result.TaskControl.ProgressToken) + .GetRemoteVolumeAsync(vol.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); if (rv.ID < 0) @@ -142,7 +142,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "CaptureHashAndSize", LC.L("Successfully captured hash and size for {0}, updating database", vol.Name)); await db - .UpdateRemoteVolume(vol.Name, RemoteVolumeState.Verified, vol.Size, vol.Hash, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(vol.Name, RemoteVolumeState.Verified, vol.Size, vol.Hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -151,7 +151,7 @@ namespace Duplicati.Library.Main.Operation } await db - .UpdateVerificationCount(vol.Name, m_result.TaskControl.ProgressToken) + .UpdateVerificationCountAsync(vol.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } catch (Exception ex) @@ -183,7 +183,7 @@ namespace Duplicati.Library.Main.Operation { try { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { m_result.EndTime = DateTime.UtcNow; return; @@ -200,7 +200,7 @@ namespace Duplicati.Library.Main.Operation (var tf, var hash, var size) = await backend.GetWithInfoAsync(f.Name, f.Hash, f.Size, m_result.TaskControl.ProgressToken).ConfigureAwait(false); using (tf) - res = await TestVolumeInternals(db, f, tf, m_options, 1, m_result.TaskControl.ProgressToken) + res = await TestVolumeInternalsAsync(db, f, tf, m_options, 1, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.AddResult(res.Key, res.Value); @@ -216,7 +216,7 @@ namespace Duplicati.Library.Main.Operation { Logging.Log.WriteInformationMessage(LOGTAG, "CapturedHashAndSize", LC.L("Successfully captured hash and size for {0}, updating database", f.Name)); await db - .UpdateRemoteVolume(f.Name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) + .UpdateRemoteVolumeAsync(f.Name, RemoteVolumeState.Verified, size, hash, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); } } @@ -229,7 +229,7 @@ namespace Duplicati.Library.Main.Operation } await db - .UpdateVerificationCount(f.Name, m_result.TaskControl.ProgressToken) + .UpdateVerificationCountAsync(f.Name, m_result.TaskControl.ProgressToken) .ConfigureAwait(false); m_result.AddResult(f.Name, []); } @@ -270,7 +270,7 @@ namespace Duplicati.Library.Main.Operation /// A value between 0 and 1 that indicates how many blocks are tested in a dblock file. /// A cancellation token to cancel the operation. /// A task that when awaited returns a key-value pair where the key is the volume name and the value is a list of test results. - public static async Task>>> TestVolumeInternals(LocalTestDatabase db, IRemoteVolume vol, string tf, Options options, double sample_percent, CancellationToken cancellationToken) + public static async Task>>> TestVolumeInternalsAsync(LocalTestDatabase db, IRemoteVolume vol, string tf, Options options, double sample_percent, CancellationToken cancellationToken) { var hashsize = HashFactory.HashSizeBytes(options.BlockHashAlgorithm); var parsedInfo = Volumes.VolumeBase.ParseFilename(vol.Name); @@ -281,15 +281,15 @@ namespace Duplicati.Library.Main.Operation case RemoteVolumeType.Files: //Compare with db and see if all files are accounted for // with correct file hashes and blocklist hashes - await using (var fl = await db.CreateFilelist(vol.Name, cancellationToken).ConfigureAwait(false)) + await using (var fl = await db.CreateFilelistAsync(vol.Name, cancellationToken).ConfigureAwait(false)) { using (var rd = new Volumes.FilesetVolumeReader(parsedInfo.CompressionModule, tf, options)) foreach (var f in rd.Files) await fl - .Add(f.Path, f.Size, f.Hash, f.Metasize, f.Metahash, f.BlocklistHashes, f.Type, f.Time, cancellationToken) + .AddAsync(f.Path, f.Size, f.Hash, f.Metasize, f.Metahash, f.BlocklistHashes, f.Type, f.Time, cancellationToken) .ConfigureAwait(false); - return new KeyValuePair>>(vol.Name, await fl.Compare(cancellationToken).ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false)); + return new KeyValuePair>>(vol.Name, await fl.CompareAsync(cancellationToken).ToListAsync(cancellationToken: cancellationToken).ConfigureAwait(false)); } case RemoteVolumeType.Index: @@ -302,16 +302,16 @@ namespace Duplicati.Library.Main.Operation foreach (var v in rd.Volumes) { blocklinks.Add(new Tuple(v.Filename, v.Hash, v.Length)); - await using (var bl = await db.CreateBlocklist(v.Filename, cancellationToken).ConfigureAwait(false)) + await using (var bl = await db.CreateBlocklistAsync(v.Filename, cancellationToken).ConfigureAwait(false)) { foreach (var h in v.Blocks) await bl - .AddBlock(h.Key, h.Value, cancellationToken) + .AddBlockAsync(h.Key, h.Value, cancellationToken) .ConfigureAwait(false); combined.AddRange( await bl - .Compare(cancellationToken) + .CompareAsync(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); @@ -321,16 +321,16 @@ namespace Duplicati.Library.Main.Operation if (options.IndexfilePolicy == Options.IndexFileStrategy.Full) { var hashesPerBlock = options.Blocksize / options.BlockhashSize; - await using (var bl = await db.CreateBlocklistHashList(vol.Name, cancellationToken).ConfigureAwait(false)) + await using (var bl = await db.CreateBlocklistHashListAsync(vol.Name, cancellationToken).ConfigureAwait(false)) { foreach (var b in rd.BlockLists) await bl - .AddBlockHash(b.Hash, b.Length, cancellationToken) + .AddBlockHashAsync(b.Hash, b.Length, cancellationToken) .ConfigureAwait(false); combined.AddRange( await bl - .Compare(hashesPerBlock, options.BlockhashSize, options.Blocksize, cancellationToken) + .CompareAsync(hashesPerBlock, options.BlockhashSize, options.Blocksize, cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); @@ -339,16 +339,16 @@ namespace Duplicati.Library.Main.Operation } // Compare with db and see that all blocklists are listed - await using (var il = await db.CreateIndexlist(vol.Name, cancellationToken).ConfigureAwait(false)) + await using (var il = await db.CreateIndexlistAsync(vol.Name, cancellationToken).ConfigureAwait(false)) { foreach (var t in blocklinks) await il - .AddBlockLink(t.Item1, t.Item2, t.Item3, cancellationToken) + .AddBlockLinkAsync(t.Item1, t.Item2, t.Item3, cancellationToken) .ConfigureAwait(false); combined.AddRange( await il - .Compare(cancellationToken) + .CompareAsync(cancellationToken) .ToArrayAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ); @@ -357,13 +357,13 @@ namespace Duplicati.Library.Main.Operation return new KeyValuePair>>(vol.Name, combined); case RemoteVolumeType.Blocks: using (var blockhasher = HashFactory.CreateHasher(options.BlockHashAlgorithm)) - await using (var bl = await db.CreateBlocklist(vol.Name, cancellationToken).ConfigureAwait(false)) + await using (var bl = await db.CreateBlocklistAsync(vol.Name, cancellationToken).ConfigureAwait(false)) using (var rd = new Volumes.BlockVolumeReader(parsedInfo.CompressionModule, tf, options)) { //Verify that all blocks are in the file foreach (var b in rd.Blocks) await bl - .AddBlock(b.Key, b.Value, cancellationToken) + .AddBlockAsync(b.Key, b.Value, cancellationToken) .ConfigureAwait(false); //Select random blocks and verify their hashes match the filename and size @@ -393,7 +393,7 @@ namespace Duplicati.Library.Main.Operation vol.Name, changes.Union( await bl - .Compare(cancellationToken) + .CompareAsync(cancellationToken) .ToListAsync(cancellationToken: cancellationToken) .ConfigureAwait(false) ) @@ -413,7 +413,7 @@ namespace Duplicati.Library.Main.Operation foreach (var vol in faultyIndexFiles) { - if (!await m_result.TaskControl.ProgressRendevouz().ConfigureAwait(false)) + if (!await m_result.TaskControl.ProgressRendevouzAsync().ConfigureAwait(false)) { m_result.EndTime = DateTime.UtcNow; return; @@ -423,7 +423,7 @@ namespace Duplicati.Library.Main.Operation try { var w = newEntry = new IndexVolumeWriter(m_options); - await RepairHandler.RunRepairDindex(backendManager, repairdb, w, vol, m_options, cancellationToken).ConfigureAwait(false); + await RepairHandler.RunRepairDindexAsync(backendManager, repairdb, w, vol, m_options, cancellationToken).ConfigureAwait(false); if (m_options.Dryrun) { Logging.Log.WriteDryrunMessage(LOGTAG, "ReplaceFaultyIndexFile", LC.L("Would replace faulty index file {0} with {1}", vol.Name, w.RemoteFilename)); diff --git a/Duplicati/Library/Main/Operation/VacuumHandler.cs b/Duplicati/Library/Main/Operation/VacuumHandler.cs index 6c00a61a8..7db95e41d 100644 --- a/Duplicati/Library/Main/Operation/VacuumHandler.cs +++ b/Duplicati/Library/Main/Operation/VacuumHandler.cs @@ -42,7 +42,7 @@ namespace Duplicati.Library.Main.Operation .ConfigureAwait(false); m_result.OperationProgressUpdater.UpdatePhase(OperationPhase.Vacuum_Running); await db.Transaction.CommitAsync("Vacuum", false, m_result.TaskControl.ProgressToken).ConfigureAwait(false); - await db.Vacuum(m_result.TaskControl.ProgressToken).ConfigureAwait(false); + await db.VacuumAsync(m_result.TaskControl.ProgressToken).ConfigureAwait(false); m_result.EndTime = DateTime.UtcNow; } } diff --git a/Duplicati/Library/Main/ProcessController.cs b/Duplicati/Library/Main/ProcessController.cs index a1f5375b9..1c46ee228 100755 --- a/Duplicati/Library/Main/ProcessController.cs +++ b/Duplicati/Library/Main/ProcessController.cs @@ -23,6 +23,7 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using CoCoL; using Duplicati.Library.Utility; namespace Duplicati.Library.Main @@ -142,7 +143,7 @@ namespace Duplicati.Library.Main { Logging.Log.WriteWarningMessage(LOGTAG, "SleepPreventionError", ex, "Failed to set sleep prevention"); } - }); + }).FireAndForget(); m_runningSleepPrevention = true; } diff --git a/Duplicati/Library/Main/ResultClasses.cs b/Duplicati/Library/Main/ResultClasses.cs index 4952a4398..396617f2c 100644 --- a/Duplicati/Library/Main/ResultClasses.cs +++ b/Duplicati/Library/Main/ResultClasses.cs @@ -266,10 +266,10 @@ namespace Duplicati.Library.Main /// The database to flush the log messages to. /// The cancellation token to use. /// A task that completes when the log messages have been flushed. - public async Task FlushLog(LocalDatabase db, CancellationToken token) + public async Task FlushLogAsync(LocalDatabase db, CancellationToken token) { if (m_parent != null) - await m_parent.FlushLog(db, token).ConfigureAwait(false); + await m_parent.FlushLogAsync(db, token).ConfigureAwait(false); else { await m_lock.WaitAsync(token).ConfigureAwait(false); @@ -279,7 +279,7 @@ namespace Duplicati.Library.Main { var el = m_dbqueue.Dequeue(); await db - .LogMessage(el.Type, el.Message, el.Exception, token) + .LogMessageAsync(el.Type, el.Message, el.Exception, token) .ConfigureAwait(false); } } diff --git a/Duplicati/Library/Main/Utility.cs b/Duplicati/Library/Main/Utility.cs index c3bf2d9ac..8775da4de 100644 --- a/Duplicati/Library/Main/Utility.cs +++ b/Duplicati/Library/Main/Utility.cs @@ -139,9 +139,9 @@ namespace Duplicati.Library.Main /// The options to update. /// A cancellation token to cancel the operation. /// A task that completes when the options have been updated. - internal static async Task UpdateOptionsFromDb(LocalDatabase db, Options options, CancellationToken cancellationToken) + internal static async Task UpdateOptionsFromDbAsync(LocalDatabase db, Options options, CancellationToken cancellationToken) { - var opts = await db.GetDbOptions(cancellationToken).ConfigureAwait(false); + var opts = await db.GetDbOptionsAsync(cancellationToken).ConfigureAwait(false); foreach (var option in PersistedOptionMappings) RestoreOptionFromDb(opts, options, option.DatabaseKey, option.OptionKey, option.RestoreValue); @@ -159,9 +159,9 @@ namespace Duplicati.Library.Main /// The database to check. /// A cancellation token to cancel the operation. /// A task that when awaited returns true if the database contains options that need to be verified; false otherwise. - internal static async Task ContainsOptionsForVerification(LocalDatabase db, CancellationToken cancellationToken) + internal static async Task ContainsOptionsForVerificationAsync(LocalDatabase db, CancellationToken cancellationToken) { - var opts = await db.GetDbOptions(cancellationToken).ConfigureAwait(false); + var opts = await db.GetDbOptionsAsync(cancellationToken).ConfigureAwait(false); await db.Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); return PersistedOptionMappings .Select(option => option.DatabaseKey) @@ -176,10 +176,10 @@ namespace Duplicati.Library.Main /// The options to verify. /// A cancellation token to cancel the operation. /// A task that completes when the options have been verified and the database has been updated if needed. - internal static async Task VerifyOptionsAndUpdateDatabase(LocalDatabase db, Options options, CancellationToken cancellationToken) + internal static async Task VerifyOptionsAndUpdateDatabaseAsync(LocalDatabase db, Options options, CancellationToken cancellationToken) { var newDict = GetPersistedOptionValues(options); - var opts = await db.GetDbOptions(cancellationToken).ConfigureAwait(false); + var opts = await db.GetDbOptionsAsync(cancellationToken).ConfigureAwait(false); if (options.NoEncryption) { @@ -229,11 +229,11 @@ namespace Duplicati.Library.Main } //Extra sanity check - if (await db.GetBlocksLargerThan(options.Blocksize, cancellationToken).ConfigureAwait(false) > 0) + if (await db.GetBlocksLargerThanAsync(options.Blocksize, cancellationToken).ConfigureAwait(false) > 0) throw new Duplicati.Library.Interface.UserInformationException("You have attempted to change the block-size on an existing backup, which is not supported. Please configure a new clean backup if you want to change the block-size.", "BlockSizeChangeNotSupported"); if (needsUpdate) - await PersistOptionsToDb(db, options, opts, cancellationToken).ConfigureAwait(false); + await PersistOptionsToDbAsync(db, options, opts, cancellationToken).ConfigureAwait(false); } /// @@ -244,10 +244,10 @@ namespace Duplicati.Library.Main /// The operation description to record in the database. /// A cancellation token to cancel the operation. /// A task that completes when the options have been stored. - public static async Task PersistOptionsToDatabaseWithoutValidation(string dbpath, Options options, string operation, CancellationToken cancellationToken) + public static async Task PersistOptionsToDatabaseWithoutValidationAsync(string dbpath, Options options, string operation, CancellationToken cancellationToken) { await using var db = await LocalDatabase.CreateLocalDatabaseAsync(dbpath, operation, true, null, cancellationToken).ConfigureAwait(false); - await PersistOptionsToDb(db, options, null, cancellationToken).ConfigureAwait(false); + await PersistOptionsToDbAsync(db, options, null, cancellationToken).ConfigureAwait(false); await db.Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } @@ -260,10 +260,10 @@ namespace Duplicati.Library.Main /// The operation description to record in the database. /// A cancellation token to cancel the operation. /// A task that completes when the options have been restored. - public static async Task UpdateOptionsFromDatabase(string dbpath, Options options, string operation, CancellationToken cancellationToken) + public static async Task UpdateOptionsFromDatabaseAsync(string dbpath, Options options, string operation, CancellationToken cancellationToken) { await using var db = await LocalDatabase.CreateLocalDatabaseAsync(dbpath, operation, true, null, cancellationToken).ConfigureAwait(false); - await UpdateOptionsFromDb(db, options, cancellationToken).ConfigureAwait(false); + await UpdateOptionsFromDbAsync(db, options, cancellationToken).ConfigureAwait(false); await db.Transaction.CommitAsync(cancellationToken).ConfigureAwait(false); } @@ -294,10 +294,10 @@ namespace Duplicati.Library.Main /// The existing options to merge with, or null /// A cancellation token to cancel the operation. /// An awaitable task - private static async Task PersistOptionsToDb(LocalDatabase db, Options options, IDictionary existingOptions, CancellationToken cancellationToken) + private static async Task PersistOptionsToDbAsync(LocalDatabase db, Options options, IDictionary existingOptions, CancellationToken cancellationToken) { - existingOptions ??= await db.GetDbOptions(cancellationToken).ConfigureAwait(false); - await db.SetDbOptions(BuildStoredOptions(existingOptions, options), cancellationToken).ConfigureAwait(false); + existingOptions ??= await db.GetDbOptionsAsync(cancellationToken).ConfigureAwait(false); + await db.SetDbOptionsAsync(BuildStoredOptions(existingOptions, options), cancellationToken).ConfigureAwait(false); } /// diff --git a/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs b/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs index d03dc9732..e740d7570 100644 --- a/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs +++ b/Duplicati/Library/Main/Volumes/BlockVolumeWriter.cs @@ -42,7 +42,7 @@ namespace Duplicati.Library.Main.Volumes { } - public async Task AddBlock(string hash, byte[] data, int offset, int size, CompressionHint hint) + public async Task AddBlockAsync(string hash, byte[] data, int offset, int size, CompressionHint hint) { m_blocks++; m_sourcesize += size; diff --git a/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs b/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs index 26c4497d9..0ea31d3ca 100644 --- a/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs +++ b/Duplicati/Library/Main/Volumes/FilesetVolumeWriter.cs @@ -85,19 +85,13 @@ namespace Duplicati.Library.Main.Volumes } } - public async Task AddFile(string name, string filehash, long size, DateTime lastmodified, string metahash, long metasize, string metablockhash, string blockhash, long blocksize, IAsyncEnumerable blocklisthashes, IEnumerable metablocklisthashes) - { - await AddFileEntry(FilelistEntryType.File, name, filehash, size, lastmodified, metahash, metasize, metablockhash, blockhash, blocksize, blocklisthashes, metablocklisthashes) - .ConfigureAwait(false); - } + public Task AddFileAsync(string name, string filehash, long size, DateTime lastmodified, string metahash, long metasize, string metablockhash, string blockhash, long blocksize, IAsyncEnumerable blocklisthashes, IEnumerable metablocklisthashes) + => AddFileEntryAsync(FilelistEntryType.File, name, filehash, size, lastmodified, metahash, metasize, metablockhash, blockhash, blocksize, blocklisthashes, metablocklisthashes); - public async Task AddAlternateStream(string name, string filehash, long size, DateTime lastmodified, string metahash, string metablockhash, long metasize, string blockhash, long blocksize, IAsyncEnumerable blocklisthashes, IEnumerable metablocklisthashes) - { - await AddFileEntry(FilelistEntryType.AlternateStream, name, filehash, size, lastmodified, metahash, metasize, metablockhash, blockhash, blocksize, blocklisthashes, metablocklisthashes) - .ConfigureAwait(false); - } + public Task AddAlternateStreamAsync(string name, string filehash, long size, DateTime lastmodified, string metahash, string metablockhash, long metasize, string blockhash, long blocksize, IAsyncEnumerable blocklisthashes, IEnumerable metablocklisthashes) + => AddFileEntryAsync(FilelistEntryType.AlternateStream, name, filehash, size, lastmodified, metahash, metasize, metablockhash, blockhash, blocksize, blocklisthashes, metablocklisthashes); - private async Task AddFileEntry(FilelistEntryType type, string name, string filehash, long size, DateTime lastmodified, string metahash, long metasize, string metablockhash, string blockhash, long blocksize, IAsyncEnumerable blocklisthashes, IEnumerable metablocklisthashes) + private async Task AddFileEntryAsync(FilelistEntryType type, string name, string filehash, long size, DateTime lastmodified, string metahash, long metasize, string metablockhash, string blockhash, long blocksize, IAsyncEnumerable blocklisthashes, IEnumerable metablocklisthashes) { m_filecount++; m_writer.WriteStartObject(); @@ -241,8 +235,8 @@ namespace Duplicati.Library.Main.Volumes /// The time to increment by each probe /// The maximum number of tries to probe /// The first unused filename - internal static Task ProbeUnusedFilenameName(DatabaseCommon database, Options options, DateTime start, CancellationToken cancellationToken, TimeSpan increment = default, int maxTries = 60) - => ProbeUnusedFilenameName((name, CancellationToken) => database.GetRemoteVolumeIDAsync(name, cancellationToken), options, start, cancellationToken, increment, maxTries); + internal static Task ProbeUnusedFilenameNameAsync(DatabaseCommon database, Options options, DateTime start, CancellationToken cancellationToken, TimeSpan increment = default, int maxTries = 60) + => ProbeUnusedFilenameNameAsync((name, CancellationToken) => database.GetRemoteVolumeIDAsync(name, cancellationToken), options, start, cancellationToken, increment, maxTries); /// /// Probes for an unused filename, using the current time as a starting point. @@ -253,8 +247,8 @@ namespace Duplicati.Library.Main.Volumes /// The time to increment by each probe. /// The maximum number of tries to probe. /// A task that when awaited returns the first unused filename. - internal static async Task ProbeUnusedFilenameName(LocalDatabase database, Options options, DateTime start, CancellationToken cancellationToken, TimeSpan increment = default, int maxTries = 60) - => await ProbeUnusedFilenameName(database.GetRemoteVolumeID, options, start, cancellationToken, increment, maxTries) + internal static async Task ProbeUnusedFilenameNameAsync(LocalDatabase database, Options options, DateTime start, CancellationToken cancellationToken, TimeSpan increment = default, int maxTries = 60) + => await ProbeUnusedFilenameNameAsync(database.GetRemoteVolumeIDAsync, options, start, cancellationToken, increment, maxTries) .ConfigureAwait(false); /// @@ -266,7 +260,7 @@ namespace Duplicati.Library.Main.Volumes /// The time to increment by each probe /// The maximum number of tries to probe /// The first unused filename - private static async Task ProbeUnusedFilenameName(Func> ProbeForId, Options options, DateTime start, CancellationToken cancellationToken, TimeSpan increment = default, int maxTries = 60) + private static async Task ProbeUnusedFilenameNameAsync(Func> ProbeForId, Options options, DateTime start, CancellationToken cancellationToken, TimeSpan increment = default, int maxTries = 60) { if (increment == default) increment = TimeSpan.FromSeconds(1); diff --git a/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs b/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs index 020e87847..7c2b0bc56 100644 --- a/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs +++ b/Duplicati/Library/RemoteControl/KeepRemoteConnection.cs @@ -24,6 +24,7 @@ using System.Net; using System.Net.Http.Headers; using System.Security.Cryptography; using System.Text.Json; +using CoCoL; using Duplicati.Library.AutoUpdater; using Duplicati.Library.Logging; using Duplicati.Library.Utility; @@ -236,14 +237,14 @@ public class KeepRemoteConnection : IDisposable _refreshSettingsBy = refreshSettingsBy ?? DateTimeOffset.MinValue; _client = new Websocket.Client.WebsocketClient(new Uri(serverUrl)); - _runnerTask = RunMainLoop(forceConnect); + _runnerTask = RunMainLoopAsync(forceConnect); } /// /// Runs the inner loop of the connection /// /// If the connection should be force enabled, ignoring re-connect delays - private async Task RunMainLoop(bool forceConnect) + private async Task RunMainLoopAsync(bool forceConnect) { _client.ReconnectTimeout = NoResponseTimeout; _client.LostReconnectTimeout = ReconnectInterval; @@ -251,7 +252,7 @@ public class KeepRemoteConnection : IDisposable // Set up the periodic refreshers using var reconnectHelper = new PeriodicRefresher( - Timeout.InfiniteTimeSpan, + System.Threading.Timeout.InfiniteTimeSpan, ReconnectInterval, async token => { @@ -274,7 +275,7 @@ public class KeepRemoteConnection : IDisposable { SetState(ConnectionState.Error); Log.WriteMessage(LogMessageType.Warning, LogTag, "WebsocketDisconnect", "No response from server"); - _client.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "No response"); + _client.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "No response").FireAndForget(); } SendEnvelope(new EnvelopedMessage() @@ -291,10 +292,10 @@ public class KeepRemoteConnection : IDisposable }, _cancellationTokenSource.Token); - using var certificateRfreshHelper = new PeriodicRefresher( + using var certificateRefreshHelper = new PeriodicRefresher( CertificateRefreshInterval, MinimumCertificateRefreshInterval, - RefreshCertificates, + RefreshCertificatesAsync, _cancellationTokenSource.Token); _client.DisconnectionHappened.Subscribe(info => @@ -313,62 +314,80 @@ public class KeepRemoteConnection : IDisposable } }); - _client.MessageReceived.Subscribe(async msg => + _client.MessageReceived.Subscribe(msg => OnMessageAsync(msg, certificateRefreshHelper, reconnectHelper).Await()); + + // Start the connection + if (forceConnect || !IsAutoReconnectDisabled()) + reconnectHelper.Signal(); + + var t = await Task.WhenAny( + heartbeatHelper.RunLoopAsync(), + reconnectHelper.RunLoopAsync(), + certificateRefreshHelper.RunLoopAsync() + ); + + await _cancellationTokenSource.CancelAsync(); + + // Re-throw any exceptions + await t; + } + + private async Task OnMessageAsync(Websocket.Client.ResponseMessage msg, PeriodicRefresher certificateRefreshHelper, PeriodicRefresher reconnectHelper) + { + // Ignore messages if we are in an error state + if (_state == ConnectionState.Error) + return; + + _lastMessageReceived = DateTimeOffset.Now; + if (_state == ConnectionState.NotConnected) + Log.WriteMessage(LogMessageType.Verbose, LogTag, "WebsocketMessage", "Received message from server: {0}", msg); + else // Encrypted messages are not logged, as the content has no meaning before being decrypted + Log.WriteMessage(LogMessageType.Verbose, LogTag, "WebsocketMessage", "Received encrypted message from server"); + + try { - // Ignore messages if we are in an error state - if (_state == ConnectionState.Error) - return; + if (string.IsNullOrWhiteSpace(msg.Text)) + throw new ProtocolViolationException("Empty message"); - _lastMessageReceived = DateTimeOffset.Now; - if (_state == ConnectionState.NotConnected) - Log.WriteMessage(LogMessageType.Verbose, LogTag, "WebsocketMessage", "Received message from server: {0}", msg); - else // Encrypted messages are not logged, as the content has no meaning before being decrypted - Log.WriteMessage(LogMessageType.Verbose, LogTag, "WebsocketMessage", "Received encrypted message from server"); - - try + if (_serverCertificate == null || _serverPublicKey == null || _state == ConnectionState.NotConnected) { - if (string.IsNullOrWhiteSpace(msg.Text)) - throw new ProtocolViolationException("Empty message"); + // Should be safe from replay, as the response is encrypted with the server public key + // So even a replay attack would not let the attacker know the client's token + var welcomeEnvelope = EnvelopedMessage.ForceParse(msg.Text); + if (welcomeEnvelope.GetMessageType() != MessageType.Welcome) + throw new ProtocolViolationException("Expected welcome message"); + if (string.IsNullOrWhiteSpace(welcomeEnvelope.Payload)) + throw new ProtocolViolationException("No payload in welcome message"); - if (_serverCertificate == null || _serverPublicKey == null || _state == ConnectionState.NotConnected) + var welcomeMessage = welcomeEnvelope.GetPayload() + ?? throw new ProtocolViolationException("Invalid welcome message"); + + if (string.IsNullOrWhiteSpace(welcomeMessage.PublicKeyHash)) + throw new ProtocolViolationException("No public key hash in welcome message"); + _serverCertificate = _serverKeys.FirstOrDefault(x => x.PublicKeyHash == welcomeMessage.PublicKeyHash && x.Expiry > DateTimeOffset.Now); + + if (_serverCertificate == null) { - // Should be safe from replay, as the response is encrypted with the server public key - // So even a replay attack would not let the attacker know the client's token - var welcomeEnvelope = EnvelopedMessage.ForceParse(msg.Text); - if (welcomeEnvelope.GetMessageType() != MessageType.Welcome) - throw new ProtocolViolationException("Expected welcome message"); - if (string.IsNullOrWhiteSpace(welcomeEnvelope.Payload)) - throw new ProtocolViolationException("No payload in welcome message"); + certificateRefreshHelper.Signal(); + throw new ProtocolViolationException("No valid server certificate"); + } - var welcomeMessage = welcomeEnvelope.GetPayload() - ?? throw new ProtocolViolationException("Invalid welcome message"); + try + { + var tmp = RSA.Create(); + tmp.ImportFromPem(_serverCertificate.PublicKey); + _serverPublicKey = tmp; + } + catch + { + certificateRefreshHelper.Signal(); + throw new ProtocolViolationException("Invalid server certificate"); + } - if (string.IsNullOrWhiteSpace(welcomeMessage.PublicKeyHash)) - throw new ProtocolViolationException("No public key hash in welcome message"); - _serverCertificate = _serverKeys.FirstOrDefault(x => x.PublicKeyHash == welcomeMessage.PublicKeyHash && x.Expiry > DateTimeOffset.Now); + SetState(ConnectionState.WelcomeReceived); - if (_serverCertificate == null) - { - certificateRfreshHelper.Signal(); - throw new ProtocolViolationException("No valid server certificate"); - } - - try - { - var tmp = RSA.Create(); - tmp.ImportFromPem(_serverCertificate.PublicKey); - _serverPublicKey = tmp; - } - catch - { - certificateRfreshHelper.Signal(); - throw new ProtocolViolationException("Invalid server certificate"); - } - - SetState(ConnectionState.WelcomeReceived); - - // Prepare basic metadata and allow additional metadata to be added - var metadata = await _onConnect(new Dictionary() { + // Prepare basic metadata and allow additional metadata to be added + var metadata = await _onConnect(new Dictionary() { { "client-version", UpdaterManager.SelfVersion?.Version ?? "0.0.0.0" }, { "client-id", ClientId }, { "client-uptime", (DateTime.Now - Process.GetCurrentProcess().StartTime).ToString() }, @@ -380,114 +399,97 @@ public class KeepRemoteConnection : IDisposable { "update-channel", UpdaterManager.CurrentChannel.ToString() } }); - SendEnvelope( - welcomeEnvelope.RespondWith( - new AuthMessage( - _token, - ClientKey.ExportRSAPublicKeyPem(), - UpdaterManager.SelfVersion?.Version ?? "0.0.0.0", - PROTOCOL_VERSION, - metadata - ), - "auth" + SendEnvelope( + welcomeEnvelope.RespondWith( + new AuthMessage( + _token, + ClientKey.ExportRSAPublicKeyPem(), + UpdaterManager.SelfVersion?.Version ?? "0.0.0.0", + PROTOCOL_VERSION, + metadata ), - force: true); - return; - } - - if (_serverCertificate == null || _serverPublicKey == null || _serverCertificate.HasExpired()) - { - certificateRfreshHelper.Signal(); - throw new ProtocolViolationException("No valid server certificate"); - } - - var envelope = TransportHelper.ParseFromEncryptedMessage(msg.Text, ClientKey); - if (_state == ConnectionState.WelcomeReceived) - { - if (envelope.GetMessageType() != MessageType.Auth) - throw new ProtocolViolationException("Expected welcome message"); - - var authMessage = envelope.GetPayload(); - if (!authMessage.Accepted ?? false) - throw new ProtocolViolationException("Authentication failed"); - - SetState(ConnectionState.Authenticated); - - if ((authMessage.WillReplaceToken ?? false) && authMessage.NewToken != null) - { - _token = authMessage.NewToken; - await InvokeReKey(); - } - } - else if (_state == ConnectionState.Authenticated) - { - Log.WriteVerboseMessage(LogTag, "WebsocketMessage", "Processing message of type {0}", envelope.GetMessageType()); - switch (envelope.GetMessageType()) - { - case MessageType.Pong: - break; - - case MessageType.Command: - await _onMessage(new CommandMessage( - envelope.GetPayload(), - response => SendEnvelope(envelope.RespondWith(response)) - )); - break; - - case MessageType.Control: - await _onControl(new ControlMessage( - envelope.GetPayload(), - response => SendEnvelope(envelope.RespondWith(response)), - refreshSettingsBy => - { - _refreshSettingsBy = DateTimeOffset.FromUnixTimeMilliseconds(Math.Max(refreshSettingsBy.ToUnixTimeMilliseconds(), DateTimeOffset.UtcNow.AddSeconds(30).ToUnixTimeMilliseconds())); - _client.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Disconnect requested"); - } - )); - break; - - default: - throw new ProtocolViolationException("Unexpected message"); - } - } - else - { - throw new ProtocolViolationException("Unexpected message"); - } - - + "auth" + ), + force: true); + return; } - catch (Exception ex) + + if (_serverCertificate == null || _serverPublicKey == null || _serverCertificate.HasExpired()) { - SetState(ConnectionState.Error); - Log.WriteMessage(LogMessageType.Error, LogTag, "WebsocketMessage", ex, "Failed to process message: {0}", msg); - - await _client.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Error"); - reconnectHelper.Signal(); + certificateRefreshHelper.Signal(); + throw new ProtocolViolationException("No valid server certificate"); } - }); - // Start the connection - if (forceConnect || !IsAutoReconnectDisabled()) + var envelope = TransportHelper.ParseFromEncryptedMessage(msg.Text, ClientKey); + if (_state == ConnectionState.WelcomeReceived) + { + if (envelope.GetMessageType() != MessageType.Auth) + throw new ProtocolViolationException("Expected welcome message"); + + var authMessage = envelope.GetPayload(); + if (!authMessage.Accepted ?? false) + throw new ProtocolViolationException("Authentication failed"); + + SetState(ConnectionState.Authenticated); + + if ((authMessage.WillReplaceToken ?? false) && authMessage.NewToken != null) + { + _token = authMessage.NewToken; + await InvokeReKeyAsync(); + } + } + else if (_state == ConnectionState.Authenticated) + { + Log.WriteVerboseMessage(LogTag, "WebsocketMessage", "Processing message of type {0}", envelope.GetMessageType()); + switch (envelope.GetMessageType()) + { + case MessageType.Pong: + break; + + case MessageType.Command: + await _onMessage(new CommandMessage( + envelope.GetPayload(), + response => SendEnvelope(envelope.RespondWith(response)) + )); + break; + + case MessageType.Control: + await _onControl(new ControlMessage( + envelope.GetPayload(), + response => SendEnvelope(envelope.RespondWith(response)), + refreshSettingsBy => + { + _refreshSettingsBy = DateTimeOffset.FromUnixTimeMilliseconds(Math.Max(refreshSettingsBy.ToUnixTimeMilliseconds(), DateTimeOffset.UtcNow.AddSeconds(30).ToUnixTimeMilliseconds())); + _client.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Disconnect requested").FireAndForget(); + } + )); + break; + + default: + throw new ProtocolViolationException("Unexpected message"); + } + } + else + { + throw new ProtocolViolationException("Unexpected message"); + } + } + catch (Exception ex) + { + SetState(ConnectionState.Error); + Log.WriteMessage(LogMessageType.Error, LogTag, "WebsocketMessage", ex, "Failed to process message: {0}", msg); + + await _client.Stop(System.Net.WebSockets.WebSocketCloseStatus.NormalClosure, "Error"); reconnectHelper.Signal(); + } - var t = await Task.WhenAny( - heartbeatHelper.RunLoopAsync(), - reconnectHelper.RunLoopAsync(), - certificateRfreshHelper.RunLoopAsync() - ); - - _cancellationTokenSource.Cancel(); - - // Re-throw any exceptions - await t; } /// /// Helper method to invoke the rekey callback /// /// An awaitable task - private Task InvokeReKey() + private Task InvokeReKeyAsync() => _onReKey(new ClaimedClientData(_token, _serverUrl, _certificateUrl, _serverKeys, null, null)); /// @@ -505,7 +507,7 @@ public class KeepRemoteConnection : IDisposable /// The callback to call when a control message is received /// The callback to call when a command message is received /// - public static Task Start( + public static Task StartAsync( string serverUrl, string JWT, string certificateUrl, @@ -527,17 +529,17 @@ public class KeepRemoteConnection : IDisposable /// Gets the task representing the connection /// /// The task - public Task Run() + public Task RunAsync() => _runnerTask; /// /// Stops the connection /// /// An awaitable task - public Task Stop() + public async Task StopAsync() { - _cancellationTokenSource.Cancel(); - return _runnerTask; + await _cancellationTokenSource.CancelAsync(); + await _runnerTask; } /// @@ -623,7 +625,7 @@ public class KeepRemoteConnection : IDisposable /// /// The cancellation token /// An awaitable task - private async Task RefreshCertificates(CancellationToken cancelToken) + private async Task RefreshCertificatesAsync(CancellationToken cancelToken) { using var client = HttpClientHelper.CreateClient(); // We won't set infiniteTimeout and keep the default 100s timeout var response = await client.GetAsync(_certificateUrl, cancelToken); @@ -637,7 +639,7 @@ public class KeepRemoteConnection : IDisposable .Where(x => !x.HasExpired() && !string.IsNullOrWhiteSpace(x.PublicKeyHash) && !string.IsNullOrWhiteSpace(x.PublicKey)) .ToList(); - await InvokeReKey(); + await InvokeReKeyAsync(); } } } @@ -689,7 +691,7 @@ public class KeepRemoteConnection : IDisposable /// /// The pre-configured http client /// An awaitable task - public async Task Handle(HttpClient client) + public async Task HandleAsync(HttpClient client) { try { diff --git a/Duplicati/Library/RemoteControl/RegisterForRemote.cs b/Duplicati/Library/RemoteControl/RegisterForRemote.cs index 2ec8a6e5d..a868d37f9 100644 --- a/Duplicati/Library/RemoteControl/RegisterForRemote.cs +++ b/Duplicati/Library/RemoteControl/RegisterForRemote.cs @@ -199,7 +199,7 @@ public class RegisterForRemote : IDisposable /// The data needed to claim the machine /// Thrown if the class is not in the correct state /// Thrown if the machine could not be registered - public async Task<(RegisterClientData? RegistrationData, ClaimedClientData? ClaimedData)> Register(int? maxRetries = null, TimeSpan? retryInterval = null) + public async Task<(RegisterClientData? RegistrationData, ClaimedClientData? ClaimedData)> RegisterAsync(int? maxRetries = null, TimeSpan? retryInterval = null) { if (_state != States.NotStarted) throw new InvalidOperationException("Registration process has already started"); @@ -207,7 +207,7 @@ public class RegisterForRemote : IDisposable _state = States.Registering; try { - (_registerClientData, _claimedClientData) = await RetryHelper.Retry(() => RegisterClient(), maxRetries ?? ClientRegisterMaxRetries, retryInterval ?? ClientRegisterRetryInterval, _cancellationTokenSource.Token); + (_registerClientData, _claimedClientData) = await RetryHelper.Retry(() => RegisterClientAsync(), maxRetries ?? ClientRegisterMaxRetries, retryInterval ?? ClientRegisterRetryInterval, _cancellationTokenSource.Token); } catch (Exception ex) { @@ -261,7 +261,7 @@ public class RegisterForRemote : IDisposable /// Registers the machine with the server /// /// The data needed to claim the machine - private async Task<(RegisterClientData?, ClaimedClientData?)> RegisterClient() + private async Task<(RegisterClientData?, ClaimedClientData?)> RegisterClientAsync() { var response = await _httpClient.PostAsync(_registrationUrl, CreateMachineData(), _cancellationTokenSource.Token); @@ -300,7 +300,7 @@ public class RegisterForRemote : IDisposable /// The settings for the machine /// Thrown if the class is not in the correct state /// Thrown if the machine could not be registered - public async Task Claim() + public async Task ClaimAsync() { if (_state == States.Claimed) return _claimedClientData ?? throw new InvalidOperationException("Claimed data is null"); @@ -312,7 +312,7 @@ public class RegisterForRemote : IDisposable _cancellationTokenSource.CancelAfter(TimeSpan.FromSeconds(Math.Min(3600, Math.Max(1, _registerClientData!.MaxLifetimeSeconds)))); try { - _claimedClientData = await RetryHelper.Retry(() => CheckClientClaimed(), Math.Min(100, Math.Max(1, _registerClientData!.MaxRetries)), TimeSpan.FromSeconds(_registerClientData!.RetrySeconds), _cancellationTokenSource.Token); + _claimedClientData = await RetryHelper.Retry(() => CheckClientClaimedAsync(), Math.Min(100, Math.Max(1, _registerClientData!.MaxRetries)), TimeSpan.FromSeconds(_registerClientData!.RetrySeconds), _cancellationTokenSource.Token); } catch (Exception ex) { @@ -331,7 +331,7 @@ public class RegisterForRemote : IDisposable /// /// The data for the claimed machine /// Thrown if the machine could not be claimed - private async Task CheckClientClaimed() + private async Task CheckClientClaimedAsync() { var response = await _httpClient.PostAsync(_registerClientData!.StatusLink, CreateMachineData(), _cancellationTokenSource.Token); diff --git a/Duplicati/Library/RestAPI/Abstractions/IQueueRunnerService.cs b/Duplicati/Library/RestAPI/Abstractions/IQueueRunnerService.cs index 9d94b87b1..98a615683 100644 --- a/Duplicati/Library/RestAPI/Abstractions/IQueueRunnerService.cs +++ b/Duplicati/Library/RestAPI/Abstractions/IQueueRunnerService.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Server.Serialization.Interface; @@ -103,5 +104,5 @@ public interface IQueueRunnerService /// Note that the task will run concurrently with the queue tasks and may cause database lock issues. /// /// The task to run - IBasicResults? RunImmediately(IQueuedTask task); + Task RunImmediatelyAsync(IQueuedTask task); } \ No newline at end of file diff --git a/Duplicati/Library/RestAPI/Database/Connection.cs b/Duplicati/Library/RestAPI/Database/Connection.cs index 4af2523da..e8b4f9021 100644 --- a/Duplicati/Library/RestAPI/Database/Connection.cs +++ b/Duplicati/Library/RestAPI/Database/Connection.cs @@ -433,7 +433,9 @@ namespace Duplicati.Server.Database provider?.GetRequiredService()?.SignalNewEvent(); provider?.GetRequiredService()?.SignalServerSettingsUpdated(); // If throttle options were changed, update now - provider?.GetRequiredService()?.GetCurrentTask()?.UpdateThrottleSpeeds(ApplicationSettings.UploadSpeedLimit, ApplicationSettings.DownloadSpeedLimit); + var currentTask = provider?.GetRequiredService()?.GetCurrentTask(); + if (currentTask != null) + _ = currentTask.UpdateThrottleSpeedsAsync(ApplicationSettings.UploadSpeedLimit, ApplicationSettings.DownloadSpeedLimit); provider?.GetRequiredService()?.UpdatePowerModeProvider(); } diff --git a/Duplicati/Library/RestAPI/Database/ServerSettings.cs b/Duplicati/Library/RestAPI/Database/ServerSettings.cs index 49fb1a8f1..92dda0273 100644 --- a/Duplicati/Library/RestAPI/Database/ServerSettings.cs +++ b/Duplicati/Library/RestAPI/Database/ServerSettings.cs @@ -128,6 +128,7 @@ namespace Duplicati.Server.Database [GuardedInput, GuardedOutput, BooleanOutput] public const string CLIENT_LICENSE_KEY = "client-license-key"; public const string ENABLE_FOLDER_STATUS_SERVICE = "enable-folder-status-service"; + public const string USE_OUT_OF_PROCESS_CONTROLLER = "use-out-of-process-controller"; // UI support settings // public const string SHOWN_WELCOME_PAGE_V1 = "shown-welcome-page-v1"; @@ -881,6 +882,12 @@ namespace Duplicati.Server.Database } } + public bool UseOutOfProcessController + { + get => Utility.ParseBool(settings[CONST.USE_OUT_OF_PROCESS_CONTROLLER], false); + set => SetAndSaveSetting(CONST.USE_OUT_OF_PROCESS_CONTROLLER, value.ToString()); + } + public PowerModeProvider PowerModeProvider { get diff --git a/Duplicati/Library/RestAPI/Runner.cs b/Duplicati/Library/RestAPI/Runner.cs index 1e052993a..0000b105b 100644 --- a/Duplicati/Library/RestAPI/Runner.cs +++ b/Duplicati/Library/RestAPI/Runner.cs @@ -35,6 +35,7 @@ using Duplicati.WebserverCore.Abstractions; using System.Threading; using System.Text.Json; using System.Globalization; +using CoCoL; namespace Duplicati.Server { @@ -49,7 +50,7 @@ namespace Duplicati.Server int PageSize { get; } int PageOffset { get; } bool ReturnExtended { get; } - void SetController(Library.Main.Controller? controller); + void SetController(Library.Main.IController? controller); } private class RunnerData : IRunnerData @@ -80,37 +81,41 @@ namespace Duplicati.Server public DateTime? TaskStarted { get; set; } public DateTime? TaskFinished { get; set; } - internal Library.Main.Controller? Controller { get; set; } + internal Library.Main.IController? Controller { get; set; } - public void SetController(Library.Main.Controller? controller) + public void SetController(Library.Main.IController? controller) { Controller = controller; } - public void Stop() + public async Task StopAsync() { - Controller?.Stop(); + if (Controller != null) + await Controller.StopAsync().ConfigureAwait(false); } - public void Abort() + public async Task AbortAsync() { - Controller?.Abort(); + if (Controller != null) + await Controller.AbortAsync().ConfigureAwait(false); } - public void Pause(bool alsoTransfers) + public async Task PauseAsync(bool alsoTransfers) { - Controller?.Pause(alsoTransfers); + if (Controller != null) + await Controller.PauseAsync(alsoTransfers).ConfigureAwait(false); } - public void Resume() + public async Task ResumeAsync() { - Controller?.Resume(); + if (Controller != null) + await Controller.ResumeAsync().ConfigureAwait(false); } public long OriginalUploadSpeed { get; set; } public long OriginalDownloadSpeed { get; set; } - public void UpdateThrottleSpeeds(string? uploadSpeed, string? downloadSpeed) + public async Task UpdateThrottleSpeedsAsync(string? uploadSpeed, string? downloadSpeed) { var controller = this.Controller; if (controller == null) @@ -145,7 +150,7 @@ namespace Duplicati.Server if (download_throttle <= 0 || download_throttle == long.MaxValue) download_throttle = 0; - controller.SetThrottleSpeeds(upload_throttle, download_throttle); + await controller.SetThrottleSpeedsAsync(upload_throttle, download_throttle).ConfigureAwait(false); } private readonly long m_taskID; @@ -564,15 +569,15 @@ namespace Duplicati.Server return parts.ToArray(); } - public static IBasicResults? Run(Connection databaseConnection, EventPollNotify eventPollNotify, INotificationUpdateService notificationUpdateService, IProgressStateProviderService progressStateProviderService, IApplicationSettings applicationSettings, IQueuedTask data, bool fromQueue) + public static Task RunAsync(Connection databaseConnection, EventPollNotify eventPollNotify, INotificationUpdateService notificationUpdateService, IProgressStateProviderService progressStateProviderService, IApplicationSettings applicationSettings, IQueuedTask data, bool fromQueue) { if (data is IRunnerData runnerData) - return RunInternal(databaseConnection, eventPollNotify, notificationUpdateService, progressStateProviderService, applicationSettings, runnerData, fromQueue); + return RunInternalAsync(databaseConnection, eventPollNotify, notificationUpdateService, progressStateProviderService, applicationSettings, runnerData, fromQueue); throw new ArgumentException("Invalid task type", nameof(data)); } - private static IBasicResults? RunInternal(Connection databaseConnection, EventPollNotify eventPollNotify, INotificationUpdateService notificationUpdateService, IProgressStateProviderService progressStateProviderService, IApplicationSettings applicationSettings, IRunnerData data, bool fromQueue) + private static async Task RunInternalAsync(Connection databaseConnection, EventPollNotify eventPollNotify, INotificationUpdateService notificationUpdateService, IProgressStateProviderService progressStateProviderService, IApplicationSettings applicationSettings, IRunnerData data, bool fromQueue) { data.TaskStarted = DateTime.Now; if (data is CustomRunnerTask task) @@ -600,7 +605,7 @@ namespace Duplicati.Server if (!cts.IsCancellationRequested) eventPollNotify.SignalProgressUpdate(sink.Copy); } - }); + }).FireAndForget(); task.Run(sink); eventPollNotify.SignalProgressUpdate(sink.Copy); @@ -647,7 +652,7 @@ namespace Duplicati.Server if (!cts.IsCancellationRequested) eventPollNotify.SignalProgressUpdate(sink.Copy); } - }); + }).FireAndForget(); } var options = ApplyOptions(databaseConnection, backup, GetCommonOptions(databaseConnection), out var url); @@ -663,15 +668,16 @@ namespace Duplicati.Server if (data.Operation == DuplicatiOperation.Backup && options.ContainsKey("store-task-config")) tempfolder = StoreTaskConfigAndGetTempFolder(databaseConnection, data, options); + var useOutOfProcess = databaseConnection.ApplicationSettings.UseOutOfProcessController; + // Attach a log scope that tags all messages to relay the TaskID and BackupID using (Library.Logging.Log.StartScope(log => { log[ILogWriteHandler.LiveLogEntry.LOG_EXTRA_TASKID] = data.TaskID.ToString(); log[ILogWriteHandler.LiveLogEntry.LOG_EXTRA_BACKUPID] = data.BackupID; })) - using (tempfolder) - using (var controller = new Duplicati.Library.Main.Controller(url, options, sink)) + using (var controller = await CreateControllerAsync(url, options, sink, useOutOfProcess).ConfigureAwait(false)) { try { @@ -689,16 +695,16 @@ namespace Duplicati.Server ((RunnerData)data).Controller = controller; var appSettings = databaseConnection.ApplicationSettings; - data.UpdateThrottleSpeeds(appSettings.UploadSpeedLimit, appSettings.DownloadSpeedLimit); + await data.UpdateThrottleSpeedsAsync(appSettings.UploadSpeedLimit, appSettings.DownloadSpeedLimit).ConfigureAwait(false); // Pass on the provider, will be replaced if configured in the backup - controller.SetSecretProvider(applicationSettings.SecretProvider); + await controller.SetSecretProviderAsync(applicationSettings.SecretProvider).ConfigureAwait(false); if (backup.Metadata.ContainsKey("LastCompactFinished")) - controller.LastCompact = Utility.DeserializeDateTime(backup.Metadata["LastCompactFinished"]); + await controller.SetLastCompactAsync(Utility.DeserializeDateTime(backup.Metadata["LastCompactFinished"])).ConfigureAwait(false); if (backup.Metadata.ContainsKey("LastVacuumFinished")) - controller.LastVacuum = Utility.DeserializeDateTime(backup.Metadata["LastVacuumFinished"]); + await controller.SetLastVacuumAsync(Utility.DeserializeDateTime(backup.Metadata["LastVacuumFinished"])).ConfigureAwait(false); switch (data.Operation) { @@ -710,49 +716,49 @@ namespace Duplicati.Server .WhereNotNullOrWhiteSpace() .ToArray(); - var r = controller.Backup(sources, filter); + var r = await controller.BackupAsync(sources, filter).ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.List: { - var r = controller.List(data.FilterStrings, null); + var r = await controller.ListAsync(data.FilterStrings, null).ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.Repair: { - var r = controller.Repair(data.FilterStrings == null ? null : new Library.Utility.FilterExpression(data.FilterStrings)); + var r = await controller.RepairAsync(data.FilterStrings == null ? null : new Library.Utility.FilterExpression(data.FilterStrings)).ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.RepairUpdate: { - var r = controller.UpdateDatabaseWithVersions(); + var r = await controller.UpdateDatabaseWithVersionsAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.Remove: { - var r = controller.Delete(); + var r = await controller.DeleteAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.Restore: { - var r = controller.Restore(data.FilterStrings); + var r = await controller.RestoreAsync(data.FilterStrings).ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.Verify: { - var r = controller.Test(); + var r = await controller.TestAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.Compact: { - var r = controller.Compact(); + var r = await controller.CompactAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } @@ -760,7 +766,7 @@ namespace Duplicati.Server { using (var tf = new Library.Utility.TempFile()) { - var r = controller.CreateLogDatabase(tf); + var r = await controller.CreateLogDatabaseAsync(tf).ConfigureAwait(false); var tempid = databaseConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3)); if (string.Equals(tf, r.TargetPath, Utility.ClientFilenameStringComparison)) @@ -785,7 +791,7 @@ namespace Duplicati.Server case DuplicatiOperation.ListRemote: { - var r = controller.ListRemote(); + var r = await controller.ListRemoteAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } @@ -795,7 +801,7 @@ namespace Duplicati.Server if (data.ExtraOptions != null) { if (Utility.ParseBoolOption(data.ExtraOptions.AsReadOnly(), "delete-remote-files")) - controller.DeleteAllRemoteFiles(); + await controller.DeleteAllRemoteFilesAsync().ConfigureAwait(false); if (Utility.ParseBoolOption(data.ExtraOptions.AsReadOnly(), "delete-local-db")) { @@ -811,30 +817,30 @@ namespace Duplicati.Server } case DuplicatiOperation.Vacuum: { - var r = controller.Vacuum(); + var r = await controller.VacuumAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.ListFilesets: { - var r = controller.ListFilesets(); + var r = await controller.ListFilesetsAsync().ConfigureAwait(false); UpdateMetadataBase(databaseConnection, eventPollNotify, notificationUpdateService, backup, r); return r; } case DuplicatiOperation.ListFolderContents: { - return controller.ListFolder(data.ExtraArguments, data.PageOffset * data.PageSize, data.PageSize, data.ReturnExtended); + return await controller.ListFolderAsync(data.ExtraArguments, data.PageOffset * data.PageSize, data.PageSize, data.ReturnExtended).ConfigureAwait(false); } case DuplicatiOperation.ListFileVersions: { - return controller.ListFileVersions(data.ExtraArguments, data.PageOffset * data.PageSize, data.PageSize); + return await controller.ListFileVersionsAsync(data.ExtraArguments, data.PageOffset * data.PageSize, data.PageSize).ConfigureAwait(false); } case DuplicatiOperation.SearchEntries: { var parsedfilter = new FilterExpression(data.FilterStrings); - return controller.SearchEntries(data.ExtraArguments, parsedfilter, data.PageOffset * data.PageSize, data.PageSize, data.ReturnExtended); + return await controller.SearchEntriesAsync(data.ExtraArguments, parsedfilter, data.PageOffset * data.PageSize, data.PageSize, data.ReturnExtended).ConfigureAwait(false); } default: //TODO: Log this @@ -859,6 +865,11 @@ namespace Duplicati.Server } } + private static async Task CreateControllerAsync(string url, Dictionary options, MessageSink sink, bool useOutOfProcess) + { + return new Library.Main.Controller(url, options, sink); + } + private static TempFolder? StoreTaskConfigAndGetTempFolder(Connection databaseConnection, IRunnerData data, Dictionary options) { if (data.Backup == null) diff --git a/Duplicati/Library/RestAPI/Scheduler.cs b/Duplicati/Library/RestAPI/Scheduler.cs index 39e1bd066..1cd9df3f5 100644 --- a/Duplicati/Library/RestAPI/Scheduler.cs +++ b/Duplicati/Library/RestAPI/Scheduler.cs @@ -215,7 +215,7 @@ namespace Duplicati.Server return res; } - private Task OnCompleted(Runner.IRunnerData task) + private Task OnCompletedAsync(Runner.IRunnerData task) { Tuple? t = null; lock (m_lock) @@ -234,7 +234,7 @@ namespace Duplicati.Server return Task.CompletedTask; } - private Task OnStartingWork(Runner.IRunnerData task) + private Task OnStartingWorkAsync(Runner.IRunnerData task) { if (task is null) return Task.CompletedTask; @@ -354,8 +354,8 @@ namespace Duplicati.Server } var job = Server.Runner.CreateTask(Serialization.DuplicatiOperation.Backup, entry, taskOptions); - job.OnStarting = () => OnStartingWork(job); - job.OnFinished = (_) => OnCompleted(job); + job.OnStarting = () => OnStartingWorkAsync(job); + job.OnFinished = (_) => OnCompletedAsync(job); collectedJobs.Add((start, job)); } } diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs index 12d410ef7..29be7852a 100644 --- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs +++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs @@ -58,7 +58,7 @@ public interface IQueuedTask /// /// The upload speed to set. /// The download speed to set. - void UpdateThrottleSpeeds(string? uploadSpeed, string? downloadSpeed); + Task UpdateThrottleSpeedsAsync(string? uploadSpeed, string? downloadSpeed); /// /// The time when the task was starting to execute. /// @@ -70,18 +70,18 @@ public interface IQueuedTask /// /// Stops the task. /// - void Stop(); + Task StopAsync(); /// /// Aborts the task. /// - void Abort(); + Task AbortAsync(); /// /// Pauses the task. /// /// If true, also pauses transfers. - void Pause(bool alsoTransfers); + Task PauseAsync(bool alsoTransfers); /// /// Resumes the task. /// - void Resume(); + Task ResumeAsync(); } diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs index 47a914e05..eec5638a1 100644 --- a/Duplicati/Server/Program.cs +++ b/Duplicati/Server/Program.cs @@ -1,1650 +1,1652 @@ -// Copyright (C) 2026, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Duplicati.Library.AutoUpdater; -using Duplicati.Library.Certificates; -using Duplicati.Library.Common.IO; -using Duplicati.Library.Crashlog; -using Duplicati.Library.Encryption; -using Duplicati.Library.Interface; -using Duplicati.Library.Logging; -using Duplicati.Library.Main; -using Duplicati.Library.Main.Database; -using Duplicati.Library.Utility; -using Duplicati.Server.Database; -using Duplicati.WebserverCore; -using Duplicati.WebserverCore.Abstractions; -using Duplicati.WebserverCore.Services; -using Microsoft.Extensions.DependencyInjection; - -namespace Duplicati.Server -{ - public class Program - { - /// The commandline argument name for parameters file. - private const string PARAMETERS_FILE_OPTION = "parameters-file"; - /// Alternative names for the parameters file option. - private static readonly string[] PARAMETERS_FILE_OPTION_EXTRAS = ["parameterfile"]; - /// The commandline argument name for ping-pong keepalive. - private const string PING_PONG_KEEPALIVE_OPTION = "ping-pong-keepalive"; - /// The commandline argument name for Windows event log. - private const string WINDOWS_EVENTLOG_OPTION = "windows-eventlog"; - /// The commandline argument name for Windows event log level. - private const string WINDOWS_EVENTLOG_LEVEL_OPTION = "windows-eventlog-level"; - /// The commandline argument name for disabling database encryption. - private const string DISABLE_DB_ENCRYPTION_OPTION = "disable-db-encryption"; - /// The commandline argument name for requiring database encryption key. - private const string REQUIRE_DB_ENCRYPTION_KEY_OPTION = "require-db-encryption-key"; - /// The commandline argument name for settings encryption key. - private const string SETTINGS_ENCRYPTION_KEY_OPTION = "settings-encryption-key"; - /// The commandline argument name for disabling update check. - private const string DISABLE_UPDATE_CHECK_OPTION = "disable-update-check"; - /// The commandline argument name for registering remote control. - private const string REGISTER_REMOTE_CONTROL_OPTION = "register-remote-control"; - /// The commandline argument name for forcing remote control reregistration. - private const string REGISTER_REMOTE_CONTROL_REREGISTER_OPTION = "register-remote-control-force"; - /// The commandline argument name for allowed backend modules. - private const string ALLOWED_BACKEND_MODULES = "allowed-backend-modules"; - /// The commandline argument name for allowed encryption modules. - private const string ALLOWED_ENCRYPTION_MODULES = "allowed-encryption-modules"; - /// The commandline argument name for allowed compression modules. - private const string ALLOWED_COMPRESSION_MODULES = "allowed-compression-modules"; - /// The commandline argument name for log file. - private const string LOG_FILE_OPTION = "log-file"; - /// Notification action for CA key stored in unencrypted database. - private const string NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE = "config:issue:ca-key-unencrypted-database"; - /// Notification action for certificate renewal failure. - private const string NOTIFICATION_CERTIFICATE_RENEWAL_FAILED = "config:issue:certificate-renewal-failed"; - /// The commandline argument name for log level. - private const string LOG_LEVEL_OPTION = "log-level"; - /// The commandline argument name for log console. - private const string LOG_CONSOLE_OPTION = "log-console"; - /// The commandline argument name for temp directory. - private const string TEMPDIR_OPTION = "tempdir"; - /// The commandline argument name for log retention. - private const string LOG_RETENTION_OPTION = "log-retention"; - /// The commandline argument name for help. - private const string HELP_OPTION = "help"; - - /// - /// The name used in the secret provider for the database encryption key - /// - private const string DATABASE_SECRET_VALUE_NAME = "duplicati-server-encryption-key"; - -#if DEBUG - private const bool DEBUG_MODE = true; -#else - private const bool DEBUG_MODE = false; -#endif - - /// - /// Options to be ignored when validating the command line - /// - public static readonly HashSet ValidationIgnoredOptions = new HashSet(StringComparer.OrdinalIgnoreCase); - /// - /// The log tag for messages from this class - /// - private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(); - /// - /// The path to the directory that contains the main executable - /// - public static readonly string StartupPath = Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR; - - /// - /// The environment variable prefix - /// - private static readonly string ENV_NAME_PREFIX = AutoUpdateSettings.AppName.ToUpperInvariant(); - - /// - /// The single instance - /// - public static SingleInstance ApplicationInstance = null; - - /// - /// The thread running the ping-pong handler - /// - private static Thread PingPongThread; - - /// - /// The controller interface for pause/resume and throttle options - /// - public static LiveControls LiveControl { get => DuplicatiWebserver.Provider.GetRequiredService(); } - - /// - /// Duplicati webserver instance - /// - public static DuplicatiWebserver DuplicatiWebserver { get; set; } - - /// - /// The default secret provider - /// - public static ISecretProvider DefaultSecretProvider { get; set; } - - /// - /// Callback to shutdown the modern webserver - /// - private static void ShutdownModernWebserver() - { - DuplicatiWebserver.Stop().GetAwaiter().GetResult(); - } - - /// - /// An event that is set once the server is ready to respond to requests - /// - public static readonly ManualResetEvent ServerStartedEvent = new ManualResetEvent(false); - - /// - /// Timer for purging temp files and log data - /// - private static System.Threading.Timer PurgeTempFilesTimer = null; - - /// - /// Timer for checking certificate renewal - /// - private static System.Threading.Timer CertificateRenewalTimer = null; - - /// - /// The main entry point for the application. - /// - /// The command line arguments - [STAThread] - public static int Main(string[] _args) - => Main(null, _args); - - /// - /// The main entry point for the application. - /// - /// The application settings - /// The command line arguments - [STAThread] - public static int Main(IApplicationSettings applicationSettings, string[] _args) - { - PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, PackageHelper.NamedExecutable.Server, out var preloadDbSettings); - - applicationSettings ??= new ApplicationSettings(); - - //If this executable is invoked directly, write to console, otherwise throw exceptions - var writeToConsoleOnException = applicationSettings.Origin == "Server"; - - // Prepared for the future, where we might want to have a silent console mode - var silentConsole = false; - var logMessageToConsole = (string message) => { if (!silentConsole) Console.WriteLine(message); }; - - //Find commandline options here for handling special startup cases - var args = new List(_args); - var optionsWithFilter = FilterCollector.ExtractOptions(new List(args)); - var commandlineOptions = optionsWithFilter.Item1; - var filter = optionsWithFilter.Item2; - - if (HelpOptionExtensions.IsArgumentAnyHelpString(args)) - { - return ShowHelp(writeToConsoleOnException); - } - - if (commandlineOptions.ContainsKey("tempdir") && !string.IsNullOrEmpty(commandlineOptions["tempdir"])) - { - SystemContextSettings.DefaultTempPath = commandlineOptions["tempdir"]; - } - - SystemContextSettings.StartSession(); - - ApplyEnvironmentVariables(commandlineOptions); - ApplySecretProvider(applicationSettings, commandlineOptions, CancellationToken.None).Await(); - DefaultSecretProvider = SecretProviderHelper.GetDefaultSecretProvider(commandlineOptions, CancellationToken.None) - .Await(); - - var parameterFileOption = PARAMETERS_FILE_OPTION_EXTRAS.Prepend(PARAMETERS_FILE_OPTION) - .FirstOrDefault(x => commandlineOptions.ContainsKey(x)); - - if (parameterFileOption != null && !string.IsNullOrEmpty(commandlineOptions[parameterFileOption])) - { - string filename = commandlineOptions[parameterFileOption]; - commandlineOptions.Remove(parameterFileOption); - ReadOptionsFromFile(filename, ref filter, args, commandlineOptions); - } - - var logHandler = new LogWriteHandler(); - using var logScope = ConfigureLogging(logHandler, commandlineOptions); - - // Validate after logging is configured - CommandLineArgumentValidator.ValidateArguments(SupportedCommands, commandlineOptions, KnownDuplicateOptions, ValidationIgnoredOptions); - - // Unload any modules that are not allowed - Library.DynamicLoader.BackendLoader.OnlyAllowBackends(commandlineOptions.GetValueOrDefault(ALLOWED_BACKEND_MODULES)); - Library.DynamicLoader.EncryptionLoader.OnlyAllowModules(commandlineOptions.GetValueOrDefault(ALLOWED_ENCRYPTION_MODULES)); - Library.DynamicLoader.CompressionLoader.OnlyAllowModules(commandlineOptions.GetValueOrDefault(ALLOWED_COMPRESSION_MODULES)); - - - var crashed = false; - var terminated = false; - IQueueRunnerService queueRunner = null; - UpdatePollThread updatePollThread = null; - EventPollNotify eventPollNotify = null; - ISchedulerService scheduler = null; - try - { - var connection = GetDatabaseConnection(applicationSettings, commandlineOptions, silentConsole, true); - - if (!connection.ApplicationSettings.FixedInvalidBackupId) - connection.FixInvalidBackupId(); - - connection.ApplicationSettings.UpgradePasswordToKBDF(); - - // Handle --configure-https option before starting the webserver - if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_CONFIGURE_HTTPS)) - HandleConfigureHttps(connection, commandlineOptions); - - // Check for certificate renewal on startup (only for autogenerated certs) - var renewalResult = CheckAndRenewCertificate(connection); - if (renewalResult.RenewalFailedReason != null) - RegisterRenewalFailedNotification(connection, renewalResult.RenewalFailedReason); - - CreateApplicationInstance(applicationSettings.DataFolder, writeToConsoleOnException); - - applicationSettings.StartOrStopUsageReporter = () => StartOrStopUsageReporter(connection); - // Bit messy, but the callback needs the connection, and the connection needs the callback - connection.UpdateUsageReporterCallback(applicationSettings.StartOrStopUsageReporter); - applicationSettings.StartOrStopUsageReporter?.Invoke(); - - AdjustApplicationSettings(connection, commandlineOptions); - - UpdaterManager.OnError += obj => - { - connection.LogError(null, "Error in updater", obj); - }; - - DuplicatiWebserver = StartWebServer(commandlineOptions, connection, logHandler, applicationSettings).Await(); - - connection.SetServiceProvider(DuplicatiWebserver.Provider); - queueRunner = DuplicatiWebserver.Provider.GetRequiredService(); - updatePollThread = DuplicatiWebserver.Provider.GetRequiredService(); - eventPollNotify = DuplicatiWebserver.Provider.GetRequiredService(); - scheduler = DuplicatiWebserver.Provider.GetRequiredService(); - - updatePollThread.Init(Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_UPDATE_CHECK_OPTION)); - - SetPurgeTempFilesTimer(connection, commandlineOptions); - SetCertificateRenewalTimer(connection); - - LiveControl.StateChanged = (e) => { LiveControl_StateChanged(queueRunner, connection, eventPollNotify, scheduler, e); }; - - // Invoke it once to set initial state - LiveControl_StateChanged(queueRunner, connection, eventPollNotify, scheduler, new LiveControls.LiveControlEvent - { - State = LiveControl.State, - TransfersPaused = LiveControl.TransfersPaused, - WaitTimeExpiration = LiveControl.EstimatedPauseEnd - }); - - if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, PING_PONG_KEEPALIVE_OPTION)) - { - PingPongThread = new Thread(() => PingPongMethod(applicationSettings)) { IsBackground = true }; - PingPongThread.Start(); - } - - connection.ReWriteAllFieldsIfEncryptionChanged(); - connection.SetPreloadSettingsIfChanged(preloadDbSettings); - EmitWarningsForConfigurationIssues(connection, applicationSettings, commandlineOptions); - - Log.WriteInformationMessage(LOGTAG, "ServerStarted", Strings.Program.ServerStarted(DuplicatiWebserver.Interface, DuplicatiWebserver.Port)); - logMessageToConsole(Strings.Program.ServerStarted(DuplicatiWebserver.Interface, DuplicatiWebserver.Port)); - - var remoteControlUrl = commandlineOptions.GetValueOrDefault(REGISTER_REMOTE_CONTROL_OPTION); - if (!string.IsNullOrWhiteSpace(remoteControlUrl)) - RegisterForRemoteControl(DuplicatiWebserver.Provider.GetRequiredService(), DuplicatiWebserver.Provider.GetRequiredService(), remoteControlUrl, Library.Utility.Utility.ParseBoolOption(commandlineOptions, REGISTER_REMOTE_CONTROL_REREGISTER_OPTION), logMessageToConsole); - - if (!string.IsNullOrWhiteSpace(connection.ApplicationSettings.ClientLicenseKey)) - Proprietary.LicenseChecker.LicenseHelper.SetRemoteClientLicenseKey(connection.ApplicationSettings.ClientLicenseKey); - - if (applicationSettings.Origin == "Server" && connection.ApplicationSettings.AutogeneratedPassphrase) - { - var signinToken = DuplicatiWebserver.Provider.GetRequiredService().CreateSigninToken("server-cli"); - var hostname = (connection.ApplicationSettings.AllowedHostnames ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(x => x != "*") ?? "localhost"; - var scheme = connection.ApplicationSettings.UseHTTPS ? "https" : "http"; - - var url = $"{scheme}://{hostname}:{DuplicatiWebserver.Port}/signin.html?token={signinToken}"; - Log.WriteWarningMessage(LOGTAG, "ServerStartedSignin", null, Strings.Program.ServerStartedSignin(url)); - logMessageToConsole(Strings.Program.ServerStartedSignin(url)); - } - - DuplicatiWebserver.TerminationTask.ContinueWith((t) => - { - if (t.Exception != null) - { - Log.WriteWarningMessage(LOGTAG, "ServerCrashed", t.Exception, Strings.Program.ServerCrashed(t.Exception.Message)); - logMessageToConsole(Strings.Program.ServerStartedSignin(Strings.Program.ServerCrashed(t.Exception.ToString()))); - } - - terminated = true; - applicationSettings.SignalApplicationExit(); - }); - - var stopCounter = 0; - Console.CancelKeyPress += (sender, e) => - { - if (Interlocked.Increment(ref stopCounter) <= 1) - { - Log.WriteInformationMessage(LOGTAG, "CancelKeyPressed", "Cancel key pressed, stopping server"); - Task.Run(() => DuplicatiWebserver?.Stop()); - } - else - { - Log.WriteWarningMessage(LOGTAG, "CancelKeyPressed", null, "Cancel key pressed twice, terminating now"); - if (OperatingSystem.IsWindows()) - Environment.FailFast("Cancel key pressed twice, terminating now"); - else - Environment.Exit(0); - } - }; - - ServerStartedEvent.Set(); - applicationSettings.ApplicationExit.WaitHandle.WaitOne(); - } - catch (SingleInstance.MultipleInstanceException mex) - { - crashed = true; - Log.WriteErrorMessage(LOGTAG, "MultipleInstanceError", mex, Strings.Program.ServerCrashed(mex.Message)); - System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); - if (!writeToConsoleOnException) throw; - - Console.WriteLine(Strings.Program.SeriousError(mex.ToString())); - return 100; - } - catch (Exception ex) - { - crashed = true; - Log.WriteErrorMessage(LOGTAG, "ServerCrashed", ex, Strings.Program.ServerCrashed(ex.Message)); - System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); - if (writeToConsoleOnException) - { - Console.WriteLine(Strings.Program.SeriousError(ex.ToString())); - return 100; - } - else - throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex); - } - finally - { - Log.WriteInformationMessage(LOGTAG, "ServerStopping", Strings.Program.ServerStopping); - - var steps = new Action[] { - () => eventPollNotify?.SignalNewEvent(), - () => ShutdownModernWebserver(), - () => updatePollThread?.Terminate(), - () => scheduler?.Terminate(true), - () => queueRunner?.Terminate(true), - () => ApplicationInstance?.Dispose(), - () => PurgeTempFilesTimer?.Dispose(), - () => CertificateRenewalTimer?.Dispose(), - () => Library.UsageReporter.Reporter.ShutDown(), - () => PingPongThread?.Interrupt(), - () => - { - Log.WriteInformationMessage(LOGTAG, "ServerStopped", Strings.Program.ServerStopped); - logHandler?.Dispose(); - } - }; - - foreach (var teardownStep in steps) - { - try - { - teardownStep(); - } - catch (Exception ex) - { - // If the server is already crashed, that is the main error - // If the server crashes during teardown, we log that as an error - if (!(crashed || terminated)) - { - System.Diagnostics.Trace.WriteLine(Strings.Program.TearDownError(ex.ToString())); - logMessageToConsole(Strings.Program.TearDownError(ex.ToString())); - } - } - } - } - - return 0; - } - - /// - /// Starts the Duplicati web server - /// - /// The commandline options - /// The connection to use - /// The log write handler - /// The application settings - /// - private static async Task StartWebServer(IReadOnlyDictionary options, Connection connection, ILogWriteHandler logWriteHandler, IApplicationSettings applicationSettings) - { - var server = await WebServerLoader.TryRunServer(options, connection, async parsedOptions => - { - var mappedSettings = new DuplicatiWebserver.InitSettings( - parsedOptions.WebRoot, - parsedOptions.Port, - parsedOptions.Interface, - parsedOptions.AllowedHostnames, - parsedOptions.DisableStaticFiles, - parsedOptions.TokenLifetimeInMinutes, - parsedOptions.SPAPaths, - parsedOptions.CorsOrigins, - parsedOptions.PreAuthTokens - ); - - var server = DuplicatiWebserver.CreateWebServer(mappedSettings, connection, logWriteHandler, applicationSettings); - - // Start the server, but catch any configuration issues - var task = server.Start(); - await Task.WhenAny(task, Task.Delay(500)); - if (task.IsCompleted) - await task; - - return server; - }).ConfigureAwait(false); - - connection.ApplicationSettings.ServerPortChanged |= server.Port != connection.ApplicationSettings.LastWebserverPort; - connection.ApplicationSettings.LastWebserverPort = server.Port; - - return server; - } - - /// - /// Sets up the timer for purging temp files and log data - /// - /// The connection to use - /// The commandline options - private static void SetPurgeTempFilesTimer(Connection connection, Dictionary commandlineOptions) - { - var lastPurge = new DateTime(0); - - TimerCallback purgeTempFilesCallback = (x) => - { - try - { - if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < (DEBUG_MODE ? 1 : 23)) - { - return; - } - - lastPurge = DateTime.Now; - - foreach (var e in connection.GetTempFiles().Where((f) => f.Expires < DateTime.Now)) - { - try - { - if (System.IO.File.Exists(e.Path)) - System.IO.File.Delete(e.Path); - } - catch (Exception ex) - { - connection.LogError(null, $"Failed to delete temp file: {e.Path}", ex); - } - - connection.DeleteTempFile(e.ID); - } - - - Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => - { - connection.LogError(null, $"Failed to delete temp file: {path}", ex); - }); - - if (!commandlineOptions.TryGetValue("log-retention", out string pts)) - { - pts = DEFAULT_LOG_RETENTION; - } - - connection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true)); - } - catch (Exception ex) - { - connection.LogError(null, "Failed during temp file cleanup", ex); - } - }; - - PurgeTempFilesTimer = - new System.Threading.Timer(purgeTempFilesCallback, null, - DEBUG_MODE ? TimeSpan.FromSeconds(10) : TimeSpan.FromHours(1), - DEBUG_MODE ? TimeSpan.FromHours(1) : TimeSpan.FromDays(1)); - } - - /// - /// Sets up a timer to periodically check for certificate renewal. - /// This ensures certificates are renewed even if the server runs continuously - /// without restarts. - /// - /// The database connection. - private static void SetCertificateRenewalTimer(Connection connection) - { - TimerCallback certificateRenewalCallback = (x) => - { - try - { - // Only check for autogenerated certificates - if (!connection.ApplicationSettings.ServerSSLCertificateAutogenerated) - return; - - var renewalResult = CheckAndRenewCertificate(connection); - - if (renewalResult.Renewed) - { - Log.WriteInformationMessage(LOGTAG, "CertificateRenewedAutomatically", - "SSL certificate was automatically renewed during periodic check."); - } - else if (renewalResult.RenewalFailedReason != null) - { - Log.WriteWarningMessage(LOGTAG, "CertificateRenewalFailed", - null, - $"Automatic certificate renewal failed: {renewalResult.RenewalFailedReason}"); - } - } - catch (Exception ex) - { - Log.WriteErrorMessage(LOGTAG, "CertificateRenewalCheckFailed", ex, - "Failed to check/renew SSL certificate during periodic check."); - } - }; - - CertificateRenewalTimer = - new System.Threading.Timer(certificateRenewalCallback, null, - TimeSpan.FromHours(1), - TimeSpan.FromDays(1)); - } - - /// - /// Registers the server for remote control - /// - /// The remote controller registration - /// The remote controller - /// The remote control URL - /// Whether to re-register - /// Callback to log messages to console - private static void RegisterForRemoteControl(IRemoteControllerRegistration remoteControllerRegistration, IRemoteController remoteController, string remoteControlUrl, bool reRegister, Action logMessageToConsole) - { - if (remoteController.CanEnable) - { - if (!reRegister) - { - Log.WriteInformationMessage(LOGTAG, "RemoteControlAlreadyRegistered", Strings.Program.RemoteControlAlreadyRegistered); - return; - } - - remoteController.DeleteRegistration(); - } - - Task.Run(async () => - { - try - { - var regTask = remoteControllerRegistration.RegisterMachine(remoteControlUrl); - while (!regTask.IsCompleted) - { - // Interface does not have events, so poll it every second - await Task.WhenAny(Task.Delay(1000), regTask).ConfigureAwait(false); - if (!string.IsNullOrWhiteSpace(remoteControllerRegistration.RegistrationUrl)) - { - Log.WriteWarningMessage(LOGTAG, "RemoteControlRegistrationUrl", null, Strings.Program.RemoteControlRegistrationUrl(remoteControllerRegistration.RegistrationUrl)); - logMessageToConsole(Strings.Program.RemoteControlRegistrationUrl(remoteControllerRegistration.RegistrationUrl)); - break; - } - } - - // Await the registration task and ensure any errors are logged - await regTask.ConfigureAwait(false); - } - catch (Exception ex) - { - Log.WriteErrorMessage(LOGTAG, "RemoteControlRegistrationFailed", ex, Strings.Program.RemoteControlRegistrationFailed(ex.Message)); - } - }); - } - - /// - /// Adjusts the application settings based on commandline options - /// - /// The connection to use - /// The commandline options - private static void AdjustApplicationSettings(Connection connection, Dictionary commandlineOptions) - { - // This clears the JWT config, and a new will be generated, invalidating all existing tokens - if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG)) - { - connection.ApplicationSettings.JWTConfig = null; - // Clean up stored tokens as they are now invalid - connection.ExecuteWithCommand((con) => con.ExecuteNonQuery("DELETE FROM TokenFamily")); - } - - if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN)) - connection.ApplicationSettings.EnableForeverTokens(); - - if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS)) - connection.ApplicationSettings.DisableSigninTokens = Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS); - - if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS)) - connection.ApplicationSettings.DisabledAPIExtensions = commandlineOptions.GetValueOrDefault(WebServerLoader.OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS)? - .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) - .ToHashSet(StringComparer.OrdinalIgnoreCase) ?? []; - - if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOLDER_STATUS_SERVICE)) - connection.ApplicationSettings.EnableFolderStatusService = Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOLDER_STATUS_SERVICE); - - if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_PASSWORD)) - connection.ApplicationSettings.SetWebserverPassword(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_PASSWORD]); - - if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES)) - connection.ApplicationSettings.SetAllowedHostnames(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES]); - else if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT)) - connection.ApplicationSettings.SetAllowedHostnames(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]); - - if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE) && !string.IsNullOrEmpty(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE])) - try - { - connection.ApplicationSettings.Timezone = TimeZoneHelper.FindTimeZone(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE]); - } - catch (Exception ex) - { - throw new UserInformationException(Strings.Program.InvalidTimezone(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE]), "InvalidTimeZone", ex); - } - - // The database has recorded a new version - if (connection.ApplicationSettings.UpdatedVersion != null) - { - // Check if the running version is newer than the recorded version - if (UpdaterManager.TryParseVersion(connection.ApplicationSettings.UpdatedVersion.Version) <= UpdaterManager.TryParseVersion(UpdaterManager.SelfVersion.Version)) - { - // Clean up lingering update notifications - var updateNotifications = connection.GetNotifications().Where(x => x.Action == "update:new").ToList(); - foreach (var n in updateNotifications) - connection.DismissNotification(n.ID); - - // Clear up the recorded version - connection.ApplicationSettings.UpdatedVersion = null; - } - } - } - - /// - /// Emits warnings for configuration issues - /// - /// The connection - /// The application settings - /// The commandline options - private static void EmitWarningsForConfigurationIssues(Connection connection, IApplicationSettings applicationSettings, Dictionary commandlineOptions) - { - // First, remove any pending notifications, if the issue is resolved - if (connection.IsEncryptingFields) - { - // Remove any existing unencrypted database notification - var updateNotifications = connection.GetNotifications().Where(x => x.Action == "config:issue:unencrypted-database").ToList(); - foreach (var n in updateNotifications) - connection.DismissNotification(n.ID); - - // Remove any existing HTTPS related notifications - var caKeyNotifications = connection.GetNotifications().Where(x => x.Action == NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE || x.Action == NOTIFICATION_CERTIFICATE_RENEWAL_FAILED).ToList(); - foreach (var n in caKeyNotifications) - connection.DismissNotification(n.ID); - } - - if (OperatingSystem.IsWindows() && !applicationSettings.DataFolder.StartsWith(Util.AppendDirSeparator(Environment.GetFolderPath(Environment.SpecialFolder.Windows)), StringComparison.OrdinalIgnoreCase)) - { - // Remove any existing windows folder used notification - var updateNotifications = connection.GetNotifications().Where(x => x.Action == "config:issue:windows-folder-used").ToList(); - foreach (var n in updateNotifications) - connection.DismissNotification(n.ID); - } - - // Check tempdir free space against volume sizes - CheckTempDirFreeSpace(connection, commandlineOptions); - - // Emit warnings if the application has been updated - if (connection.ApplicationSettings.LastConfigIssueCheckVersion != UpdaterManager.SelfVersion.Version) - { - var updateNotifications = connection.GetNotifications().Where(x => x.Action.StartsWith("config:issue:")).ToList(); - foreach (var n in updateNotifications) - connection.DismissNotification(n.ID); - - if (!connection.IsEncryptingFields && !Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_DB_ENCRYPTION_OPTION)) - { - connection.RegisterNotification( - Serialization.NotificationType.Warning, - "Unencrypted database", - "The database is not encrypted. This is a security risk and should be fixed as soon as possible.", - null, - null, - "config:issue:unencrypted-database", - null, - "UnencryptedDatabase", - null, - (self, all) => - { - return all.FirstOrDefault(x => x.Action == "config:issue:unencrypted-database") ?? self; - } - ); - } - - if (OperatingSystem.IsWindows() && applicationSettings.DataFolder.StartsWith(Util.AppendDirSeparator(Environment.GetFolderPath(Environment.SpecialFolder.Windows)), StringComparison.OrdinalIgnoreCase)) - { - connection.RegisterNotification( - Serialization.NotificationType.Warning, - "Incorrect storage folder", - "The server configuration is stored inside the Windows folder. Please move the configuration to a different location, or it may be deleted on Windows version upgrades.", - null, - null, - "config:issue:windows-folder-used", - null, - "UnencryptedDatabase", - null, - (self, all) => - { - return all.FirstOrDefault(x => x.Action == "config:issue:windows-folder-used") ?? self; - } - ); - } - - connection.ApplicationSettings.LastConfigIssueCheckVersion = UpdaterManager.SelfVersion.Version; - } - } - - /// - /// Checks if the tempdir has enough free space relative to the volume sizes of configured backups. - /// Emits a warning if free space is less than 4 times the volume size. - /// - /// The database connection - /// The commandline options - private static void CheckTempDirFreeSpace(Connection connection, Dictionary commandlineOptions) - { - const string TEMPDIR_WARNING_ACTION = "config:issue:tempdir-low-space"; - - // Get the tempdir path - check Connection settings first, then fall back to system default - var tempDir = TempFolder.SystemTempPath; - var serverSettings = connection.GetSettings(Connection.ANY_BACKUP_ID); - var tempDirSetting = serverSettings?.FirstOrDefault(s => - string.Equals(s.Name, "tempdir", StringComparison.OrdinalIgnoreCase) || string.Equals(s.Name, "--tempdir", StringComparison.OrdinalIgnoreCase)); - if (tempDirSetting != null && !string.IsNullOrEmpty(tempDirSetting.Value)) - tempDir = tempDirSetting.Value; - - // Get free space in tempdir using the Utility method - var spaceInfo = Library.Utility.Utility.GetFreeSpaceForPath(tempDir); - // Could not determine free space, skip the check - if (spaceInfo == null) - return; - - // Get the maximum volume size from all configured backups - var maxVolumeSize = GetMaxVolumeSize(connection, commandlineOptions); - - // Check if free space is less than 4 times the volume size - if (spaceInfo.Value.FreeSpace < maxVolumeSize * Library.Utility.Utility.VOLUME_SIZE_FREE_SPACE_MULTIPLIER) - { - // Emit warning - connection.RegisterNotification( - Serialization.NotificationType.Warning, - "Low temporary folder space", - $"The temporary folder '{tempDir}' has limited free space ({Library.Utility.Utility.FormatSizeString(spaceInfo.Value.FreeSpace)}). " + - $"It is recommended to have at least {Library.Utility.Utility.VOLUME_SIZE_FREE_SPACE_MULTIPLIER} times the volume size ({Library.Utility.Utility.FormatSizeString(maxVolumeSize * Library.Utility.Utility.VOLUME_SIZE_FREE_SPACE_MULTIPLIER)}) available for optimal operation.", - null, - null, - TEMPDIR_WARNING_ACTION, - null, - "TempDirLowSpace", - null, - (self, all) => - { - return all.FirstOrDefault(x => x.Action == TEMPDIR_WARNING_ACTION) ?? self; - } - ); - } - else - { - // Clear any existing warning - var existingNotifications = connection.GetNotifications().Where(x => x.Action == TEMPDIR_WARNING_ACTION).ToList(); - foreach (var n in existingNotifications) - connection.DismissNotification(n.ID); - } - } - - /// - /// Gets the maximum volume size from all configured backups. - /// - /// The database connection - /// The commandline options - /// The maximum volume size in bytes - private static long GetMaxVolumeSize(Connection connection, Dictionary commandlineOptions) - { - const string DEFAULT_VOLUME_SIZE = "50mb"; - long maxVolumeSize = 0; - - // Check if there's a global dblock-size in commandline options - if (commandlineOptions.TryGetValue("dblock-size", out var globalDblockSize) && !string.IsNullOrEmpty(globalDblockSize)) - { - try - { - maxVolumeSize = Sizeparser.ParseSize(globalDblockSize, "mb"); - } - catch - { - // Ignore parsing errors - } - } - - // Check if there's a global dblock-size in the database settings - var serverSettings = connection.GetSettings(Connection.ANY_BACKUP_ID); - var defaultDblockSize = serverSettings?.FirstOrDefault(s => string.Equals(s.Name, "dblock-size", StringComparison.OrdinalIgnoreCase) || string.Equals(s.Name, "--dblock-size", StringComparison.OrdinalIgnoreCase)); - if (!string.IsNullOrEmpty(defaultDblockSize?.Value)) - try - { - maxVolumeSize = Math.Max(Sizeparser.ParseSize(defaultDblockSize.Value, "mb"), maxVolumeSize); - } - catch - { - // Ignore parsing errors - } - - // If no global setting, use default - maxVolumeSize = Math.Max(maxVolumeSize, Sizeparser.ParseSize(DEFAULT_VOLUME_SIZE, "mb")); - - // Check all configured backups for their volume sizes - foreach (var backup in connection.Backups) - { - try - { - // Get settings for this backup - var backupId = long.Parse(backup.ID); - var settings = connection.GetSettings(backupId); - - // Look for dblock-size setting - var dblockSizeSetting = settings?.FirstOrDefault(s => - string.Equals(s.Name, "dblock-size", StringComparison.OrdinalIgnoreCase) || string.Equals(s.Name, "--dblock-size", StringComparison.OrdinalIgnoreCase)); - - if (dblockSizeSetting != null && !string.IsNullOrEmpty(dblockSizeSetting.Value)) - { - var backupVolumeSize = Sizeparser.ParseSize(dblockSizeSetting.Value, "mb"); - if (backupVolumeSize > maxVolumeSize) - maxVolumeSize = backupVolumeSize; - } - } - catch - { - // Ignore errors for individual backups - } - } - - return maxVolumeSize; - } - - /// - /// Creates the application instance to ensure a single instance is running for the current user - /// - /// The data folder to use for single instance locking - /// Write to console on exception - private static void CreateApplicationInstance(string dataFolder, bool writeToConsoleOnException) - { - try - { - //This will also create DATAFOLDER if it does not exist - ApplicationInstance = new SingleInstance(dataFolder); - } - catch (Exception ex) - { - if (writeToConsoleOnException) - { - Console.WriteLine(Strings.Program.StartupFailure(ex)); - Environment.Exit(200); - } - - throw new Exception(Strings.Program.StartupFailure(ex)); - } - - if (!ApplicationInstance.IsFirstInstance) - { - if (writeToConsoleOnException) - { - Console.WriteLine(Strings.Program.AnotherInstanceDetected); - Environment.Exit(200); - } - - throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected); - } - } - - /// - /// Applies environment variables by converting them to commandline options - /// - /// The commandline options - private static void ApplyEnvironmentVariables(Dictionary commandlineOptions) - { - foreach (var key in SupportedCommands.SelectMany(x => (x.Aliases ?? []).Prepend(x.Name)).Distinct()) - { - // Commandline options take precedence - if (commandlineOptions.ContainsKey(key)) - continue; - - var envkey = $"{ENV_NAME_PREFIX}__{key.Replace('-', '_').ToUpperInvariant()}"; - var envval = Environment.GetEnvironmentVariable(envkey); - if (!string.IsNullOrWhiteSpace(envval)) - commandlineOptions[key] = envval; - } - - // Set the encryption key from the environment variable - if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)) && string.IsNullOrWhiteSpace(commandlineOptions.GetValueOrDefault(SETTINGS_ENCRYPTION_KEY_OPTION))) - commandlineOptions[SETTINGS_ENCRYPTION_KEY_OPTION] = Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME); - } - - /// - /// Applies the secret provider to the commandline options and application settings - /// - /// The application settings - /// The commandline options - /// The cancellation token - /// A task that represents the asynchronous operation - private static async Task ApplySecretProvider(IApplicationSettings applicationSettings, Dictionary commandlineOptions, CancellationToken cancellationToken) - => applicationSettings.SecretProvider = await SecretProviderHelper.ApplySecretProviderAsync([], [], commandlineOptions, TempFolder.SystemTempPath, applicationSettings.SecretProvider, cancellationToken).ConfigureAwait(false); - - /// - /// A log destination that writes to the console - /// - /// The minimum log level - private class ConsoleLogDestination(LogMessageType level) : ILogDestination - { - /// - public void WriteMessage(LogEntry entry) - { - if (entry.Level >= level) - Console.WriteLine(entry.AsString(true)); - } - } - - /// - /// Configures logging based on commandline options - /// - /// The log write handler - /// The commandline options - /// A disposable log scope - private static IDisposable ConfigureLogging(ILogWriteHandler logWriteHandler, Dictionary commandlineOptions) - { - IDisposable logScope; - //Log various information in the logfile - if (DEBUG_MODE && !commandlineOptions.ContainsKey(LOG_FILE_OPTION)) - { - var prefix = System.Reflection.Assembly.GetEntryAssembly().GetName().Name.StartsWith("Duplicati.Server") ? "server" : "trayicon"; - commandlineOptions[LOG_FILE_OPTION] = System.IO.Path.Combine(StartupPath, $"Duplicati-{prefix}.debug.log"); - commandlineOptions[LOG_LEVEL_OPTION] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); - if (System.IO.File.Exists(commandlineOptions[LOG_FILE_OPTION])) - System.IO.File.Delete(commandlineOptions[LOG_FILE_OPTION]); - } - - logScope = Log.StartScope(logWriteHandler, null); - - if (commandlineOptions.ContainsKey(LOG_FILE_OPTION)) - { - var loglevel = Library.Logging.LogMessageType.Warning; - if (commandlineOptions.ContainsKey(LOG_LEVEL_OPTION)) - Enum.TryParse(commandlineOptions[LOG_LEVEL_OPTION], true, out loglevel); - - logWriteHandler.SetServerFile(commandlineOptions[LOG_FILE_OPTION], loglevel); - } - - if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, LOG_CONSOLE_OPTION)) - { - var loglevel = Library.Logging.LogMessageType.Information; - if (commandlineOptions.ContainsKey(LOG_LEVEL_OPTION)) - Enum.TryParse(commandlineOptions[LOG_LEVEL_OPTION], true, out loglevel); - - logWriteHandler.AppendLogDestination(new ConsoleLogDestination(loglevel), loglevel); - } - - if (commandlineOptions.TryGetValue(WINDOWS_EVENTLOG_OPTION, out var source) && !string.IsNullOrEmpty(source)) - { - if (!OperatingSystem.IsWindows()) - { - Log.WriteWarningMessage(LOGTAG, "WindowsLogNotSupported", null, Strings.Program.WindowsEventLogNotSupported); - } - else - { - if (!WindowsEventLogSource.SourceExists(source)) - { - Log.WriteInformationMessage(LOGTAG, "WindowsLogMissingCreating", null, Strings.Program.WindowsEventLogSourceNotFound(source)); - try - { - WindowsEventLogSource.CreateEventSource(source); - } - catch (Exception ex) - { - Log.WriteWarningMessage(LOGTAG, "WindowsLogFailedCreate", ex, Strings.Program.WindowsEventLogSourceNotCreated(source)); - } - } - - if (WindowsEventLogSource.SourceExists(source)) - { - var loglevel = LogMessageType.Information; - if (commandlineOptions.ContainsKey(WINDOWS_EVENTLOG_LEVEL_OPTION)) - Enum.TryParse(commandlineOptions[WINDOWS_EVENTLOG_LEVEL_OPTION], true, out loglevel); - - logWriteHandler.AppendLogDestination(new WindowsEventLogSource(source), loglevel); - } - } - } - - CrashlogHelper.OnUnobservedTaskException += (ex) => logWriteHandler.WriteMessage(new LogEntry(ex.Message, null, Library.Logging.LogMessageType.Error, LOGTAG, "UnobservedTaskException", ex)); - return logScope; - } - - /// - /// Shows the help information - /// - /// Whether to write to console on exception - /// An integer indicating the result - private static int ShowHelp(bool writeToConsoleOnException) - { - if (writeToConsoleOnException) - { - Console.WriteLine(Strings.Program.HelpDisplayDialog); - - foreach (var arg in SupportedCommands) - Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription)); - - return 0; - } - - throw new Exception("Server invoked with --help"); - } - - /// - /// Gets the database connection, upgrading the database if needed - /// - /// The application settings - /// The commandline options - /// Whether to suppress console output - /// Whether to change the database encryption state if the arguments require it - /// The database connection - public static Connection GetDatabaseConnection(IApplicationSettings applicationSettings, Dictionary commandlineOptions, bool silentConsole, bool changeDbEncryption) - { - // Emit a warning if the database is stored in the Windows folder - if (Util.IsPathUnderWindowsFolder(applicationSettings.DataFolder)) - Log.WriteWarningMessage(LOGTAG, "DatabaseInWindowsFolder", null, "The database is stored in the Windows folder, this is not recommended as it will be deleted on Windows upgrades."); - - CrashlogHelper.DefaultLogDir = applicationSettings.DataFolder; - - var sqliteVersion = new Version(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteVersion); - if (sqliteVersion < new Version(3, 6, 3)) - { - //The official Mono SQLite provider is also broken with less than 3.6.3 - throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3")); - } - - //Create the connection instance - var con = Library.SQLiteHelper.SQLiteLoader.LoadConnection(); - - try - { - var databasePath = System.IO.Path.Combine(applicationSettings.DataFolder, DataFolderManager.SERVER_DATABASE_FILENAME); - - if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(databasePath))) - System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(databasePath)); - - // Attempt to open the database, removing any encryption present - Library.SQLiteHelper.SQLiteLoader.OpenDatabaseAsync(con, databasePath, Library.SQLiteHelper.SQLiteRC4Decrypter.GetEncryptionPassword(commandlineOptions)).Await(); - - Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, databasePath, typeof(Library.RestAPI.Database.DatabaseSchemaMarker)); - } - catch (Exception ex) - { - //Unwrap the reflection exceptions - if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) - ex = ex.InnerException; - - throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex); - } - - var disableDbEncryption = Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_DB_ENCRYPTION_OPTION); - var requireDbEncryptionKey = Library.Utility.Utility.ParseBoolOption(commandlineOptions, REQUIRE_DB_ENCRYPTION_KEY_OPTION); - var encKey = EncryptedFieldHelper.KeyInstance.CreateKeyIfValid(commandlineOptions.GetValueOrDefault(SETTINGS_ENCRYPTION_KEY_OPTION)); - var usingBlacklistedKey = encKey?.IsBlacklisted ?? false; - var hasValidEncryptionKey = encKey != null; - - // Don't encrypt the database in debug mode, unless explicitly requested - if (DEBUG_MODE) - { - if (string.IsNullOrWhiteSpace(commandlineOptions.GetValueOrDefault(DISABLE_DB_ENCRYPTION_OPTION))) - disableDbEncryption = true; - } - - // If we are supposed to have an encryption key, but do not, try to get it from the (default) secret provider - if (!hasValidEncryptionKey && DefaultSecretProvider != null) - { - string encryptionKey = null; - try - { - encryptionKey = DefaultSecretProvider.ResolveSecretAsync(DATABASE_SECRET_VALUE_NAME, CancellationToken.None).Await(); - } - catch - { - Log.WriteInformationMessage(LOGTAG, "SecretProviderFailedToGetEncryptionKey", null, Strings.Program.SecretProviderFailedToGetEncryptionKey); - } - - // If there is no encryption key, and the secret provider supports setting secrets, try to set the encryption key - if (!disableDbEncryption && string.IsNullOrWhiteSpace(encryptionKey) && DefaultSecretProvider.IsSetSupported) - { - var tmpkey = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32)); - try - { - DefaultSecretProvider.SetSecretAsync( - DATABASE_SECRET_VALUE_NAME, tmpkey, false, - CancellationToken.None).Await(); - - // For some cloud providers, this can sometimes fail due to eventual consistency - // For now, the solution is that settings will be encrypted in the next run - var tmp = DefaultSecretProvider.ResolveSecretAsync(DATABASE_SECRET_VALUE_NAME, CancellationToken.None).Await(); - - if (tmp != tmpkey) - throw new Exception("Secret provider did not return the expected value"); - - encryptionKey = tmpkey; - } - catch - { - Log.WriteVerboseMessage(LOGTAG, "SecretProviderFailedToSetEncryptionKey", null, Strings.Program.SecretProviderFailedToSetEncryptionKey); - } - } - - // If we got an encryption key (or created one), apply it - if (!string.IsNullOrWhiteSpace(encryptionKey)) - { - encKey = EncryptedFieldHelper.KeyInstance.CreateKeyIfValid(encryptionKey); - hasValidEncryptionKey = encKey != null; - } - } - - - if (requireDbEncryptionKey && !(hasValidEncryptionKey || disableDbEncryption)) - throw new UserInformationException(Strings.Program.DatabaseEncryptionKeyRequired(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION), "RequireDbEncryptionKey"); - - applicationSettings.SettingsEncryptionKeyProvidedExternally = hasValidEncryptionKey; - - var hasEncryptedFields = false; - try - { - using (var cmd = con.CreateCommand(@$"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = @Name AND ""BackupID"" = @BackupId")) - hasEncryptedFields = Library.Utility.Utility.ParseBool(cmd - .SetParameterValue("@Name", Database.ServerSettings.CONST.ENCRYPTED_FIELDS) - .SetParameterValue("@BackupId", Connection.SERVER_SETTINGS_ID).ExecuteScalar()?.ToString(), false); - - if (hasEncryptedFields && !hasValidEncryptionKey) - { - Log.WriteWarningMessage(LOGTAG, "EncryptionKeyMissing", null, Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); - if (!silentConsole) - Console.WriteLine(Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); - } - } - catch - { - // Ignore errors here, as we are just checking for a potential issue - // Only negative effect is that we do not show a potentially helpful warning - } - - if (!hasValidEncryptionKey && !disableDbEncryption) - { - disableDbEncryption = true; - Log.WriteWarningMessage(LOGTAG, "MissingEncryptionKey", null, Strings.Program.NoEncryptionKeySpecified(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); - if (!silentConsole) - Console.WriteLine(Strings.Program.NoEncryptionKeySpecified(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); - } - - if (usingBlacklistedKey && !disableDbEncryption) - { - disableDbEncryption = true; - Log.WriteErrorMessage(LOGTAG, "BlacklistedEncryptionKey", null, Strings.Program.BlacklistedEncryptionKey(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); - if (!silentConsole) - Console.WriteLine(Strings.Program.BlacklistedEncryptionKey(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); - } - - // If the database is not encrypted, and we are not changing the encryption - // don't pass the key, as that would cause the database to be encrypted - if (!hasEncryptedFields && !changeDbEncryption && hasValidEncryptionKey) - encKey = null; - - return new Connection(con, disableDbEncryption, encKey, applicationSettings.DataFolder, applicationSettings.StartOrStopUsageReporter); - } - - /// - /// Starts or stops the usage reporter based on application settings - /// - /// The connection - private static void StartOrStopUsageReporter(Connection connection) - { - var disableUsageReporter = - string.Equals(connection.ApplicationSettings.UsageReporterLevel, "none", StringComparison.OrdinalIgnoreCase) - || - string.Equals(connection.ApplicationSettings.UsageReporterLevel, "disabled", StringComparison.OrdinalIgnoreCase); - - if (!Enum.TryParse(connection.ApplicationSettings.UsageReporterLevel, true, out var reportLevel)) - Library.UsageReporter.Reporter.SetReportLevel(null, disableUsageReporter); - else - Library.UsageReporter.Reporter.SetReportLevel(reportLevel, disableUsageReporter); - } - - /// - /// This event handler updates the trayicon menu with the current state of the runner. - /// - /// - private static void LiveControl_StateChanged(IQueueRunnerService queueRunnerService, Connection connection, EventPollNotify eventPollNotify, ISchedulerService schedulerService, LiveControls.LiveControlEvent e) - { - var appSettings = connection.ApplicationSettings; - switch (e.State) - { - case LiveControls.LiveControlState.Paused: - { - queueRunnerService.Pause(); - queueRunnerService.GetCurrentTask()?.Pause(e.TransfersPaused); - appSettings.PausedUntil = e.WaitTimeExpiration; - break; - } - case LiveControls.LiveControlState.Running: - { - queueRunnerService.Resume(); - queueRunnerService.GetCurrentTask()?.Resume(); - schedulerService?.Reschedule(); - appSettings.PausedUntil = null; - break; - } - default: - Log.WriteWarningMessage(LOGTAG, "InvalidPauseResumeState", null, Strings.Program.InvalidPauseResumeState(LiveControl.State)); - break; - } - - eventPollNotify.SignalNewEvent(); - - } - - /// - /// Simple method for tracking if the server has crashed - /// - /// The application settings - private static void PingPongMethod(IApplicationSettings applicationSettings) - { - var rd = new System.IO.StreamReader(Console.OpenStandardInput()); - var wr = new System.IO.StreamWriter(Console.OpenStandardOutput()); - string line; - while ((line = rd.ReadLine()) != null) - { - if (string.Equals("shutdown", line, StringComparison.OrdinalIgnoreCase)) - { - // TODO: All calls to ApplicationExitEvent and TrayIcon->Quit - // should check if we are running something - applicationSettings.SignalApplicationExit(); - } - else - { - wr.WriteLine("pong"); - wr.Flush(); - } - } - } - - /// - /// Handles the --configure-https option by generating and installing HTTPS certificates. - /// - /// The database connection. - /// The commandline options. - private static void HandleConfigureHttps(Connection connection, Dictionary commandlineOptions) - { - var hostnames = commandlineOptions.GetValueOrDefault(WebServerLoader.OPTION_CONFIGURE_HTTPS_HOSTNAMES); - - // Read existing certificate data from database - var isAutogenerated = connection.ApplicationSettings.ServerSSLCertificateAutogenerated; - var serverCert = connection.ApplicationSettings.ServerSSLCertificate; - var caCert = connection.ApplicationSettings.ServerCACertificate; - var caKey = connection.ApplicationSettings.ServerCACertificateKey; - var caPassword = connection.ApplicationSettings.ServerCACertificatePassword; - - CACertificateData caData = null; - if (!string.IsNullOrEmpty(caCert) && !string.IsNullOrEmpty(caKey) && !string.IsNullOrEmpty(caPassword)) - { - caData = new CACertificateData - { - CACertificate = caCert, - CAKey = caKey, - CAPassword = caPassword - }; - } - - var result = CertificateRenewalChecker.GenerateAndInstallCertificates( - isAutogenerated, - serverCert, - caData, - hostnames, - skipTrustInstallation: false); - - if (result.Success) - { - Log.WriteInformationMessage(LOGTAG, "ConfigureHttpsComplete", "HTTPS certificates configured successfully."); - - // Store generated/renewed certificates in database - if (result.CACertificate != null && result.ServerCertificate != null) - { - // New CA + server certificate generated - var settings = new Dictionary - { - [Database.ServerSettings.CONST.SERVER_CA_CERTIFICATE] = result.CACertificate.CACertificate, - [Database.ServerSettings.CONST.SERVER_CA_CERTIFICATE_KEY] = result.CACertificate.CAKey, - [Database.ServerSettings.CONST.SERVER_CA_CERTIFICATE_PASSWORD] = result.CACertificate.CAPassword, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE] = result.ServerCertificate.ServerCertificate, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATEPASSWORD] = result.ServerCertificate.Password, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE_AUTOGENERATED] = "true" - }; - - connection.ApplicationSettings.UpdateSettings(settings, false); - - // Check if database is encrypted and emit warning if not - if (!connection.IsEncryptingFields) - EmitUnencryptedDatabaseWarning(connection); - } - else if (result.ServerCertificate != null) - { - // Server certificate renewed using existing CA - var settings = new Dictionary - { - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE] = result.ServerCertificate.ServerCertificate, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATEPASSWORD] = result.ServerCertificate.Password, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE_AUTOGENERATED] = "true" - }; - - connection.ApplicationSettings.UpdateSettings(settings, false); - - // Check if database is encrypted and emit warning if not - if (!connection.IsEncryptingFields) - EmitUnencryptedDatabaseWarning(connection); - } - } - else - { - Log.WriteErrorMessage(LOGTAG, "ConfigureHttpsFailed", null, "Failed to configure HTTPS certificates."); - } - } - - /// - /// Checks if the server certificate needs renewal and performs renewal if necessary. - /// Handles database I/O for the renewal process. - /// - /// The database connection. - /// A result object containing renewal status and any failure reason. - private static CertificateRenewalResult CheckAndRenewCertificate(Connection connection) - { - // Read certificate data from database - var isAutogenerated = connection.ApplicationSettings.ServerSSLCertificateAutogenerated; - var serverCert = connection.ApplicationSettings.ServerSSLCertificate; - var caCert = connection.ApplicationSettings.ServerCACertificate; - var caKey = connection.ApplicationSettings.ServerCACertificateKey; - var caPassword = connection.ApplicationSettings.ServerCACertificatePassword; - - CACertificateData caData = null; - if (!string.IsNullOrEmpty(caCert) && !string.IsNullOrEmpty(caKey) && !string.IsNullOrEmpty(caPassword)) - { - caData = new CACertificateData - { - CACertificate = caCert, - CAKey = caKey, - CAPassword = caPassword - }; - } - - // Check and renew if needed - var result = CertificateRenewalChecker.CheckAndRenewIfNeeded(isAutogenerated, serverCert, caData); - - // If renewed, store the new certificate in database - if (result.Renewed && result.RenewedCertificate != null) - { - var settings = new Dictionary - { - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE] = result.RenewedCertificate.ServerCertificate, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATEPASSWORD] = result.RenewedCertificate.Password, - [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE_AUTOGENERATED] = "true" - }; - - connection.ApplicationSettings.UpdateSettings(settings, false); - - // Check if database is encrypted and emit warning if not - if (!connection.IsEncryptingFields) - EmitUnencryptedDatabaseWarning(connection); - } - - return result; - } - - /// - /// Emits a warning notification if the database is not encrypted. - /// - /// The database connection. - private static void EmitUnencryptedDatabaseWarning(Connection connection) - { - connection.RegisterNotification( - Serialization.NotificationType.Warning, - "Unencrypted database with CA key", - "The database is not encrypted and contains the CA private key. This is a major security risk and could allow MitM attacks on all HTTPS connections. Consider enabling database encryption.", - null, - null, - NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE, - null, - "UnencryptedDatabaseWithCAKey", - null, - (self, all) => - { - return all.FirstOrDefault(x => x.Action == NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE) ?? self; - } - ); - } - - /// - /// Registers a notification for certificate renewal failure. - /// - /// The database connection. - /// The reason for the failure. - private static void RegisterRenewalFailedNotification(Connection connection, string reason) - { - connection.RegisterNotification( - Serialization.NotificationType.Error, - "Certificate renewal failed", - $"The automatic renewal of the HTTPS certificate failed: {reason}. Please run '{PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.ConfigureTool)} https regenerate-ca' to generate new certificates.", - null, - null, - NOTIFICATION_CERTIFICATE_RENEWAL_FAILED, - null, - "CertificateRenewalFailed", - null, - (self, all) => - { - return all.FirstOrDefault(x => x.Action == NOTIFICATION_CERTIFICATE_RENEWAL_FAILED) ?? self; - } - ); - } - - /// - /// The default log retention - /// - private static readonly string DEFAULT_LOG_RETENTION = "30D"; - - /// - /// The options related to the secret provider - /// - private static readonly IReadOnlyList SECRET_PROVIDER_OPTIONS = new Options(new Dictionary()).SupportedCommands.Where(x => x.Name.StartsWith("secret-provider")).ToList(); - - /// - /// Gets additional commandline arguments support on Windows - /// - private static readonly ICommandLineArgument[] WindowsOptions = OperatingSystem.IsWindows() - ? [ - new CommandLineArgument(WINDOWS_EVENTLOG_OPTION, CommandLineArgument.ArgumentType.String, Strings.Program.LogwindowseventlogShort, Strings.Program.LogwindowseventlogLong), - new CommandLineArgument(WINDOWS_EVENTLOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LogwindowseventloglevelShort, Strings.Program.LogwindowseventloglevelLong, Library.Logging.LogMessageType.Information.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))) - ] - : []; - - /// - /// Gets a list of all supported commandline options - /// - public static ICommandLineArgument[] SupportedCommands => - [ - .. WindowsOptions, - new CommandLineArgument(TEMPDIR_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.TempdirShort, Strings.Program.TempdirLong, System.IO.Path.GetTempPath()), - new CommandLineArgument(HELP_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription), - new CommandLineArgument(PARAMETERS_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong, "", PARAMETERS_FILE_OPTION_EXTRAS), - new CommandLineArgument(DataFolderManager.PORTABLE_MODE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription, DataFolderManager.PORTABLE_MODE.ToString().ToLowerInvariant()), - new CommandLineArgument(LOG_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription), - new CommandLineArgument(LOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, Library.Logging.LogMessageType.Warning.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))), - new CommandLineArgument(LOG_CONSOLE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.LogConsoleDescription, Strings.Program.LogConsoleDescription, false.ToString()), - new CommandLineArgument(WebServerLoader.OPTION_WEBROOT, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, WebServerLoader.DEFAULT_OPTION_WEBROOT), - new CommandLineArgument(WebServerLoader.OPTION_PORT, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPortDescription, Strings.Program.WebserverPortDescription, WebServerLoader.DEFAULT_OPTION_PORT.ToString()), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLEHTTPS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableHTTPSDescription, Strings.Program.WebserverDisableHTTPSDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_REMOVESSLCERTIFICATE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverRemoveCertificateDescription, Strings.Program.WebserverRemoveCertificateDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription), - new CommandLineArgument(WebServerLoader.OPTION_INTERFACE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, WebServerLoader.DEFAULT_OPTION_INTERFACE), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PASSWORD, CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverAllowedhostnamesDescription, Strings.Program.WebserverAllowedhostnamesDescription, null, [WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverResetJwtConfigDescription, Strings.Program.WebserverResetJwtConfigDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverEnableForeverTokenDescription, Strings.Program.WebserverEnableForeverTokenDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableApiExtensionsDescription, Strings.Program.WebserverDisableApiExtensionsDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_API_ONLY, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverApiOnlyDescription, Strings.Program.WebserverApiOnlyDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableSigninTokensDescription, Strings.Program.WebserverDisableSigninTokensDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SPAPATHS, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverSpaPathsDescription, Strings.Program.WebserverSpaPathsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverTimezoneDescription, Strings.Program.WebserverTimezoneDescription, TimeZoneHelper.GetLocalTimeZone(), null, TimeZoneHelper.GetTimeZones().Select(x => x.Id).ToArray()), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_CORS_ORIGINS, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverCorsOriginsDescription, Strings.Program.WebserverCorsOriginsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PRE_AUTH_TOKENS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPreAuthTokensDescription, Strings.Program.WebserverPreAuthTokensDescription), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_TOKENDURATION, CommandLineArgument.ArgumentType.Timespan, Strings.Program.WebserverTokenDurationDescription, Strings.Program.WebserverTokenDurationDescription, WebServerLoader.DEFAULT_OPTION_TOKENDURATION), - new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOLDER_STATUS_SERVICE, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverEnableFolderStatusServiceDescription, Strings.Program.WebserverEnableFolderStatusServiceDescription), - new CommandLineArgument(WebServerLoader.OPTION_CONFIGURE_HTTPS, CommandLineArgument.ArgumentType.Boolean, Strings.Program.ConfigureHttpsShort, Strings.Program.ConfigureHttpsLong), - new CommandLineArgument(WebServerLoader.OPTION_CONFIGURE_HTTPS_HOSTNAMES, CommandLineArgument.ArgumentType.String, Strings.Program.ConfigureHttpsHostnamesShort, Strings.Program.ConfigureHttpsHostnamesLong), - new CommandLineArgument(PING_PONG_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong), - new CommandLineArgument(DISABLE_UPDATE_CHECK_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisableupdatecheckShort, Strings.Program.DisableupdatecheckLong), - new CommandLineArgument(LOG_RETENTION_OPTION, CommandLineArgument.ArgumentType.Timespan, Strings.Program.LogretentionShort, Strings.Program.LogretentionLong, DEFAULT_LOG_RETENTION), - new CommandLineArgument(DataFolderManager.SERVER_DATAFOLDER_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ServerdatafolderShort, Strings.Program.ServerdatafolderLong(DataFolderManager.DATAFOLDER_ENV_NAME), DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)), - new CommandLineArgument(DISABLE_DB_ENCRYPTION_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisabledbencryptionShort, Strings.Program.DisabledbencryptionLong), - new CommandLineArgument(REQUIRE_DB_ENCRYPTION_KEY_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.RequiredbencryptionShort, Strings.Program.RequiredbencryptionLong), - new CommandLineArgument(SETTINGS_ENCRYPTION_KEY_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Program.SettingsencryptionkeyShort, Strings.Program.SettingsencryptionkeyLong(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)), - new CommandLineArgument(REGISTER_REMOTE_CONTROL_OPTION, CommandLineArgument.ArgumentType.String, Strings.Program.RegisterRemoteControlShort, Strings.Program.RegisterRemoteControlLong), - new CommandLineArgument(REGISTER_REMOTE_CONTROL_REREGISTER_OPTION, CommandLineArgument.ArgumentType.String, Strings.Program.RegisterRemoteControlReregisterShort, Strings.Program.RegisterRemoteControlReregisterLong), - new CommandLineArgument(ALLOWED_BACKEND_MODULES, CommandLineArgument.ArgumentType.String, Strings.Program.AllowedBackendModulesShort, Strings.Program.AllowedBackendModulesLong), - new CommandLineArgument(ALLOWED_ENCRYPTION_MODULES, CommandLineArgument.ArgumentType.String, Strings.Program.AllowedEncryptionModulesShort, Strings.Program.AllowedEncryptionModulesLong), - new CommandLineArgument(ALLOWED_COMPRESSION_MODULES, CommandLineArgument.ArgumentType.String, Strings.Program.AllowedCompressionModulesShort, Strings.Program.AllowedCompressionModulesLong), - .. SECRET_PROVIDER_OPTIONS - ]; - - /// - /// List of known duplicate option names - /// - public static readonly HashSet KnownDuplicateOptions = new HashSet(StringComparer.OrdinalIgnoreCase); - - /// - /// Reads options from a file and applies them to the current commandline arguments and options - /// - /// The filename - /// The filter - /// The commandline arguments - /// The options - /// True if successful - private static void ReadOptionsFromFile(string filename, ref IFilter filter, List cargs, Dictionary options) - { - try - { - var fargs = new List(Library.Utility.Utility.ReadFileWithDefaultEncoding(Environment.ExpandEnvironmentVariables(filename)).Replace("\r\n", "\n").Replace("\r", "\n").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); - var newsource = new List(); - string newtarget = null; - string prependfilter = null; - string appendfilter = null; - string replacefilter = null; - - var tmpparsed = FilterCollector.ExtractOptions(fargs, (key, value) => - { - if (key.Equals("source", StringComparison.OrdinalIgnoreCase)) - { - newsource.Add(value); - return false; - } - else if (key.Equals("target", StringComparison.OrdinalIgnoreCase)) - { - newtarget = value; - return false; - } - else if (key.Equals("append-filter", StringComparison.OrdinalIgnoreCase)) - { - appendfilter = value; - return false; - } - else if (key.Equals("prepend-filter", StringComparison.OrdinalIgnoreCase)) - { - prependfilter = value; - return false; - } - else if (key.Equals("replace-filter", StringComparison.OrdinalIgnoreCase)) - { - replacefilter = value; - return false; - } - - return true; - }); - - var opt = tmpparsed.Item1; - var newfilter = tmpparsed.Item2; - - // If the user specifies parameters-file, all filters must be in the file. - // Allowing to specify some filters on the command line could result in wrong filter ordering - if (!filter.Empty && !newfilter.Empty) - throw new UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2, "FiltersCannotBeUsedOnCommandLineAndInParameterFile"); - - if (!newfilter.Empty) - filter = newfilter; - - if (!string.IsNullOrWhiteSpace(prependfilter)) - filter = FilterExpression.Combine(FilterExpression.Deserialize(prependfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)), filter); - - if (!string.IsNullOrWhiteSpace(appendfilter)) - filter = FilterExpression.Combine(filter, FilterExpression.Deserialize(appendfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries))); - - if (!string.IsNullOrWhiteSpace(replacefilter)) - filter = FilterExpression.Deserialize(replacefilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); - - foreach (var keyvalue in opt) - options[keyvalue.Key] = keyvalue.Value; - - if (!string.IsNullOrEmpty(newtarget)) - { - if (cargs.Count <= 1) - cargs.Add(newtarget); - else - cargs[1] = newtarget; - } - - if (cargs.Count >= 1 && cargs[0].Equals("backup", StringComparison.OrdinalIgnoreCase)) - cargs.AddRange(newsource); - else if (newsource.Count > 0) - Log.WriteVerboseMessage(LOGTAG, "NotUsingBackupSources", Strings.Program.SkippingSourceArgumentsOnNonBackupOperation); - } - catch (Exception e) - { - throw new Exception(Strings.Program.FailedToParseParametersFileError(filename, e.Message)); - } - } - } -} +// Copyright (C) 2026, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using CoCoL; +using Duplicati.Library.AutoUpdater; +using Duplicati.Library.Certificates; +using Duplicati.Library.Common.IO; +using Duplicati.Library.Crashlog; +using Duplicati.Library.Encryption; +using Duplicati.Library.Interface; +using Duplicati.Library.Logging; +using Duplicati.Library.Main; +using Duplicati.Library.Main.Database; +using Duplicati.Library.Utility; +using Duplicati.Server.Database; +using Duplicati.WebserverCore; +using Duplicati.WebserverCore.Abstractions; +using Duplicati.WebserverCore.Services; +using Microsoft.Extensions.DependencyInjection; + +namespace Duplicati.Server +{ + public class Program + { + /// The commandline argument name for parameters file. + private const string PARAMETERS_FILE_OPTION = "parameters-file"; + /// Alternative names for the parameters file option. + private static readonly string[] PARAMETERS_FILE_OPTION_EXTRAS = ["parameterfile"]; + /// The commandline argument name for ping-pong keepalive. + private const string PING_PONG_KEEPALIVE_OPTION = "ping-pong-keepalive"; + /// The commandline argument name for Windows event log. + private const string WINDOWS_EVENTLOG_OPTION = "windows-eventlog"; + /// The commandline argument name for Windows event log level. + private const string WINDOWS_EVENTLOG_LEVEL_OPTION = "windows-eventlog-level"; + /// The commandline argument name for disabling database encryption. + private const string DISABLE_DB_ENCRYPTION_OPTION = "disable-db-encryption"; + /// The commandline argument name for requiring database encryption key. + private const string REQUIRE_DB_ENCRYPTION_KEY_OPTION = "require-db-encryption-key"; + /// The commandline argument name for settings encryption key. + private const string SETTINGS_ENCRYPTION_KEY_OPTION = "settings-encryption-key"; + /// The commandline argument name for disabling update check. + private const string DISABLE_UPDATE_CHECK_OPTION = "disable-update-check"; + /// The commandline argument name for registering remote control. + private const string REGISTER_REMOTE_CONTROL_OPTION = "register-remote-control"; + /// The commandline argument name for forcing remote control reregistration. + private const string REGISTER_REMOTE_CONTROL_REREGISTER_OPTION = "register-remote-control-force"; + /// The commandline argument name for allowed backend modules. + private const string ALLOWED_BACKEND_MODULES = "allowed-backend-modules"; + /// The commandline argument name for allowed encryption modules. + private const string ALLOWED_ENCRYPTION_MODULES = "allowed-encryption-modules"; + /// The commandline argument name for allowed compression modules. + private const string ALLOWED_COMPRESSION_MODULES = "allowed-compression-modules"; + /// The commandline argument name for log file. + private const string LOG_FILE_OPTION = "log-file"; + /// Notification action for CA key stored in unencrypted database. + private const string NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE = "config:issue:ca-key-unencrypted-database"; + /// Notification action for certificate renewal failure. + private const string NOTIFICATION_CERTIFICATE_RENEWAL_FAILED = "config:issue:certificate-renewal-failed"; + /// The commandline argument name for log level. + private const string LOG_LEVEL_OPTION = "log-level"; + /// The commandline argument name for log console. + private const string LOG_CONSOLE_OPTION = "log-console"; + /// The commandline argument name for temp directory. + private const string TEMPDIR_OPTION = "tempdir"; + /// The commandline argument name for log retention. + private const string LOG_RETENTION_OPTION = "log-retention"; + /// The commandline argument name for help. + private const string HELP_OPTION = "help"; + + /// + /// The name used in the secret provider for the database encryption key + /// + private const string DATABASE_SECRET_VALUE_NAME = "duplicati-server-encryption-key"; + +#if DEBUG + private const bool DEBUG_MODE = true; +#else + private const bool DEBUG_MODE = false; +#endif + + /// + /// Options to be ignored when validating the command line + /// + public static readonly HashSet ValidationIgnoredOptions = new HashSet(StringComparer.OrdinalIgnoreCase); + /// + /// The log tag for messages from this class + /// + private static readonly string LOGTAG = Library.Logging.Log.LogTagFromType(); + /// + /// The path to the directory that contains the main executable + /// + public static readonly string StartupPath = Duplicati.Library.AutoUpdater.UpdaterManager.INSTALLATIONDIR; + + /// + /// The environment variable prefix + /// + private static readonly string ENV_NAME_PREFIX = AutoUpdateSettings.AppName.ToUpperInvariant(); + + /// + /// The single instance + /// + public static SingleInstance ApplicationInstance = null; + + /// + /// The thread running the ping-pong handler + /// + private static Thread PingPongThread; + + /// + /// The controller interface for pause/resume and throttle options + /// + public static LiveControls LiveControl { get => DuplicatiWebserver.Provider.GetRequiredService(); } + + /// + /// Duplicati webserver instance + /// + public static DuplicatiWebserver DuplicatiWebserver { get; set; } + + /// + /// The default secret provider + /// + public static ISecretProvider DefaultSecretProvider { get; set; } + + /// + /// Callback to shutdown the modern webserver + /// + private static void ShutdownModernWebserver() + { + DuplicatiWebserver.StopAsync().Await(); + } + + /// + /// An event that is set once the server is ready to respond to requests + /// + public static readonly ManualResetEvent ServerStartedEvent = new ManualResetEvent(false); + + /// + /// Timer for purging temp files and log data + /// + private static System.Threading.Timer PurgeTempFilesTimer = null; + + /// + /// Timer for checking certificate renewal + /// + private static System.Threading.Timer CertificateRenewalTimer = null; + + /// + /// The main entry point for the application. + /// + /// The command line arguments + [STAThread] + public static int Main(string[] _args) + => Main(null, _args); + + /// + /// The main entry point for the application. + /// + /// The application settings + /// The command line arguments + [STAThread] + public static int Main(IApplicationSettings applicationSettings, string[] _args) + { + PreloadSettingsLoader.ConfigurePreloadSettings(ref _args, PackageHelper.NamedExecutable.Server, out var preloadDbSettings); + + applicationSettings ??= new ApplicationSettings(); + + //If this executable is invoked directly, write to console, otherwise throw exceptions + var writeToConsoleOnException = applicationSettings.Origin == "Server"; + + // Prepared for the future, where we might want to have a silent console mode + var silentConsole = false; + var logMessageToConsole = (string message) => { if (!silentConsole) Console.WriteLine(message); }; + + //Find commandline options here for handling special startup cases + var args = new List(_args); + var optionsWithFilter = FilterCollector.ExtractOptions(new List(args)); + var commandlineOptions = optionsWithFilter.Item1; + var filter = optionsWithFilter.Item2; + + if (HelpOptionExtensions.IsArgumentAnyHelpString(args)) + { + return ShowHelp(writeToConsoleOnException); + } + + if (commandlineOptions.ContainsKey("tempdir") && !string.IsNullOrEmpty(commandlineOptions["tempdir"])) + { + SystemContextSettings.DefaultTempPath = commandlineOptions["tempdir"]; + } + + SystemContextSettings.StartSession(); + + ApplyEnvironmentVariables(commandlineOptions); + ApplySecretProviderAsync(applicationSettings, commandlineOptions, CancellationToken.None).Await(); + DefaultSecretProvider = SecretProviderHelper.GetDefaultSecretProvider(commandlineOptions, CancellationToken.None) + .Await(); + + var parameterFileOption = PARAMETERS_FILE_OPTION_EXTRAS.Prepend(PARAMETERS_FILE_OPTION) + .FirstOrDefault(x => commandlineOptions.ContainsKey(x)); + + if (parameterFileOption != null && !string.IsNullOrEmpty(commandlineOptions[parameterFileOption])) + { + string filename = commandlineOptions[parameterFileOption]; + commandlineOptions.Remove(parameterFileOption); + ReadOptionsFromFile(filename, ref filter, args, commandlineOptions); + } + + var logHandler = new LogWriteHandler(); + using var logScope = ConfigureLogging(logHandler, commandlineOptions); + + // Validate after logging is configured + CommandLineArgumentValidator.ValidateArguments(SupportedCommands, commandlineOptions, KnownDuplicateOptions, ValidationIgnoredOptions); + + // Unload any modules that are not allowed + Library.DynamicLoader.BackendLoader.OnlyAllowBackends(commandlineOptions.GetValueOrDefault(ALLOWED_BACKEND_MODULES)); + Library.DynamicLoader.EncryptionLoader.OnlyAllowModules(commandlineOptions.GetValueOrDefault(ALLOWED_ENCRYPTION_MODULES)); + Library.DynamicLoader.CompressionLoader.OnlyAllowModules(commandlineOptions.GetValueOrDefault(ALLOWED_COMPRESSION_MODULES)); + + + var crashed = false; + var terminated = false; + IQueueRunnerService queueRunner = null; + UpdatePollThread updatePollThread = null; + EventPollNotify eventPollNotify = null; + ISchedulerService scheduler = null; + try + { + var connection = GetDatabaseConnection(applicationSettings, commandlineOptions, silentConsole, true); + + if (!connection.ApplicationSettings.FixedInvalidBackupId) + connection.FixInvalidBackupId(); + + connection.ApplicationSettings.UpgradePasswordToKBDF(); + + // Handle --configure-https option before starting the webserver + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_CONFIGURE_HTTPS)) + HandleConfigureHttps(connection, commandlineOptions); + + // Check for certificate renewal on startup (only for autogenerated certs) + var renewalResult = CheckAndRenewCertificate(connection); + if (renewalResult.RenewalFailedReason != null) + RegisterRenewalFailedNotification(connection, renewalResult.RenewalFailedReason); + + CreateApplicationInstance(applicationSettings.DataFolder, writeToConsoleOnException); + + applicationSettings.StartOrStopUsageReporter = () => StartOrStopUsageReporter(connection); + // Bit messy, but the callback needs the connection, and the connection needs the callback + connection.UpdateUsageReporterCallback(applicationSettings.StartOrStopUsageReporter); + applicationSettings.StartOrStopUsageReporter?.Invoke(); + + AdjustApplicationSettings(connection, commandlineOptions); + + UpdaterManager.OnError += obj => + { + connection.LogError(null, "Error in updater", obj); + }; + + DuplicatiWebserver = StartWebServerAsync(commandlineOptions, connection, logHandler, applicationSettings).Await(); + + connection.SetServiceProvider(DuplicatiWebserver.Provider); + queueRunner = DuplicatiWebserver.Provider.GetRequiredService(); + updatePollThread = DuplicatiWebserver.Provider.GetRequiredService(); + eventPollNotify = DuplicatiWebserver.Provider.GetRequiredService(); + scheduler = DuplicatiWebserver.Provider.GetRequiredService(); + + updatePollThread.Init(Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_UPDATE_CHECK_OPTION)); + + SetPurgeTempFilesTimer(connection, commandlineOptions); + SetCertificateRenewalTimer(connection); + + LiveControl.StateChanged = (e) => { LiveControl_StateChanged(queueRunner, connection, eventPollNotify, scheduler, e); }; + + // Invoke it once to set initial state + LiveControl_StateChanged(queueRunner, connection, eventPollNotify, scheduler, new LiveControls.LiveControlEvent + { + State = LiveControl.State, + TransfersPaused = LiveControl.TransfersPaused, + WaitTimeExpiration = LiveControl.EstimatedPauseEnd + }); + + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, PING_PONG_KEEPALIVE_OPTION)) + { + PingPongThread = new Thread(() => PingPongMethod(applicationSettings)) { IsBackground = true }; + PingPongThread.Start(); + } + + connection.ReWriteAllFieldsIfEncryptionChanged(); + connection.SetPreloadSettingsIfChanged(preloadDbSettings); + EmitWarningsForConfigurationIssues(connection, applicationSettings, commandlineOptions); + + Log.WriteInformationMessage(LOGTAG, "ServerStarted", Strings.Program.ServerStarted(DuplicatiWebserver.Interface, DuplicatiWebserver.Port)); + logMessageToConsole(Strings.Program.ServerStarted(DuplicatiWebserver.Interface, DuplicatiWebserver.Port)); + + var remoteControlUrl = commandlineOptions.GetValueOrDefault(REGISTER_REMOTE_CONTROL_OPTION); + if (!string.IsNullOrWhiteSpace(remoteControlUrl)) + RegisterForRemoteControl(DuplicatiWebserver.Provider.GetRequiredService(), DuplicatiWebserver.Provider.GetRequiredService(), remoteControlUrl, Library.Utility.Utility.ParseBoolOption(commandlineOptions, REGISTER_REMOTE_CONTROL_REREGISTER_OPTION), logMessageToConsole); + + if (!string.IsNullOrWhiteSpace(connection.ApplicationSettings.ClientLicenseKey)) + Proprietary.LicenseChecker.LicenseHelper.SetRemoteClientLicenseKey(connection.ApplicationSettings.ClientLicenseKey); + + if (applicationSettings.Origin == "Server" && connection.ApplicationSettings.AutogeneratedPassphrase) + { + var signinToken = DuplicatiWebserver.Provider.GetRequiredService().CreateSigninToken("server-cli"); + var hostname = (connection.ApplicationSettings.AllowedHostnames ?? string.Empty).Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries).FirstOrDefault(x => x != "*") ?? "localhost"; + var scheme = connection.ApplicationSettings.UseHTTPS ? "https" : "http"; + + var url = $"{scheme}://{hostname}:{DuplicatiWebserver.Port}/signin.html?token={signinToken}"; + Log.WriteWarningMessage(LOGTAG, "ServerStartedSignin", null, Strings.Program.ServerStartedSignin(url)); + logMessageToConsole(Strings.Program.ServerStartedSignin(url)); + } + + DuplicatiWebserver.TerminationTask.ContinueWith((t) => + { + if (t.Exception != null) + { + Log.WriteWarningMessage(LOGTAG, "ServerCrashed", t.Exception, Strings.Program.ServerCrashed(t.Exception.Message)); + logMessageToConsole(Strings.Program.ServerStartedSignin(Strings.Program.ServerCrashed(t.Exception.ToString()))); + } + + terminated = true; + applicationSettings.SignalApplicationExit(); + }, TaskScheduler.Default).FireAndForget(); + + var stopCounter = 0; + Console.CancelKeyPress += (sender, e) => + { + if (Interlocked.Increment(ref stopCounter) <= 1) + { + Log.WriteInformationMessage(LOGTAG, "CancelKeyPressed", "Cancel key pressed, stopping server"); + Task.Run(async () => DuplicatiWebserver?.StopAsync() ?? Task.CompletedTask).FireAndForget(); + } + else + { + Log.WriteWarningMessage(LOGTAG, "CancelKeyPressed", null, "Cancel key pressed twice, terminating now"); + if (OperatingSystem.IsWindows()) + Environment.FailFast("Cancel key pressed twice, terminating now"); + else + Environment.Exit(0); + } + }; + + ServerStartedEvent.Set(); + applicationSettings.ApplicationExit.WaitHandle.WaitOne(); + } + catch (SingleInstance.MultipleInstanceException mex) + { + crashed = true; + Log.WriteErrorMessage(LOGTAG, "MultipleInstanceError", mex, Strings.Program.ServerCrashed(mex.Message)); + System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(mex.ToString())); + if (!writeToConsoleOnException) throw; + + Console.WriteLine(Strings.Program.SeriousError(mex.ToString())); + return 100; + } + catch (Exception ex) + { + crashed = true; + Log.WriteErrorMessage(LOGTAG, "ServerCrashed", ex, Strings.Program.ServerCrashed(ex.Message)); + System.Diagnostics.Trace.WriteLine(Strings.Program.SeriousError(ex.ToString())); + if (writeToConsoleOnException) + { + Console.WriteLine(Strings.Program.SeriousError(ex.ToString())); + return 100; + } + else + throw new Exception(Strings.Program.SeriousError(ex.ToString()), ex); + } + finally + { + Log.WriteInformationMessage(LOGTAG, "ServerStopping", Strings.Program.ServerStopping); + + var steps = new Action[] { + () => eventPollNotify?.SignalNewEvent(), + () => ShutdownModernWebserver(), + () => updatePollThread?.Terminate(), + () => scheduler?.Terminate(true), + () => queueRunner?.Terminate(true), + () => ApplicationInstance?.Dispose(), + () => PurgeTempFilesTimer?.Dispose(), + () => CertificateRenewalTimer?.Dispose(), + () => Library.UsageReporter.Reporter.ShutDown(), + () => PingPongThread?.Interrupt(), + () => + { + Log.WriteInformationMessage(LOGTAG, "ServerStopped", Strings.Program.ServerStopped); + logHandler?.Dispose(); + } + }; + + foreach (var teardownStep in steps) + { + try + { + teardownStep(); + } + catch (Exception ex) + { + // If the server is already crashed, that is the main error + // If the server crashes during teardown, we log that as an error + if (!(crashed || terminated)) + { + System.Diagnostics.Trace.WriteLine(Strings.Program.TearDownError(ex.ToString())); + logMessageToConsole(Strings.Program.TearDownError(ex.ToString())); + } + } + } + } + + return 0; + } + + /// + /// Starts the Duplicati web server + /// + /// The commandline options + /// The connection to use + /// The log write handler + /// The application settings + /// + private static async Task StartWebServerAsync(IReadOnlyDictionary options, Connection connection, ILogWriteHandler logWriteHandler, IApplicationSettings applicationSettings) + { + var server = await WebServerLoader.TryRunServerAsync(options, connection, async parsedOptions => + { + var mappedSettings = new DuplicatiWebserver.InitSettings( + parsedOptions.WebRoot, + parsedOptions.Port, + parsedOptions.Interface, + parsedOptions.AllowedHostnames, + parsedOptions.DisableStaticFiles, + parsedOptions.TokenLifetimeInMinutes, + parsedOptions.SPAPaths, + parsedOptions.CorsOrigins, + parsedOptions.PreAuthTokens + ); + + var server = DuplicatiWebserver.CreateWebServer(mappedSettings, connection, logWriteHandler, applicationSettings); + + // Start the server, but catch any configuration issues + var task = server.StartAsync(); + await Task.WhenAny(task, Task.Delay(500)); + if (task.IsCompleted) + await task; + + return server; + }).ConfigureAwait(false); + + connection.ApplicationSettings.ServerPortChanged |= server.Port != connection.ApplicationSettings.LastWebserverPort; + connection.ApplicationSettings.LastWebserverPort = server.Port; + + return server; + } + + /// + /// Sets up the timer for purging temp files and log data + /// + /// The connection to use + /// The commandline options + private static void SetPurgeTempFilesTimer(Connection connection, Dictionary commandlineOptions) + { + var lastPurge = new DateTime(0); + + TimerCallback purgeTempFilesCallback = (x) => + { + try + { + if (Math.Abs((DateTime.Now - lastPurge).TotalHours) < (DEBUG_MODE ? 1 : 23)) + { + return; + } + + lastPurge = DateTime.Now; + + foreach (var e in connection.GetTempFiles().Where((f) => f.Expires < DateTime.Now)) + { + try + { + if (System.IO.File.Exists(e.Path)) + System.IO.File.Delete(e.Path); + } + catch (Exception ex) + { + connection.LogError(null, $"Failed to delete temp file: {e.Path}", ex); + } + + connection.DeleteTempFile(e.ID); + } + + + Library.Utility.TempFile.RemoveOldApplicationTempFiles((path, ex) => + { + connection.LogError(null, $"Failed to delete temp file: {path}", ex); + }); + + if (!commandlineOptions.TryGetValue("log-retention", out string pts)) + { + pts = DEFAULT_LOG_RETENTION; + } + + connection.PurgeLogData(Library.Utility.Timeparser.ParseTimeInterval(pts, DateTime.Now, true)); + } + catch (Exception ex) + { + connection.LogError(null, "Failed during temp file cleanup", ex); + } + }; + + PurgeTempFilesTimer = + new System.Threading.Timer(purgeTempFilesCallback, null, + DEBUG_MODE ? TimeSpan.FromSeconds(10) : TimeSpan.FromHours(1), + DEBUG_MODE ? TimeSpan.FromHours(1) : TimeSpan.FromDays(1)); + } + + /// + /// Sets up a timer to periodically check for certificate renewal. + /// This ensures certificates are renewed even if the server runs continuously + /// without restarts. + /// + /// The database connection. + private static void SetCertificateRenewalTimer(Connection connection) + { + TimerCallback certificateRenewalCallback = (x) => + { + try + { + // Only check for autogenerated certificates + if (!connection.ApplicationSettings.ServerSSLCertificateAutogenerated) + return; + + var renewalResult = CheckAndRenewCertificate(connection); + + if (renewalResult.Renewed) + { + Log.WriteInformationMessage(LOGTAG, "CertificateRenewedAutomatically", + "SSL certificate was automatically renewed during periodic check."); + } + else if (renewalResult.RenewalFailedReason != null) + { + Log.WriteWarningMessage(LOGTAG, "CertificateRenewalFailed", + null, + $"Automatic certificate renewal failed: {renewalResult.RenewalFailedReason}"); + } + } + catch (Exception ex) + { + Log.WriteErrorMessage(LOGTAG, "CertificateRenewalCheckFailed", ex, + "Failed to check/renew SSL certificate during periodic check."); + } + }; + + CertificateRenewalTimer = + new System.Threading.Timer(certificateRenewalCallback, null, + TimeSpan.FromHours(1), + TimeSpan.FromDays(1)); + } + + /// + /// Registers the server for remote control + /// + /// The remote controller registration + /// The remote controller + /// The remote control URL + /// Whether to re-register + /// Callback to log messages to console + private static void RegisterForRemoteControl(IRemoteControllerRegistration remoteControllerRegistration, IRemoteController remoteController, string remoteControlUrl, bool reRegister, Action logMessageToConsole) + { + if (remoteController.CanEnable) + { + if (!reRegister) + { + Log.WriteInformationMessage(LOGTAG, "RemoteControlAlreadyRegistered", Strings.Program.RemoteControlAlreadyRegistered); + return; + } + + remoteController.DeleteRegistration(); + } + + Task.Run(async () => + { + try + { + var regTask = remoteControllerRegistration.RegisterMachineAsync(remoteControlUrl); + while (!regTask.IsCompleted) + { + // Interface does not have events, so poll it every second + await Task.WhenAny(Task.Delay(1000), regTask).ConfigureAwait(false); + if (!string.IsNullOrWhiteSpace(remoteControllerRegistration.RegistrationUrl)) + { + Log.WriteWarningMessage(LOGTAG, "RemoteControlRegistrationUrl", null, Strings.Program.RemoteControlRegistrationUrl(remoteControllerRegistration.RegistrationUrl)); + logMessageToConsole(Strings.Program.RemoteControlRegistrationUrl(remoteControllerRegistration.RegistrationUrl)); + break; + } + } + + // Await the registration task and ensure any errors are logged + await regTask.ConfigureAwait(false); + } + catch (Exception ex) + { + Log.WriteErrorMessage(LOGTAG, "RemoteControlRegistrationFailed", ex, Strings.Program.RemoteControlRegistrationFailed(ex.Message)); + } + }) + .FireAndForget(); + } + + /// + /// Adjusts the application settings based on commandline options + /// + /// The connection to use + /// The commandline options + private static void AdjustApplicationSettings(Connection connection, Dictionary commandlineOptions) + { + // This clears the JWT config, and a new will be generated, invalidating all existing tokens + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG)) + { + connection.ApplicationSettings.JWTConfig = null; + // Clean up stored tokens as they are now invalid + connection.ExecuteWithCommand((con) => con.ExecuteNonQuery("DELETE FROM TokenFamily")); + } + + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN)) + connection.ApplicationSettings.EnableForeverTokens(); + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS)) + connection.ApplicationSettings.DisableSigninTokens = Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS); + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS)) + connection.ApplicationSettings.DisabledAPIExtensions = commandlineOptions.GetValueOrDefault(WebServerLoader.OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS)? + .Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) + .ToHashSet(StringComparer.OrdinalIgnoreCase) ?? []; + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOLDER_STATUS_SERVICE)) + connection.ApplicationSettings.EnableFolderStatusService = Library.Utility.Utility.ParseBoolOption(commandlineOptions, WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOLDER_STATUS_SERVICE); + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_PASSWORD)) + connection.ApplicationSettings.SetWebserverPassword(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_PASSWORD]); + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES)) + connection.ApplicationSettings.SetAllowedHostnames(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES]); + else if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT)) + connection.ApplicationSettings.SetAllowedHostnames(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]); + + if (commandlineOptions.ContainsKey(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE) && !string.IsNullOrEmpty(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE])) + try + { + connection.ApplicationSettings.Timezone = TimeZoneHelper.FindTimeZone(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE]); + } + catch (Exception ex) + { + throw new UserInformationException(Strings.Program.InvalidTimezone(commandlineOptions[WebServerLoader.OPTION_WEBSERVICE_TIMEZONE]), "InvalidTimeZone", ex); + } + + // The database has recorded a new version + if (connection.ApplicationSettings.UpdatedVersion != null) + { + // Check if the running version is newer than the recorded version + if (UpdaterManager.TryParseVersion(connection.ApplicationSettings.UpdatedVersion.Version) <= UpdaterManager.TryParseVersion(UpdaterManager.SelfVersion.Version)) + { + // Clean up lingering update notifications + var updateNotifications = connection.GetNotifications().Where(x => x.Action == "update:new").ToList(); + foreach (var n in updateNotifications) + connection.DismissNotification(n.ID); + + // Clear up the recorded version + connection.ApplicationSettings.UpdatedVersion = null; + } + } + } + + /// + /// Emits warnings for configuration issues + /// + /// The connection + /// The application settings + /// The commandline options + private static void EmitWarningsForConfigurationIssues(Connection connection, IApplicationSettings applicationSettings, Dictionary commandlineOptions) + { + // First, remove any pending notifications, if the issue is resolved + if (connection.IsEncryptingFields) + { + // Remove any existing unencrypted database notification + var updateNotifications = connection.GetNotifications().Where(x => x.Action == "config:issue:unencrypted-database").ToList(); + foreach (var n in updateNotifications) + connection.DismissNotification(n.ID); + + // Remove any existing HTTPS related notifications + var caKeyNotifications = connection.GetNotifications().Where(x => x.Action == NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE || x.Action == NOTIFICATION_CERTIFICATE_RENEWAL_FAILED).ToList(); + foreach (var n in caKeyNotifications) + connection.DismissNotification(n.ID); + } + + if (OperatingSystem.IsWindows() && !applicationSettings.DataFolder.StartsWith(Util.AppendDirSeparator(Environment.GetFolderPath(Environment.SpecialFolder.Windows)), StringComparison.OrdinalIgnoreCase)) + { + // Remove any existing windows folder used notification + var updateNotifications = connection.GetNotifications().Where(x => x.Action == "config:issue:windows-folder-used").ToList(); + foreach (var n in updateNotifications) + connection.DismissNotification(n.ID); + } + + // Check tempdir free space against volume sizes + CheckTempDirFreeSpace(connection, commandlineOptions); + + // Emit warnings if the application has been updated + if (connection.ApplicationSettings.LastConfigIssueCheckVersion != UpdaterManager.SelfVersion.Version) + { + var updateNotifications = connection.GetNotifications().Where(x => x.Action.StartsWith("config:issue:")).ToList(); + foreach (var n in updateNotifications) + connection.DismissNotification(n.ID); + + if (!connection.IsEncryptingFields && !Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_DB_ENCRYPTION_OPTION)) + { + connection.RegisterNotification( + Serialization.NotificationType.Warning, + "Unencrypted database", + "The database is not encrypted. This is a security risk and should be fixed as soon as possible.", + null, + null, + "config:issue:unencrypted-database", + null, + "UnencryptedDatabase", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == "config:issue:unencrypted-database") ?? self; + } + ); + } + + if (OperatingSystem.IsWindows() && applicationSettings.DataFolder.StartsWith(Util.AppendDirSeparator(Environment.GetFolderPath(Environment.SpecialFolder.Windows)), StringComparison.OrdinalIgnoreCase)) + { + connection.RegisterNotification( + Serialization.NotificationType.Warning, + "Incorrect storage folder", + "The server configuration is stored inside the Windows folder. Please move the configuration to a different location, or it may be deleted on Windows version upgrades.", + null, + null, + "config:issue:windows-folder-used", + null, + "UnencryptedDatabase", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == "config:issue:windows-folder-used") ?? self; + } + ); + } + + connection.ApplicationSettings.LastConfigIssueCheckVersion = UpdaterManager.SelfVersion.Version; + } + } + + /// + /// Checks if the tempdir has enough free space relative to the volume sizes of configured backups. + /// Emits a warning if free space is less than 4 times the volume size. + /// + /// The database connection + /// The commandline options + private static void CheckTempDirFreeSpace(Connection connection, Dictionary commandlineOptions) + { + const string TEMPDIR_WARNING_ACTION = "config:issue:tempdir-low-space"; + + // Get the tempdir path - check Connection settings first, then fall back to system default + var tempDir = TempFolder.SystemTempPath; + var serverSettings = connection.GetSettings(Connection.ANY_BACKUP_ID); + var tempDirSetting = serverSettings?.FirstOrDefault(s => + string.Equals(s.Name, "tempdir", StringComparison.OrdinalIgnoreCase) || string.Equals(s.Name, "--tempdir", StringComparison.OrdinalIgnoreCase)); + if (tempDirSetting != null && !string.IsNullOrEmpty(tempDirSetting.Value)) + tempDir = tempDirSetting.Value; + + // Get free space in tempdir using the Utility method + var spaceInfo = Library.Utility.Utility.GetFreeSpaceForPath(tempDir); + // Could not determine free space, skip the check + if (spaceInfo == null) + return; + + // Get the maximum volume size from all configured backups + var maxVolumeSize = GetMaxVolumeSize(connection, commandlineOptions); + + // Check if free space is less than 4 times the volume size + if (spaceInfo.Value.FreeSpace < maxVolumeSize * Library.Utility.Utility.VOLUME_SIZE_FREE_SPACE_MULTIPLIER) + { + // Emit warning + connection.RegisterNotification( + Serialization.NotificationType.Warning, + "Low temporary folder space", + $"The temporary folder '{tempDir}' has limited free space ({Library.Utility.Utility.FormatSizeString(spaceInfo.Value.FreeSpace)}). " + + $"It is recommended to have at least {Library.Utility.Utility.VOLUME_SIZE_FREE_SPACE_MULTIPLIER} times the volume size ({Library.Utility.Utility.FormatSizeString(maxVolumeSize * Library.Utility.Utility.VOLUME_SIZE_FREE_SPACE_MULTIPLIER)}) available for optimal operation.", + null, + null, + TEMPDIR_WARNING_ACTION, + null, + "TempDirLowSpace", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == TEMPDIR_WARNING_ACTION) ?? self; + } + ); + } + else + { + // Clear any existing warning + var existingNotifications = connection.GetNotifications().Where(x => x.Action == TEMPDIR_WARNING_ACTION).ToList(); + foreach (var n in existingNotifications) + connection.DismissNotification(n.ID); + } + } + + /// + /// Gets the maximum volume size from all configured backups. + /// + /// The database connection + /// The commandline options + /// The maximum volume size in bytes + private static long GetMaxVolumeSize(Connection connection, Dictionary commandlineOptions) + { + const string DEFAULT_VOLUME_SIZE = "50mb"; + long maxVolumeSize = 0; + + // Check if there's a global dblock-size in commandline options + if (commandlineOptions.TryGetValue("dblock-size", out var globalDblockSize) && !string.IsNullOrEmpty(globalDblockSize)) + { + try + { + maxVolumeSize = Sizeparser.ParseSize(globalDblockSize, "mb"); + } + catch + { + // Ignore parsing errors + } + } + + // Check if there's a global dblock-size in the database settings + var serverSettings = connection.GetSettings(Connection.ANY_BACKUP_ID); + var defaultDblockSize = serverSettings?.FirstOrDefault(s => string.Equals(s.Name, "dblock-size", StringComparison.OrdinalIgnoreCase) || string.Equals(s.Name, "--dblock-size", StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrEmpty(defaultDblockSize?.Value)) + try + { + maxVolumeSize = Math.Max(Sizeparser.ParseSize(defaultDblockSize.Value, "mb"), maxVolumeSize); + } + catch + { + // Ignore parsing errors + } + + // If no global setting, use default + maxVolumeSize = Math.Max(maxVolumeSize, Sizeparser.ParseSize(DEFAULT_VOLUME_SIZE, "mb")); + + // Check all configured backups for their volume sizes + foreach (var backup in connection.Backups) + { + try + { + // Get settings for this backup + var backupId = long.Parse(backup.ID); + var settings = connection.GetSettings(backupId); + + // Look for dblock-size setting + var dblockSizeSetting = settings?.FirstOrDefault(s => + string.Equals(s.Name, "dblock-size", StringComparison.OrdinalIgnoreCase) || string.Equals(s.Name, "--dblock-size", StringComparison.OrdinalIgnoreCase)); + + if (dblockSizeSetting != null && !string.IsNullOrEmpty(dblockSizeSetting.Value)) + { + var backupVolumeSize = Sizeparser.ParseSize(dblockSizeSetting.Value, "mb"); + if (backupVolumeSize > maxVolumeSize) + maxVolumeSize = backupVolumeSize; + } + } + catch + { + // Ignore errors for individual backups + } + } + + return maxVolumeSize; + } + + /// + /// Creates the application instance to ensure a single instance is running for the current user + /// + /// The data folder to use for single instance locking + /// Write to console on exception + private static void CreateApplicationInstance(string dataFolder, bool writeToConsoleOnException) + { + try + { + //This will also create DATAFOLDER if it does not exist + ApplicationInstance = new SingleInstance(dataFolder); + } + catch (Exception ex) + { + if (writeToConsoleOnException) + { + Console.WriteLine(Strings.Program.StartupFailure(ex)); + Environment.Exit(200); + } + + throw new Exception(Strings.Program.StartupFailure(ex)); + } + + if (!ApplicationInstance.IsFirstInstance) + { + if (writeToConsoleOnException) + { + Console.WriteLine(Strings.Program.AnotherInstanceDetected); + Environment.Exit(200); + } + + throw new SingleInstance.MultipleInstanceException(Strings.Program.AnotherInstanceDetected); + } + } + + /// + /// Applies environment variables by converting them to commandline options + /// + /// The commandline options + private static void ApplyEnvironmentVariables(Dictionary commandlineOptions) + { + foreach (var key in SupportedCommands.SelectMany(x => (x.Aliases ?? []).Prepend(x.Name)).Distinct()) + { + // Commandline options take precedence + if (commandlineOptions.ContainsKey(key)) + continue; + + var envkey = $"{ENV_NAME_PREFIX}__{key.Replace('-', '_').ToUpperInvariant()}"; + var envval = Environment.GetEnvironmentVariable(envkey); + if (!string.IsNullOrWhiteSpace(envval)) + commandlineOptions[key] = envval; + } + + // Set the encryption key from the environment variable + if (!string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)) && string.IsNullOrWhiteSpace(commandlineOptions.GetValueOrDefault(SETTINGS_ENCRYPTION_KEY_OPTION))) + commandlineOptions[SETTINGS_ENCRYPTION_KEY_OPTION] = Environment.GetEnvironmentVariable(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME); + } + + /// + /// Applies the secret provider to the commandline options and application settings + /// + /// The application settings + /// The commandline options + /// The cancellation token + /// A task that represents the asynchronous operation + private static async Task ApplySecretProviderAsync(IApplicationSettings applicationSettings, Dictionary commandlineOptions, CancellationToken cancellationToken) + => applicationSettings.SecretProvider = await SecretProviderHelper.ApplySecretProviderAsync([], [], commandlineOptions, TempFolder.SystemTempPath, applicationSettings.SecretProvider, cancellationToken).ConfigureAwait(false); + + /// + /// A log destination that writes to the console + /// + /// The minimum log level + private class ConsoleLogDestination(LogMessageType level) : ILogDestination + { + /// + public void WriteMessage(LogEntry entry) + { + if (entry.Level >= level) + Console.WriteLine(entry.AsString(true)); + } + } + + /// + /// Configures logging based on commandline options + /// + /// The log write handler + /// The commandline options + /// A disposable log scope + private static IDisposable ConfigureLogging(ILogWriteHandler logWriteHandler, Dictionary commandlineOptions) + { + IDisposable logScope; + //Log various information in the logfile + if (DEBUG_MODE && !commandlineOptions.ContainsKey(LOG_FILE_OPTION)) + { + var prefix = System.Reflection.Assembly.GetEntryAssembly().GetName().Name.StartsWith("Duplicati.Server") ? "server" : "trayicon"; + commandlineOptions[LOG_FILE_OPTION] = System.IO.Path.Combine(StartupPath, $"Duplicati-{prefix}.debug.log"); + commandlineOptions[LOG_LEVEL_OPTION] = Duplicati.Library.Logging.LogMessageType.Profiling.ToString(); + if (System.IO.File.Exists(commandlineOptions[LOG_FILE_OPTION])) + System.IO.File.Delete(commandlineOptions[LOG_FILE_OPTION]); + } + + logScope = Log.StartScope(logWriteHandler, null); + + if (commandlineOptions.ContainsKey(LOG_FILE_OPTION)) + { + var loglevel = Library.Logging.LogMessageType.Warning; + if (commandlineOptions.ContainsKey(LOG_LEVEL_OPTION)) + Enum.TryParse(commandlineOptions[LOG_LEVEL_OPTION], true, out loglevel); + + logWriteHandler.SetServerFile(commandlineOptions[LOG_FILE_OPTION], loglevel); + } + + if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, LOG_CONSOLE_OPTION)) + { + var loglevel = Library.Logging.LogMessageType.Information; + if (commandlineOptions.ContainsKey(LOG_LEVEL_OPTION)) + Enum.TryParse(commandlineOptions[LOG_LEVEL_OPTION], true, out loglevel); + + logWriteHandler.AppendLogDestination(new ConsoleLogDestination(loglevel), loglevel); + } + + if (commandlineOptions.TryGetValue(WINDOWS_EVENTLOG_OPTION, out var source) && !string.IsNullOrEmpty(source)) + { + if (!OperatingSystem.IsWindows()) + { + Log.WriteWarningMessage(LOGTAG, "WindowsLogNotSupported", null, Strings.Program.WindowsEventLogNotSupported); + } + else + { + if (!WindowsEventLogSource.SourceExists(source)) + { + Log.WriteInformationMessage(LOGTAG, "WindowsLogMissingCreating", null, Strings.Program.WindowsEventLogSourceNotFound(source)); + try + { + WindowsEventLogSource.CreateEventSource(source); + } + catch (Exception ex) + { + Log.WriteWarningMessage(LOGTAG, "WindowsLogFailedCreate", ex, Strings.Program.WindowsEventLogSourceNotCreated(source)); + } + } + + if (WindowsEventLogSource.SourceExists(source)) + { + var loglevel = LogMessageType.Information; + if (commandlineOptions.ContainsKey(WINDOWS_EVENTLOG_LEVEL_OPTION)) + Enum.TryParse(commandlineOptions[WINDOWS_EVENTLOG_LEVEL_OPTION], true, out loglevel); + + logWriteHandler.AppendLogDestination(new WindowsEventLogSource(source), loglevel); + } + } + } + + CrashlogHelper.OnUnobservedTaskException += (ex) => logWriteHandler.WriteMessage(new LogEntry(ex.Message, null, Library.Logging.LogMessageType.Error, LOGTAG, "UnobservedTaskException", ex)); + return logScope; + } + + /// + /// Shows the help information + /// + /// Whether to write to console on exception + /// An integer indicating the result + private static int ShowHelp(bool writeToConsoleOnException) + { + if (writeToConsoleOnException) + { + Console.WriteLine(Strings.Program.HelpDisplayDialog); + + foreach (var arg in SupportedCommands) + Console.WriteLine(Strings.Program.HelpDisplayFormat(arg.Name, arg.LongDescription)); + + return 0; + } + + throw new Exception("Server invoked with --help"); + } + + /// + /// Gets the database connection, upgrading the database if needed + /// + /// The application settings + /// The commandline options + /// Whether to suppress console output + /// Whether to change the database encryption state if the arguments require it + /// The database connection + public static Connection GetDatabaseConnection(IApplicationSettings applicationSettings, Dictionary commandlineOptions, bool silentConsole, bool changeDbEncryption) + { + // Emit a warning if the database is stored in the Windows folder + if (Util.IsPathUnderWindowsFolder(applicationSettings.DataFolder)) + Log.WriteWarningMessage(LOGTAG, "DatabaseInWindowsFolder", null, "The database is stored in the Windows folder, this is not recommended as it will be deleted on Windows upgrades."); + + CrashlogHelper.DefaultLogDir = applicationSettings.DataFolder; + + var sqliteVersion = new Version(Duplicati.Library.SQLiteHelper.SQLiteLoader.SQLiteVersion); + if (sqliteVersion < new Version(3, 6, 3)) + { + //The official Mono SQLite provider is also broken with less than 3.6.3 + throw new Exception(Strings.Program.WrongSQLiteVersion(sqliteVersion, "3.6.3")); + } + + //Create the connection instance + var con = Library.SQLiteHelper.SQLiteLoader.LoadConnection(); + + try + { + var databasePath = System.IO.Path.Combine(applicationSettings.DataFolder, DataFolderManager.SERVER_DATABASE_FILENAME); + + if (!System.IO.Directory.Exists(System.IO.Path.GetDirectoryName(databasePath))) + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(databasePath)); + + // Attempt to open the database, removing any encryption present + Library.SQLiteHelper.SQLiteLoader.OpenDatabaseAsync(con, databasePath, Library.SQLiteHelper.SQLiteRC4Decrypter.GetEncryptionPassword(commandlineOptions)).Await(); + + Library.SQLiteHelper.DatabaseUpgrader.UpgradeDatabase(con, databasePath, typeof(Library.RestAPI.Database.DatabaseSchemaMarker)); + } + catch (Exception ex) + { + //Unwrap the reflection exceptions + if (ex is System.Reflection.TargetInvocationException && ex.InnerException != null) + ex = ex.InnerException; + + throw new Exception(Strings.Program.DatabaseOpenError(ex.Message), ex); + } + + var disableDbEncryption = Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_DB_ENCRYPTION_OPTION); + var requireDbEncryptionKey = Library.Utility.Utility.ParseBoolOption(commandlineOptions, REQUIRE_DB_ENCRYPTION_KEY_OPTION); + var encKey = EncryptedFieldHelper.KeyInstance.CreateKeyIfValid(commandlineOptions.GetValueOrDefault(SETTINGS_ENCRYPTION_KEY_OPTION)); + var usingBlacklistedKey = encKey?.IsBlacklisted ?? false; + var hasValidEncryptionKey = encKey != null; + + // Don't encrypt the database in debug mode, unless explicitly requested + if (DEBUG_MODE) + { + if (string.IsNullOrWhiteSpace(commandlineOptions.GetValueOrDefault(DISABLE_DB_ENCRYPTION_OPTION))) + disableDbEncryption = true; + } + + // If we are supposed to have an encryption key, but do not, try to get it from the (default) secret provider + if (!hasValidEncryptionKey && DefaultSecretProvider != null) + { + string encryptionKey = null; + try + { + encryptionKey = DefaultSecretProvider.ResolveSecretAsync(DATABASE_SECRET_VALUE_NAME, CancellationToken.None).Await(); + } + catch + { + Log.WriteInformationMessage(LOGTAG, "SecretProviderFailedToGetEncryptionKey", null, Strings.Program.SecretProviderFailedToGetEncryptionKey); + } + + // If there is no encryption key, and the secret provider supports setting secrets, try to set the encryption key + if (!disableDbEncryption && string.IsNullOrWhiteSpace(encryptionKey) && DefaultSecretProvider.IsSetSupported) + { + var tmpkey = Convert.ToBase64String(System.Security.Cryptography.RandomNumberGenerator.GetBytes(32)); + try + { + DefaultSecretProvider.SetSecretAsync( + DATABASE_SECRET_VALUE_NAME, tmpkey, false, + CancellationToken.None).Await(); + + // For some cloud providers, this can sometimes fail due to eventual consistency + // For now, the solution is that settings will be encrypted in the next run + var tmp = DefaultSecretProvider.ResolveSecretAsync(DATABASE_SECRET_VALUE_NAME, CancellationToken.None).Await(); + + if (tmp != tmpkey) + throw new Exception("Secret provider did not return the expected value"); + + encryptionKey = tmpkey; + } + catch + { + Log.WriteVerboseMessage(LOGTAG, "SecretProviderFailedToSetEncryptionKey", null, Strings.Program.SecretProviderFailedToSetEncryptionKey); + } + } + + // If we got an encryption key (or created one), apply it + if (!string.IsNullOrWhiteSpace(encryptionKey)) + { + encKey = EncryptedFieldHelper.KeyInstance.CreateKeyIfValid(encryptionKey); + hasValidEncryptionKey = encKey != null; + } + } + + + if (requireDbEncryptionKey && !(hasValidEncryptionKey || disableDbEncryption)) + throw new UserInformationException(Strings.Program.DatabaseEncryptionKeyRequired(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION), "RequireDbEncryptionKey"); + + applicationSettings.SettingsEncryptionKeyProvidedExternally = hasValidEncryptionKey; + + var hasEncryptedFields = false; + try + { + using (var cmd = con.CreateCommand(@$"SELECT ""Value"" FROM ""Option"" WHERE ""Name"" = @Name AND ""BackupID"" = @BackupId")) + hasEncryptedFields = Library.Utility.Utility.ParseBool(cmd + .SetParameterValue("@Name", Database.ServerSettings.CONST.ENCRYPTED_FIELDS) + .SetParameterValue("@BackupId", Connection.SERVER_SETTINGS_ID).ExecuteScalar()?.ToString(), false); + + if (hasEncryptedFields && !hasValidEncryptionKey) + { + Log.WriteWarningMessage(LOGTAG, "EncryptionKeyMissing", null, Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); + if (!silentConsole) + Console.WriteLine(Strings.Program.EncryptionKeyMissing(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)); + } + } + catch + { + // Ignore errors here, as we are just checking for a potential issue + // Only negative effect is that we do not show a potentially helpful warning + } + + if (!hasValidEncryptionKey && !disableDbEncryption) + { + disableDbEncryption = true; + Log.WriteWarningMessage(LOGTAG, "MissingEncryptionKey", null, Strings.Program.NoEncryptionKeySpecified(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); + if (!silentConsole) + Console.WriteLine(Strings.Program.NoEncryptionKeySpecified(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); + } + + if (usingBlacklistedKey && !disableDbEncryption) + { + disableDbEncryption = true; + Log.WriteErrorMessage(LOGTAG, "BlacklistedEncryptionKey", null, Strings.Program.BlacklistedEncryptionKey(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); + if (!silentConsole) + Console.WriteLine(Strings.Program.BlacklistedEncryptionKey(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME, DISABLE_DB_ENCRYPTION_OPTION)); + } + + // If the database is not encrypted, and we are not changing the encryption + // don't pass the key, as that would cause the database to be encrypted + if (!hasEncryptedFields && !changeDbEncryption && hasValidEncryptionKey) + encKey = null; + + return new Connection(con, disableDbEncryption, encKey, applicationSettings.DataFolder, applicationSettings.StartOrStopUsageReporter); + } + + /// + /// Starts or stops the usage reporter based on application settings + /// + /// The connection + private static void StartOrStopUsageReporter(Connection connection) + { + var disableUsageReporter = + string.Equals(connection.ApplicationSettings.UsageReporterLevel, "none", StringComparison.OrdinalIgnoreCase) + || + string.Equals(connection.ApplicationSettings.UsageReporterLevel, "disabled", StringComparison.OrdinalIgnoreCase); + + if (!Enum.TryParse(connection.ApplicationSettings.UsageReporterLevel, true, out var reportLevel)) + Library.UsageReporter.Reporter.SetReportLevel(null, disableUsageReporter); + else + Library.UsageReporter.Reporter.SetReportLevel(reportLevel, disableUsageReporter); + } + + /// + /// This event handler updates the trayicon menu with the current state of the runner. + /// + /// + private static void LiveControl_StateChanged(IQueueRunnerService queueRunnerService, Connection connection, EventPollNotify eventPollNotify, ISchedulerService schedulerService, LiveControls.LiveControlEvent e) + { + var appSettings = connection.ApplicationSettings; + switch (e.State) + { + case LiveControls.LiveControlState.Paused: + { + queueRunnerService.Pause(); + queueRunnerService.GetCurrentTask()?.PauseAsync(e.TransfersPaused).Await(); + appSettings.PausedUntil = e.WaitTimeExpiration; + break; + } + case LiveControls.LiveControlState.Running: + { + queueRunnerService.Resume(); + queueRunnerService.GetCurrentTask()?.ResumeAsync().Await(); + schedulerService?.Reschedule(); + appSettings.PausedUntil = null; + break; + } + default: + Log.WriteWarningMessage(LOGTAG, "InvalidPauseResumeState", null, Strings.Program.InvalidPauseResumeState(LiveControl.State)); + break; + } + + eventPollNotify.SignalNewEvent(); + + } + + /// + /// Simple method for tracking if the server has crashed + /// + /// The application settings + private static void PingPongMethod(IApplicationSettings applicationSettings) + { + var rd = new System.IO.StreamReader(Console.OpenStandardInput()); + var wr = new System.IO.StreamWriter(Console.OpenStandardOutput()); + string line; + while ((line = rd.ReadLine()) != null) + { + if (string.Equals("shutdown", line, StringComparison.OrdinalIgnoreCase)) + { + // TODO: All calls to ApplicationExitEvent and TrayIcon->Quit + // should check if we are running something + applicationSettings.SignalApplicationExit(); + } + else + { + wr.WriteLine("pong"); + wr.Flush(); + } + } + } + + /// + /// Handles the --configure-https option by generating and installing HTTPS certificates. + /// + /// The database connection. + /// The commandline options. + private static void HandleConfigureHttps(Connection connection, Dictionary commandlineOptions) + { + var hostnames = commandlineOptions.GetValueOrDefault(WebServerLoader.OPTION_CONFIGURE_HTTPS_HOSTNAMES); + + // Read existing certificate data from database + var isAutogenerated = connection.ApplicationSettings.ServerSSLCertificateAutogenerated; + var serverCert = connection.ApplicationSettings.ServerSSLCertificate; + var caCert = connection.ApplicationSettings.ServerCACertificate; + var caKey = connection.ApplicationSettings.ServerCACertificateKey; + var caPassword = connection.ApplicationSettings.ServerCACertificatePassword; + + CACertificateData caData = null; + if (!string.IsNullOrEmpty(caCert) && !string.IsNullOrEmpty(caKey) && !string.IsNullOrEmpty(caPassword)) + { + caData = new CACertificateData + { + CACertificate = caCert, + CAKey = caKey, + CAPassword = caPassword + }; + } + + var result = CertificateRenewalChecker.GenerateAndInstallCertificates( + isAutogenerated, + serverCert, + caData, + hostnames, + skipTrustInstallation: false); + + if (result.Success) + { + Log.WriteInformationMessage(LOGTAG, "ConfigureHttpsComplete", "HTTPS certificates configured successfully."); + + // Store generated/renewed certificates in database + if (result.CACertificate != null && result.ServerCertificate != null) + { + // New CA + server certificate generated + var settings = new Dictionary + { + [Database.ServerSettings.CONST.SERVER_CA_CERTIFICATE] = result.CACertificate.CACertificate, + [Database.ServerSettings.CONST.SERVER_CA_CERTIFICATE_KEY] = result.CACertificate.CAKey, + [Database.ServerSettings.CONST.SERVER_CA_CERTIFICATE_PASSWORD] = result.CACertificate.CAPassword, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE] = result.ServerCertificate.ServerCertificate, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATEPASSWORD] = result.ServerCertificate.Password, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE_AUTOGENERATED] = "true" + }; + + connection.ApplicationSettings.UpdateSettings(settings, false); + + // Check if database is encrypted and emit warning if not + if (!connection.IsEncryptingFields) + EmitUnencryptedDatabaseWarning(connection); + } + else if (result.ServerCertificate != null) + { + // Server certificate renewed using existing CA + var settings = new Dictionary + { + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE] = result.ServerCertificate.ServerCertificate, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATEPASSWORD] = result.ServerCertificate.Password, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE_AUTOGENERATED] = "true" + }; + + connection.ApplicationSettings.UpdateSettings(settings, false); + + // Check if database is encrypted and emit warning if not + if (!connection.IsEncryptingFields) + EmitUnencryptedDatabaseWarning(connection); + } + } + else + { + Log.WriteErrorMessage(LOGTAG, "ConfigureHttpsFailed", null, "Failed to configure HTTPS certificates."); + } + } + + /// + /// Checks if the server certificate needs renewal and performs renewal if necessary. + /// Handles database I/O for the renewal process. + /// + /// The database connection. + /// A result object containing renewal status and any failure reason. + private static CertificateRenewalResult CheckAndRenewCertificate(Connection connection) + { + // Read certificate data from database + var isAutogenerated = connection.ApplicationSettings.ServerSSLCertificateAutogenerated; + var serverCert = connection.ApplicationSettings.ServerSSLCertificate; + var caCert = connection.ApplicationSettings.ServerCACertificate; + var caKey = connection.ApplicationSettings.ServerCACertificateKey; + var caPassword = connection.ApplicationSettings.ServerCACertificatePassword; + + CACertificateData caData = null; + if (!string.IsNullOrEmpty(caCert) && !string.IsNullOrEmpty(caKey) && !string.IsNullOrEmpty(caPassword)) + { + caData = new CACertificateData + { + CACertificate = caCert, + CAKey = caKey, + CAPassword = caPassword + }; + } + + // Check and renew if needed + var result = CertificateRenewalChecker.CheckAndRenewIfNeeded(isAutogenerated, serverCert, caData); + + // If renewed, store the new certificate in database + if (result.Renewed && result.RenewedCertificate != null) + { + var settings = new Dictionary + { + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE] = result.RenewedCertificate.ServerCertificate, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATEPASSWORD] = result.RenewedCertificate.Password, + [Database.ServerSettings.CONST.SERVER_SSL_CERTIFICATE_AUTOGENERATED] = "true" + }; + + connection.ApplicationSettings.UpdateSettings(settings, false); + + // Check if database is encrypted and emit warning if not + if (!connection.IsEncryptingFields) + EmitUnencryptedDatabaseWarning(connection); + } + + return result; + } + + /// + /// Emits a warning notification if the database is not encrypted. + /// + /// The database connection. + private static void EmitUnencryptedDatabaseWarning(Connection connection) + { + connection.RegisterNotification( + Serialization.NotificationType.Warning, + "Unencrypted database with CA key", + "The database is not encrypted and contains the CA private key. This is a major security risk and could allow MitM attacks on all HTTPS connections. Consider enabling database encryption.", + null, + null, + NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE, + null, + "UnencryptedDatabaseWithCAKey", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == NOTIFICATION_CA_KEY_UNENCRYPTED_DATABASE) ?? self; + } + ); + } + + /// + /// Registers a notification for certificate renewal failure. + /// + /// The database connection. + /// The reason for the failure. + private static void RegisterRenewalFailedNotification(Connection connection, string reason) + { + connection.RegisterNotification( + Serialization.NotificationType.Error, + "Certificate renewal failed", + $"The automatic renewal of the HTTPS certificate failed: {reason}. Please run '{PackageHelper.GetExecutableName(PackageHelper.NamedExecutable.ConfigureTool)} https regenerate-ca' to generate new certificates.", + null, + null, + NOTIFICATION_CERTIFICATE_RENEWAL_FAILED, + null, + "CertificateRenewalFailed", + null, + (self, all) => + { + return all.FirstOrDefault(x => x.Action == NOTIFICATION_CERTIFICATE_RENEWAL_FAILED) ?? self; + } + ); + } + + /// + /// The default log retention + /// + private static readonly string DEFAULT_LOG_RETENTION = "30D"; + + /// + /// The options related to the secret provider + /// + private static readonly IReadOnlyList SECRET_PROVIDER_OPTIONS = new Options(new Dictionary()).SupportedCommands.Where(x => x.Name.StartsWith("secret-provider")).ToList(); + + /// + /// Gets additional commandline arguments support on Windows + /// + private static readonly ICommandLineArgument[] WindowsOptions = OperatingSystem.IsWindows() + ? [ + new CommandLineArgument(WINDOWS_EVENTLOG_OPTION, CommandLineArgument.ArgumentType.String, Strings.Program.LogwindowseventlogShort, Strings.Program.LogwindowseventlogLong), + new CommandLineArgument(WINDOWS_EVENTLOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LogwindowseventloglevelShort, Strings.Program.LogwindowseventloglevelLong, Library.Logging.LogMessageType.Information.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))) + ] + : []; + + /// + /// Gets a list of all supported commandline options + /// + public static ICommandLineArgument[] SupportedCommands => + [ + .. WindowsOptions, + new CommandLineArgument(TEMPDIR_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.TempdirShort, Strings.Program.TempdirLong, System.IO.Path.GetTempPath()), + new CommandLineArgument(HELP_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.HelpCommandDescription, Strings.Program.HelpCommandDescription), + new CommandLineArgument(PARAMETERS_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ParametersFileOptionShort, Strings.Program.ParametersFileOptionLong, "", PARAMETERS_FILE_OPTION_EXTRAS), + new CommandLineArgument(DataFolderManager.PORTABLE_MODE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PortablemodeCommandDescription, Strings.Program.PortablemodeCommandDescription, DataFolderManager.PORTABLE_MODE.ToString().ToLowerInvariant()), + new CommandLineArgument(LOG_FILE_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.LogfileCommandDescription, Strings.Program.LogfileCommandDescription), + new CommandLineArgument(LOG_LEVEL_OPTION, CommandLineArgument.ArgumentType.Enumeration, Strings.Program.LoglevelCommandDescription, Strings.Program.LoglevelCommandDescription, Library.Logging.LogMessageType.Warning.ToString(), null, Enum.GetNames(typeof(Duplicati.Library.Logging.LogMessageType))), + new CommandLineArgument(LOG_CONSOLE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.LogConsoleDescription, Strings.Program.LogConsoleDescription, false.ToString()), + new CommandLineArgument(WebServerLoader.OPTION_WEBROOT, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverWebrootDescription, Strings.Program.WebserverWebrootDescription, WebServerLoader.DEFAULT_OPTION_WEBROOT), + new CommandLineArgument(WebServerLoader.OPTION_PORT, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPortDescription, Strings.Program.WebserverPortDescription, WebServerLoader.DEFAULT_OPTION_PORT.ToString()), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLEHTTPS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableHTTPSDescription, Strings.Program.WebserverDisableHTTPSDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_REMOVESSLCERTIFICATE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverRemoveCertificateDescription, Strings.Program.WebserverRemoveCertificateDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificateFileDescription, Strings.Program.WebserverCertificateFileDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SSLCERTIFICATEFILEPASSWORD, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverCertificatePasswordDescription, Strings.Program.WebserverCertificatePasswordDescription), + new CommandLineArgument(WebServerLoader.OPTION_INTERFACE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverInterfaceDescription, Strings.Program.WebserverInterfaceDescription, WebServerLoader.DEFAULT_OPTION_INTERFACE), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PASSWORD, CommandLineArgument.ArgumentType.Password, Strings.Program.WebserverPasswordDescription, Strings.Program.WebserverPasswordDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverAllowedhostnamesDescription, Strings.Program.WebserverAllowedhostnamesDescription, null, [WebServerLoader.OPTION_WEBSERVICE_ALLOWEDHOSTNAMES_ALT]), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_RESET_JWT_CONFIG, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverResetJwtConfigDescription, Strings.Program.WebserverResetJwtConfigDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOREVER_TOKEN, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverEnableForeverTokenDescription, Strings.Program.WebserverEnableForeverTokenDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLEAPIEXTENSIONS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverDisableApiExtensionsDescription, Strings.Program.WebserverDisableApiExtensionsDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_API_ONLY, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverApiOnlyDescription, Strings.Program.WebserverApiOnlyDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_DISABLE_SIGNIN_TOKENS, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverDisableSigninTokensDescription, Strings.Program.WebserverDisableSigninTokensDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_SPAPATHS, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverSpaPathsDescription, Strings.Program.WebserverSpaPathsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_TIMEZONE, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverTimezoneDescription, Strings.Program.WebserverTimezoneDescription, TimeZoneHelper.GetLocalTimeZone(), null, TimeZoneHelper.GetTimeZones().Select(x => x.Id).ToArray()), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_CORS_ORIGINS, CommandLineArgument.ArgumentType.Path, Strings.Program.WebserverCorsOriginsDescription, Strings.Program.WebserverCorsOriginsDescription, WebServerLoader.DEFAULT_OPTION_SPAPATHS), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_PRE_AUTH_TOKENS, CommandLineArgument.ArgumentType.String, Strings.Program.WebserverPreAuthTokensDescription, Strings.Program.WebserverPreAuthTokensDescription), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_TOKENDURATION, CommandLineArgument.ArgumentType.Timespan, Strings.Program.WebserverTokenDurationDescription, Strings.Program.WebserverTokenDurationDescription, WebServerLoader.DEFAULT_OPTION_TOKENDURATION), + new CommandLineArgument(WebServerLoader.OPTION_WEBSERVICE_ENABLE_FOLDER_STATUS_SERVICE, CommandLineArgument.ArgumentType.Boolean, Strings.Program.WebserverEnableFolderStatusServiceDescription, Strings.Program.WebserverEnableFolderStatusServiceDescription), + new CommandLineArgument(WebServerLoader.OPTION_CONFIGURE_HTTPS, CommandLineArgument.ArgumentType.Boolean, Strings.Program.ConfigureHttpsShort, Strings.Program.ConfigureHttpsLong), + new CommandLineArgument(WebServerLoader.OPTION_CONFIGURE_HTTPS_HOSTNAMES, CommandLineArgument.ArgumentType.String, Strings.Program.ConfigureHttpsHostnamesShort, Strings.Program.ConfigureHttpsHostnamesLong), + new CommandLineArgument(PING_PONG_KEEPALIVE_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.PingpongkeepaliveShort, Strings.Program.PingpongkeepaliveLong), + new CommandLineArgument(DISABLE_UPDATE_CHECK_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisableupdatecheckShort, Strings.Program.DisableupdatecheckLong), + new CommandLineArgument(LOG_RETENTION_OPTION, CommandLineArgument.ArgumentType.Timespan, Strings.Program.LogretentionShort, Strings.Program.LogretentionLong, DEFAULT_LOG_RETENTION), + new CommandLineArgument(DataFolderManager.SERVER_DATAFOLDER_OPTION, CommandLineArgument.ArgumentType.Path, Strings.Program.ServerdatafolderShort, Strings.Program.ServerdatafolderLong(DataFolderManager.DATAFOLDER_ENV_NAME), DataFolderManager.GetDataFolder(DataFolderManager.AccessMode.ProbeOnly)), + new CommandLineArgument(DISABLE_DB_ENCRYPTION_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.DisabledbencryptionShort, Strings.Program.DisabledbencryptionLong), + new CommandLineArgument(REQUIRE_DB_ENCRYPTION_KEY_OPTION, CommandLineArgument.ArgumentType.Boolean, Strings.Program.RequiredbencryptionShort, Strings.Program.RequiredbencryptionLong), + new CommandLineArgument(SETTINGS_ENCRYPTION_KEY_OPTION, CommandLineArgument.ArgumentType.Password, Strings.Program.SettingsencryptionkeyShort, Strings.Program.SettingsencryptionkeyLong(EncryptedFieldHelper.ENVIROMENT_VARIABLE_NAME)), + new CommandLineArgument(REGISTER_REMOTE_CONTROL_OPTION, CommandLineArgument.ArgumentType.String, Strings.Program.RegisterRemoteControlShort, Strings.Program.RegisterRemoteControlLong), + new CommandLineArgument(REGISTER_REMOTE_CONTROL_REREGISTER_OPTION, CommandLineArgument.ArgumentType.String, Strings.Program.RegisterRemoteControlReregisterShort, Strings.Program.RegisterRemoteControlReregisterLong), + new CommandLineArgument(ALLOWED_BACKEND_MODULES, CommandLineArgument.ArgumentType.String, Strings.Program.AllowedBackendModulesShort, Strings.Program.AllowedBackendModulesLong), + new CommandLineArgument(ALLOWED_ENCRYPTION_MODULES, CommandLineArgument.ArgumentType.String, Strings.Program.AllowedEncryptionModulesShort, Strings.Program.AllowedEncryptionModulesLong), + new CommandLineArgument(ALLOWED_COMPRESSION_MODULES, CommandLineArgument.ArgumentType.String, Strings.Program.AllowedCompressionModulesShort, Strings.Program.AllowedCompressionModulesLong), + .. SECRET_PROVIDER_OPTIONS + ]; + + /// + /// List of known duplicate option names + /// + public static readonly HashSet KnownDuplicateOptions = new HashSet(StringComparer.OrdinalIgnoreCase); + + /// + /// Reads options from a file and applies them to the current commandline arguments and options + /// + /// The filename + /// The filter + /// The commandline arguments + /// The options + /// True if successful + private static void ReadOptionsFromFile(string filename, ref IFilter filter, List cargs, Dictionary options) + { + try + { + var fargs = new List(Library.Utility.Utility.ReadFileWithDefaultEncoding(Environment.ExpandEnvironmentVariables(filename)).Replace("\r\n", "\n").Replace("\r", "\n").Split(new String[] { "\n" }, StringSplitOptions.RemoveEmptyEntries).Select(x => x.Trim())); + var newsource = new List(); + string newtarget = null; + string prependfilter = null; + string appendfilter = null; + string replacefilter = null; + + var tmpparsed = FilterCollector.ExtractOptions(fargs, (key, value) => + { + if (key.Equals("source", StringComparison.OrdinalIgnoreCase)) + { + newsource.Add(value); + return false; + } + else if (key.Equals("target", StringComparison.OrdinalIgnoreCase)) + { + newtarget = value; + return false; + } + else if (key.Equals("append-filter", StringComparison.OrdinalIgnoreCase)) + { + appendfilter = value; + return false; + } + else if (key.Equals("prepend-filter", StringComparison.OrdinalIgnoreCase)) + { + prependfilter = value; + return false; + } + else if (key.Equals("replace-filter", StringComparison.OrdinalIgnoreCase)) + { + replacefilter = value; + return false; + } + + return true; + }); + + var opt = tmpparsed.Item1; + var newfilter = tmpparsed.Item2; + + // If the user specifies parameters-file, all filters must be in the file. + // Allowing to specify some filters on the command line could result in wrong filter ordering + if (!filter.Empty && !newfilter.Empty) + throw new UserInformationException(Strings.Program.FiltersCannotBeUsedWithFileError2, "FiltersCannotBeUsedOnCommandLineAndInParameterFile"); + + if (!newfilter.Empty) + filter = newfilter; + + if (!string.IsNullOrWhiteSpace(prependfilter)) + filter = FilterExpression.Combine(FilterExpression.Deserialize(prependfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)), filter); + + if (!string.IsNullOrWhiteSpace(appendfilter)) + filter = FilterExpression.Combine(filter, FilterExpression.Deserialize(appendfilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries))); + + if (!string.IsNullOrWhiteSpace(replacefilter)) + filter = FilterExpression.Deserialize(replacefilter.Split(new string[] { System.IO.Path.PathSeparator.ToString() }, StringSplitOptions.RemoveEmptyEntries)); + + foreach (var keyvalue in opt) + options[keyvalue.Key] = keyvalue.Value; + + if (!string.IsNullOrEmpty(newtarget)) + { + if (cargs.Count <= 1) + cargs.Add(newtarget); + else + cargs[1] = newtarget; + } + + if (cargs.Count >= 1 && cargs[0].Equals("backup", StringComparison.OrdinalIgnoreCase)) + cargs.AddRange(newsource); + else if (newsource.Count > 0) + Log.WriteVerboseMessage(LOGTAG, "NotUsingBackupSources", Strings.Program.SkippingSourceArgumentsOnNonBackupOperation); + } + catch (Exception e) + { + throw new Exception(Strings.Program.FailedToParseParametersFileError(filename, e.Message)); + } + } + } +} diff --git a/Duplicati/Server/WebServerLoader.cs b/Duplicati/Server/WebServerLoader.cs index 6d47c1b1b..0e9ad5a11 100644 --- a/Duplicati/Server/WebServerLoader.cs +++ b/Duplicati/Server/WebServerLoader.cs @@ -205,7 +205,7 @@ public static class WebServerLoader /// /// A set of options /// The method to start the server - public static async Task TryRunServer(IReadOnlyDictionary options, Connection connection, Func> createServer) + public static async Task TryRunServerAsync(IReadOnlyDictionary options, Connection connection, Func> createServer) { var ports = Enumerable.Empty(); options.TryGetValue(OPTION_PORT, out var portstring); diff --git a/Duplicati/UnitTest/ArchiveAttributeTests.cs b/Duplicati/UnitTest/ArchiveAttributeTests.cs index 8543f6c47..abf853a71 100644 --- a/Duplicati/UnitTest/ArchiveAttributeTests.cs +++ b/Duplicati/UnitTest/ArchiveAttributeTests.cs @@ -87,7 +87,7 @@ namespace Duplicati.UnitTest [Test] [Category("Backend")] - public void TestArchiveAttributes() + public async Task TestArchiveAttributesAsync() { var testopts = TestOptions; testopts["blocksize"] = "100kb"; @@ -101,7 +101,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -109,7 +109,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), new byte[1024 * 1024 * 2]); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -129,18 +129,18 @@ namespace Duplicati.UnitTest remotefilenames.TryGetValue(x.Name, out var r) ? r : false)); BackendLoader.AddBackend(new ArchiveEnabledBackend()); - void RunTest() + async Task RunTestAsync() { using (var c = new Library.Main.Controller(ArchiveEnabledBackend.Key + "://" + TARGETFOLDER, testopts, null)) { - var res = c.Test(remotefilenames.Count); + var res = await c.TestAsync(remotefilenames.Count); TestUtils.AssertResults(res); Assert.AreEqual(remotefilenames.Where(x => !x.Value).Count() + protectedfiles.Count, res.Verifications.Count()); } } // Run the test with no files archived - RunTest(); + await RunTestAsync(); // Archive some files var archived = remotefilenames.Keys.Take(5).ToArray(); @@ -148,28 +148,28 @@ namespace Duplicati.UnitTest remotefilenames[k] = true; // Run the test with some files archived - RunTest(); + await RunTestAsync(); // Archive all files foreach (var k in remotefilenames.Keys.ToArray()) remotefilenames[k] = true; // Run the test with all files archived - RunTest(); + await RunTestAsync(); // Unarchive some files foreach (var k in archived) remotefilenames[k] = true; // Run the test with some files unarchived - RunTest(); + await RunTestAsync(); // Unarchive all files foreach (var k in remotefilenames.Keys.ToArray()) remotefilenames[k] = false; // Run the test with all files unarchived - RunTest(); + await RunTestAsync(); } } } diff --git a/Duplicati/UnitTest/BackendToolTests.cs b/Duplicati/UnitTest/BackendToolTests.cs index db16349f7..3016ebd0e 100755 --- a/Duplicati/UnitTest/BackendToolTests.cs +++ b/Duplicati/UnitTest/BackendToolTests.cs @@ -22,6 +22,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Main; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -33,7 +34,7 @@ namespace Duplicati.UnitTest { [Test] [Category("BackendTool")] - public void Get() + public async Task GetAsync() { // Files to create in MB. int[] fileSizes = { 10, 20, 30 }; @@ -48,9 +49,9 @@ namespace Duplicati.UnitTest // Run a backup. var options = new Dictionary(TestOptions); var backendURL = "file://" + this.TARGETFOLDER; - using (Controller c = new Controller(backendURL, options, null)) + using (var c = new Controller(backendURL, options, null)) { - var backupResults = c.Backup(new[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { DATAFOLDER }); foreach (var backupResultsWarning in backupResults.Warnings) { TestContext.WriteLine("Backend result warning:" + backupResultsWarning); diff --git a/Duplicati/UnitTest/BackupExclusionAttributeTests.cs b/Duplicati/UnitTest/BackupExclusionAttributeTests.cs index 90a2159e5..b099642ca 100644 --- a/Duplicati/UnitTest/BackupExclusionAttributeTests.cs +++ b/Duplicati/UnitTest/BackupExclusionAttributeTests.cs @@ -66,19 +66,18 @@ public class BackupExclusionAttributeTests : BasicSetupHelper => throw new NotImplementedException(); } - private static bool InvokeHasBackupExclusionAttribute(ISourceProviderEntry entry) + private static Task InvokeHasBackupExclusionAttributeAsync(ISourceProviderEntry entry) { var method = typeof(FileEnumerationProcess).GetMethod( "HasBackupExclusionAttribute", BindingFlags.NonPublic | BindingFlags.Static); Assert.That(method, Is.Not.Null, "HasBackupExclusionAttribute not found via reflection"); - var task = (Task)method!.Invoke(null, [entry, CancellationToken.None])!; - return task.GetAwaiter().GetResult(); + return (Task)method!.Invoke(null, [entry, CancellationToken.None])!; } [Test] - public void HasBackupExclusionAttribute_ReturnsFalse_WhenMetadataIsEmpty() + public async Task HasBackupExclusionAttribute_ReturnsFalse_WhenMetadataIsEmpty_Async() { var entry = new TestEntry { @@ -86,13 +85,13 @@ public class BackupExclusionAttributeTests : BasicSetupHelper MinorMetadata = new Dictionary() }; - var result = InvokeHasBackupExclusionAttribute(entry); + var result = await InvokeHasBackupExclusionAttributeAsync(entry); Assert.That(result, Is.False); } [Test] - public void HasBackupExclusionAttribute_DetectsKnownExclusionAttributes() + public async Task HasBackupExclusionAttribute_DetectsKnownExclusionAttributes_Async() { var entry = new TestEntry { @@ -102,16 +101,16 @@ public class BackupExclusionAttributeTests : BasicSetupHelper // macOS attribute entry.MinorMetadata["unix-ext:com.apple.metadata:com_apple_backup_excludeItem"] = "ignored"; - Assert.That(InvokeHasBackupExclusionAttribute(entry), Is.True, "macOS exclusion attribute was not detected"); + Assert.That(await InvokeHasBackupExclusionAttributeAsync(entry), Is.True, "macOS exclusion attribute was not detected"); // Linux system attribute entry.MinorMetadata.Clear(); entry.MinorMetadata["unix-ext:duplicati.exclude"] = "ignored"; - Assert.That(InvokeHasBackupExclusionAttribute(entry), Is.True, "Linux exclusion attribute was not detected"); + Assert.That(await InvokeHasBackupExclusionAttributeAsync(entry), Is.True, "Linux exclusion attribute was not detected"); // Linux user attribute entry.MinorMetadata.Clear(); entry.MinorMetadata["unix-ext:user.duplicati.exclude"] = "ignored"; - Assert.That(InvokeHasBackupExclusionAttribute(entry), Is.True, "Linux user exclusion attribute was not detected"); + Assert.That(await InvokeHasBackupExclusionAttributeAsync(entry), Is.True, "Linux user exclusion attribute was not detected"); } } \ No newline at end of file diff --git a/Duplicati/UnitTest/BorderTests.cs b/Duplicati/UnitTest/BorderTests.cs index 9297c032a..9ffd0a78c 100644 --- a/Duplicati/UnitTest/BorderTests.cs +++ b/Duplicati/UnitTest/BorderTests.cs @@ -27,6 +27,8 @@ using System.Collections.Generic; using Duplicati.Library.Interface; using Assert = NUnit.Framework.Legacy.ClassicAssert; using Duplicati.Library.Utility; +using System.Threading.Tasks; +using Duplicati.Library.Main; namespace Duplicati.UnitTest { @@ -34,154 +36,124 @@ namespace Duplicati.UnitTest { [Test] [Category("Border")] - public void Run10kNoProgress() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["disable-file-scanner"] = "true"; - }); - } + public Task Run10kNoProgressAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["disable-file-scanner"] = "true"; + }); [Test] [Category("Border")] - public void Run10k() - { - RunCommands(1024 * 10); - } + public Task Run10kAsync() + => RunCommandsAsync(1024 * 10); [Test] [Category("Border")] - public void Run10mb() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["blocksize"] = "10mb"; - opts["dblock-size"] = "25mb"; - }); - } + public Task Run10mbAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["blocksize"] = "10mb"; + opts["dblock-size"] = "25mb"; + }); [Test] [Category("Border")] - public void Run100k() - { - RunCommands(1024 * 100); - } + public Task Run100kAsync() + => RunCommandsAsync(1024 * 100); [Test] [Category("Border")] - public void Run12345_1() - { - RunCommands(12345); - } + public Task Run12345_1Async() + => RunCommandsAsync(12345); [Test] [Category("Border")] - public void Run12345_2() - { - RunCommands(12345, 1024 * 1024 * 10); - } + public Task Run12345_2Async() + => RunCommandsAsync(12345, 1024 * 1024 * 10); [Test] [Category("Border")] - public void RunNoMetadata() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["skip-metadata"] = "true"; - }); - } + public Task RunNoMetadataAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["skip-metadata"] = "true"; + }); [Test] [Category("Border")] - public void RunMD5() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["block-hash-algorithm"] = "MD5"; - opts["file-hash-algorithm"] = "MD5"; - }); - } + public Task RunMD5Async() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["block-hash-algorithm"] = "MD5"; + opts["file-hash-algorithm"] = "MD5"; + }); [Test] [Category("Border")] - public void RunSHA384() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["block-hash-algorithm"] = "SHA384"; - opts["file-hash-algorithm"] = "SHA384"; - }); - } + public Task RunSHA384Async() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["block-hash-algorithm"] = "SHA384"; + opts["file-hash-algorithm"] = "SHA384"; + }); [Test] [Category("Border")] - public void RunMixedBlockFile_1() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["block-hash-algorithm"] = "MD5"; - opts["file-hash-algorithm"] = "SHA1"; - }); - } + public Task RunMixedBlockFile_1Async() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["block-hash-algorithm"] = "MD5"; + opts["file-hash-algorithm"] = "SHA1"; + }); [Test] [Category("Border")] - public void RunMixedBlockFile_2() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["block-hash-algorithm"] = "MD5"; - opts["file-hash-algorithm"] = "SHA256"; - }); - } + public Task RunMixedBlockFile_2Async() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["block-hash-algorithm"] = "MD5"; + opts["file-hash-algorithm"] = "SHA256"; + }); [Test] [Category("Border")] - public void RunNoIndexFiles() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["index-file-policy"] = "None"; - }); - } + public Task RunNoIndexFilesAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["index-file-policy"] = "None"; + }); [Test] [Category("Border")] - public void RunSlimIndexFiles() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["index-file-policy"] = "Lookup"; - }); - } + public Task RunSlimIndexFilesAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["index-file-policy"] = "Lookup"; + }); [Test] [Category("Border")] - public void RunQuickTimestamps() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["check-filetime-only"] = "true"; - }); - } + public Task RunQuickTimestampsAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["check-filetime-only"] = "true"; + }); [Test] [Category("Border")] - public void RunFullScan() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["disable-filetime-check"] = "true"; - }); - } + public Task RunFullScanAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["disable-filetime-check"] = "true"; + }); [Test] [Category("Border")] - public void Run10kBackupRead() + public Task Run10kBackupReadAsync() { if (!PermissionHelper.HasSeBackupPrivilege()) - return; + return Task.CompletedTask; try { @@ -189,10 +161,10 @@ namespace Duplicati.UnitTest } catch (Exception) { - return; + return Task.CompletedTask; } - RunCommands(1024 * 10, modifyOptions: opts => + return RunCommandsAsync(1024 * 10, modifyOptions: opts => { opts["backupread-policy"] = "required"; }); @@ -200,23 +172,28 @@ namespace Duplicati.UnitTest [Test] [Category("Border")] - public void Run10kTgzCompression() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["compression-module"] = "tgz"; - }); - } + public Task Run10kTgzCompressionAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["compression-module"] = "tgz"; + }); [Test] [Category("Border")] - public void Run10kTzstdCompression() - { - RunCommands(1024 * 10, modifyOptions: opts => - { - opts["compression-module"] = "tzstd"; - }); - } + public Task Run10kTzstdCompressionAsync() + => RunCommandsAsync(1024 * 10, modifyOptions: opts => + { + opts["compression-module"] = "tzstd"; + }); + + // [Test] + // [Category("Border")] + // public Task Run10kIPCAsync() + // => RunCommandsAsync(1024 * 10, modifyOptions: opts => + // { + // opts["rpc-controller"] = "1"; + // }); + public static Dictionary WriteTestFilesToFolder(string targetfolder, int blocksize, int basedatasize = 0) { @@ -252,17 +229,25 @@ namespace Duplicati.UnitTest return filenames; } - private void RunCommands(int blocksize, int basedatasize = 0, Action> modifyOptions = null) + private async Task RunCommandsAsync(int blocksize, int basedatasize = 0, Action> modifyOptions = null) { var testopts = TestOptions; testopts["blocksize"] = blocksize.ToString() + "b"; modifyOptions?.Invoke(testopts); + var useRpcController = testopts.ContainsKey("rpc-controller"); + testopts.Remove("rpc-controller"); + + async Task CreateControllerAsync(string url, Dictionary options, IMessageSink messageSink) + { + return new Controller(url, options, messageSink); + } + var filenames = WriteTestFilesToFolder(DATAFOLDER, blocksize, basedatasize); - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + IBackupResults backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); // TODO: This sometimes results in a "No block hash found for file: C:\projects\duplicati\testdata\backup-data\a-0" warning. @@ -274,8 +259,8 @@ namespace Duplicati.UnitTest testopts.Remove("block-hash-algorithm"); testopts.Remove("file-hash-algorithm"); - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) - TestUtils.AssertResults(c.List("*")); + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) + TestUtils.AssertResults(await c.ListAsync("*")); // Do a "touch" on files to trigger a re-scan, which should do nothing //foreach (var k in filenames) @@ -287,9 +272,9 @@ namespace Duplicati.UnitTest foreach (var k in filenames) File.WriteAllBytes(Path.Combine(DATAFOLDER, "b" + k.Key), data.Take(k.Value).ToArray()); - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); @@ -309,30 +294,30 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "c" + k.Key), data.Take(k.Value).ToArray()); } - System.Threading.Tasks.Task.Delay(1000).Wait(); + await Task.Delay(1000); - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); TestUtils.AssertResults(r); //ProgressWriteLine("Newest before deleting:"); //ProgressWriteLine(string.Join(Environment.NewLine, r.Files.Select(x => x.Path))); Assert.AreEqual((filenames.Count * 3) + 1, r.Files.Count()); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0, no_local_db = true }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { version = 0, no_local_db = true }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); TestUtils.AssertResults(r); //ProgressWriteLine("Newest without db:"); //ProgressWriteLine(string.Join(Environment.NewLine, r.Files.Select(x => x.Path))); Assert.AreEqual((filenames.Count * 3) + 1, r.Files.Count()); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); if (File.Exists(recreatedDatabaseFile)) @@ -340,54 +325,54 @@ namespace Duplicati.UnitTest testopts["dbpath"] = recreatedDatabaseFile; - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts, null)) + TestUtils.AssertResults(await c.RepairAsync()); - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts, null)) { - IListResults listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(3, listResults.Filesets.Count()); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 2 }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { version = 2 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); TestUtils.AssertResults(r); //ProgressWriteLine("V2 after delete:"); //ProgressWriteLine(string.Join(Environment.NewLine, r.Files.Select(x => x.Path))); Assert.AreEqual((filenames.Count * 1) + 1, r.Files.Count()); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 1 }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { version = 1 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); TestUtils.AssertResults(r); //ProgressWriteLine("V1 after delete:"); //ProgressWriteLine(string.Join(Environment.NewLine, r.Files.Select(x => x.Path))); Assert.AreEqual((filenames.Count * 2) + 1, r.Files.Count()); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); TestUtils.AssertResults(r); //ProgressWriteLine("Newest after delete:"); //ProgressWriteLine(string.Join(Environment.NewLine, r.Files.Select(x => x.Path))); Assert.AreEqual((filenames.Count * 3) + 1, r.Files.Count()); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) { - var r = c.Test(long.MaxValue); + var r = await c.TestAsync(long.MaxValue); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.IsFalse(r.Verifications.Any(p => p.Value.Any())); } - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) { - var r = c.Restore(null); + var r = await c.RestoreAsync(null); TestUtils.AssertResults(r); Assert.AreEqual(filenames.Count * 3, r.RestoredFiles); } @@ -396,9 +381,9 @@ namespace Duplicati.UnitTest using (var tf = new Library.Utility.TempFolder()) { - using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = (string)tf }), null)) + using (var c = await CreateControllerAsync("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = (string)tf }), null)) { - var r = c.Restore(new string[] { Path.Combine(DATAFOLDER, "a") + "*" }); + var r = await c.RestoreAsync(new string[] { Path.Combine(DATAFOLDER, "a") + "*" }); TestUtils.AssertResults(r); Assert.AreEqual(filenames.Count, r.RestoredFiles); } diff --git a/Duplicati/UnitTest/CertificateTests.cs b/Duplicati/UnitTest/CertificateTests.cs index d44fd8645..896c35569 100644 --- a/Duplicati/UnitTest/CertificateTests.cs +++ b/Duplicati/UnitTest/CertificateTests.cs @@ -173,7 +173,6 @@ public class CertificateTests } [Test] - [Platform(Exclude = "MacOsX", Reason = "macOS keychain export limitations")] public void CreatePfxBundle_CreatesValidPfx() { // Arrange @@ -309,7 +308,6 @@ public class CertificateTests } [Test] - [Platform(Exclude = "MacOsX", Reason = "macOS keychain export limitations")] public void SerializeDeserializeCertificateWithKey_RoundTripsCorrectly() { // Arrange @@ -404,7 +402,6 @@ public class CertificateTests } [Test] - [Platform(Exclude = "MacOsX", Reason = "macOS keychain export limitations")] public void SerializeCertificateWithKey_WrongPassword_ThrowsException() { // Arrange diff --git a/Duplicati/UnitTest/CommandLineOperationsTests.cs b/Duplicati/UnitTest/CommandLineOperationsTests.cs index 2c51e2a0d..962f11fcb 100644 --- a/Duplicati/UnitTest/CommandLineOperationsTests.cs +++ b/Duplicati/UnitTest/CommandLineOperationsTests.cs @@ -62,12 +62,12 @@ namespace Duplicati.UnitTest } [SetUp] - public void SetUp() + public async Task SetUpAsync() { if (!systemIO.FileExists(zipAlternativeFilepath)) { var url = $"{S3_URL}{this.zipFilename}"; - DownloadS3FileIfNewer(zipFilepath, url); + await DownloadS3FileIfNewerAsync(zipFilepath, url); ZipFileExtractToDirectory(this.zipFilepath, BASEFOLDER); } else @@ -76,9 +76,6 @@ namespace Duplicati.UnitTest } } - private void DownloadS3FileIfNewer(string destinationFilePath, string url, int retries = 5) - => DownloadS3FileIfNewerAsync(destinationFilePath, url, retries).Await(); - public static async Task DownloadS3FileIfNewerAsync(string destinationFilePath, string url, int retries = 5) { do @@ -128,7 +125,7 @@ namespace Duplicati.UnitTest await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); try { - System.Net.Dns.GetHostEntry(new System.Uri(url).Host); + await System.Net.Dns.GetHostEntryAsync(new System.Uri(url).Host); } catch (Exception) { diff --git a/Duplicati/UnitTest/CompactBlockVolumeIdConstraintTests.cs b/Duplicati/UnitTest/CompactBlockVolumeIdConstraintTests.cs index b99a2b996..fabf1c187 100644 --- a/Duplicati/UnitTest/CompactBlockVolumeIdConstraintTests.cs +++ b/Duplicati/UnitTest/CompactBlockVolumeIdConstraintTests.cs @@ -26,6 +26,7 @@ using System.Linq; using System.Collections.Generic; using Microsoft.Data.Sqlite; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; #nullable enable @@ -38,7 +39,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Disruption"), Category("Bug")] - public void CompactWithControllerSequence_ShouldNotCrash() + public async Task CompactWithControllerSequence_ShouldNotCrash_Async() { var testopts = new Dictionary(TestOptions) { @@ -64,9 +65,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(largeFile, largeContent); using (var c = new Library.Main.Controller(target, testopts, null)) - { - c.Backup(new[] { DATAFOLDER }); - } + await c.BackupAsync(new[] { DATAFOLDER }); File.Delete(largeFile); @@ -75,18 +74,14 @@ namespace Duplicati.UnitTest testopts["allow-full-removal"] = "true"; using (var c = new Library.Main.Controller(target, testopts, null)) - { - c.Backup(new[] { DATAFOLDER }); - } + await c.BackupAsync(new[] { DATAFOLDER }); testopts.Remove("no-auto-compact"); testopts["threshold"] = "5"; // Run compact, it should not crash. using (var c = new Library.Main.Controller(target, testopts, null)) - { - c.Compact(); - } + await c.CompactAsync(); } /// @@ -100,7 +95,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Disruption"), Category("Bug")] - public void CompactWithControllerSequence_AddBackFile_TriggersNotNullConstraint() + public async Task CompactWithControllerSequence_AddBackFile_TriggersNotNullConstraint_Async() { var testopts = new Dictionary(TestOptions) { @@ -128,9 +123,7 @@ namespace Duplicati.UnitTest // Step 1: Backup with both files using (var c = new Library.Main.Controller(target, testopts, null)) - { - c.Backup(new[] { DATAFOLDER }); - } + await c.BackupAsync(new[] { DATAFOLDER }); // Step 2: Remove large file and backup (deletes old version, moves large blocks to DeletedBlock) File.Delete(largeFile); @@ -138,16 +131,12 @@ namespace Duplicati.UnitTest testopts["allow-full-removal"] = "true"; using (var c = new Library.Main.Controller(target, testopts, null)) - { - c.Backup(new[] { DATAFOLDER }); - } + await c.BackupAsync(new[] { DATAFOLDER }); // Step 3: Add large file back and backup (moves large blocks back to Block) File.WriteAllBytes(largeFile, largeContent); using (var c = new Library.Main.Controller(target, testopts, null)) - { - c.Backup(new[] { DATAFOLDER }); - } + await c.BackupAsync(new[] { DATAFOLDER }); // Simulate the faulty database state that the natural flow does not create: // blocks exist in both Block and DeletedBlock with matching Hash/Size/VolumeID. @@ -155,14 +144,14 @@ namespace Duplicati.UnitTest // entries from DeletedBlock after moving them back to Block. using (var db = new SqliteConnection($"Data Source={DBFILE};Pooling=False")) { - db.Open(); + await db.OpenAsync(); using (var cmd = db.CreateCommand()) { cmd.CommandText = @" INSERT OR IGNORE INTO ""DeletedBlock"" (""Hash"", ""Size"", ""VolumeID"") SELECT ""Hash"", ""Size"", ""VolumeID"" FROM ""Block""; "; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } } @@ -175,7 +164,7 @@ namespace Duplicati.UnitTest Exception? caughtException = null; try { - c.Compact(); + await c.CompactAsync(); } catch (Exception ex) { diff --git a/Duplicati/UnitTest/CompactDisruptionTests.cs b/Duplicati/UnitTest/CompactDisruptionTests.cs index 40548cbd5..f60dd46c5 100644 --- a/Duplicati/UnitTest/CompactDisruptionTests.cs +++ b/Duplicati/UnitTest/CompactDisruptionTests.cs @@ -25,6 +25,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -76,7 +77,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption"), Category("Bug")] - public void InterruptedCompact5184() + public async Task InterruptedCompact5184Async() { // Reproduction steps from issue #4129 with smaller sizes var testopts = TestOptions; @@ -93,33 +94,33 @@ namespace Duplicati.UnitTest // in combination with dblock-size const long filesize = 44971; - string target = "file://" + TARGETFOLDER; - string file1 = Path.Combine(DATAFOLDER, "f1"); - string file2 = Path.Combine(DATAFOLDER, "f2"); - string file3 = Path.Combine(DATAFOLDER, "f3"); - string file4 = Path.Combine(DATAFOLDER, "f4"); - string file5 = Path.Combine(DATAFOLDER, "f5"); - string file6 = Path.Combine(DATAFOLDER, "f6"); + var target = "file://" + TARGETFOLDER; + var file1 = Path.Combine(DATAFOLDER, "f1"); + var file2 = Path.Combine(DATAFOLDER, "f2"); + var file3 = Path.Combine(DATAFOLDER, "f3"); + var file4 = Path.Combine(DATAFOLDER, "f4"); + var file5 = Path.Combine(DATAFOLDER, "f5"); + var file6 = Path.Combine(DATAFOLDER, "f6"); // Add files 1,2 TestUtils.WriteTestFile(file1, filesize); TestUtils.WriteTestFile(file2, filesize); using (var c = new Library.Main.Controller(target, testopts, null)) { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 3,4 TestUtils.WriteTestFile(file3, filesize); TestUtils.WriteTestFile(file4, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 5,6 TestUtils.WriteTestFile(file5, filesize); TestUtils.WriteTestFile(file6, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); @@ -127,7 +128,7 @@ namespace Duplicati.UnitTest File.Delete(file1); File.Delete(file3); File.Delete(file5); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); } @@ -149,11 +150,11 @@ namespace Duplicati.UnitTest return false; }; // Expect error from backend - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } }); @@ -163,7 +164,7 @@ namespace Duplicati.UnitTest target = "file://" + TARGETFOLDER; using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -175,14 +176,14 @@ namespace Duplicati.UnitTest // Repair to recreate the local database using (var c = new Library.Main.Controller(target, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); } // Re-do the full verification using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -191,7 +192,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption"), Category("Bug")] - public void InterruptedCompactPlusNormalCompact5184() + public async Task InterruptedCompactPlusNormalCompact5184Async() { // Reproduction steps from issue #4129 with smaller sizes var testopts = TestOptions; @@ -208,33 +209,33 @@ namespace Duplicati.UnitTest // in combination with dblock-size const long filesize = 44971; - string target = "file://" + TARGETFOLDER; - string file1 = Path.Combine(DATAFOLDER, "f1"); - string file2 = Path.Combine(DATAFOLDER, "f2"); - string file3 = Path.Combine(DATAFOLDER, "f3"); - string file4 = Path.Combine(DATAFOLDER, "f4"); - string file5 = Path.Combine(DATAFOLDER, "f5"); - string file6 = Path.Combine(DATAFOLDER, "f6"); + var target = "file://" + TARGETFOLDER; + var file1 = Path.Combine(DATAFOLDER, "f1"); + var file2 = Path.Combine(DATAFOLDER, "f2"); + var file3 = Path.Combine(DATAFOLDER, "f3"); + var file4 = Path.Combine(DATAFOLDER, "f4"); + var file5 = Path.Combine(DATAFOLDER, "f5"); + var file6 = Path.Combine(DATAFOLDER, "f6"); // Add files 1,2 TestUtils.WriteTestFile(file1, filesize); TestUtils.WriteTestFile(file2, filesize); using (var c = new Library.Main.Controller(target, testopts, null)) { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 3,4 TestUtils.WriteTestFile(file3, filesize); TestUtils.WriteTestFile(file4, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 5,6 TestUtils.WriteTestFile(file5, filesize); TestUtils.WriteTestFile(file6, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); @@ -242,7 +243,7 @@ namespace Duplicati.UnitTest File.Delete(file1); File.Delete(file3); File.Delete(file5); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); } @@ -264,11 +265,11 @@ namespace Duplicati.UnitTest return false; }; // Expect error from backend - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } }); @@ -279,7 +280,7 @@ namespace Duplicati.UnitTest target = "file://" + TARGETFOLDER; using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -288,14 +289,14 @@ namespace Duplicati.UnitTest // Make sure we can compact after the interrupted compact using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } // Make sure there are no errors after success compacting using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -307,14 +308,14 @@ namespace Duplicati.UnitTest // Repair to recreate the local database using (var c = new Library.Main.Controller(target, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); } // Re-do the full verification using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -323,7 +324,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption"), Category("Bug")] - public void DoubleInterruptedCompact5184() + public async Task DoubleInterruptedCompact5184Async() { // Reproduction steps from issue #4129 with smaller sizes var testopts = TestOptions; @@ -352,21 +353,21 @@ namespace Duplicati.UnitTest TestUtils.WriteTestFile(file2, filesize); using (var c = new Library.Main.Controller(target, testopts, null)) { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 3,4 TestUtils.WriteTestFile(file3, filesize); TestUtils.WriteTestFile(file4, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 5,6 TestUtils.WriteTestFile(file5, filesize); TestUtils.WriteTestFile(file6, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); @@ -374,7 +375,7 @@ namespace Duplicati.UnitTest File.Delete(file1); File.Delete(file3); File.Delete(file5); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); } @@ -396,21 +397,21 @@ namespace Duplicati.UnitTest return false; }; // Expect error from backend - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } }); // Expect error from backend, again - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } }); @@ -420,7 +421,7 @@ namespace Duplicati.UnitTest testopts["full-block-verification"] = "true"; using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -429,14 +430,14 @@ namespace Duplicati.UnitTest // Make sure we can compact after two interrupted compacts using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } // Make sure there are no errors after success compacting using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -448,14 +449,14 @@ namespace Duplicati.UnitTest // Repair to recreate the local database using (var c = new Library.Main.Controller(target, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); } // Re-do the full verification using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -464,7 +465,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption"), Category("Bug")] - public void RestoreAfterDoubleInterruptedCompact5184() + public async Task RestoreAfterDoubleInterruptedCompact5184Async() { // Reproduction steps from issue #4129 with smaller sizes var testopts = TestOptions; @@ -481,33 +482,33 @@ namespace Duplicati.UnitTest // in combination with dblock-size const long filesize = 44971; - string target = "file://" + TARGETFOLDER; - string file1 = Path.Combine(DATAFOLDER, "f1"); - string file2 = Path.Combine(DATAFOLDER, "f2"); - string file3 = Path.Combine(DATAFOLDER, "f3"); - string file4 = Path.Combine(DATAFOLDER, "f4"); - string file5 = Path.Combine(DATAFOLDER, "f5"); - string file6 = Path.Combine(DATAFOLDER, "f6"); + var target = "file://" + TARGETFOLDER; + var file1 = Path.Combine(DATAFOLDER, "f1"); + var file2 = Path.Combine(DATAFOLDER, "f2"); + var file3 = Path.Combine(DATAFOLDER, "f3"); + var file4 = Path.Combine(DATAFOLDER, "f4"); + var file5 = Path.Combine(DATAFOLDER, "f5"); + var file6 = Path.Combine(DATAFOLDER, "f6"); // Add files 1,2 TestUtils.WriteTestFile(file1, filesize); TestUtils.WriteTestFile(file2, filesize); using (var c = new Library.Main.Controller(target, testopts, null)) { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 3,4 TestUtils.WriteTestFile(file3, filesize); TestUtils.WriteTestFile(file4, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); // Add files 5,6 TestUtils.WriteTestFile(file5, filesize); TestUtils.WriteTestFile(file6, filesize); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); SleepUntilNextSecond(backupResults); @@ -515,7 +516,7 @@ namespace Duplicati.UnitTest File.Delete(file1); File.Delete(file3); File.Delete(file5); - backupResults = c.Backup(new[] { DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); } @@ -537,21 +538,21 @@ namespace Duplicati.UnitTest return false; }; // Expect error from backend - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } }); // Expect error from backend, again - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.Greater(compactResults.DownloadedFileCount, 0, "No compact operation was performed"); } }); @@ -562,7 +563,7 @@ namespace Duplicati.UnitTest testopts["full-block-verification"] = "true"; using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -574,14 +575,14 @@ namespace Duplicati.UnitTest // Repair to recreate the local database using (var c = new Library.Main.Controller(target, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); } // Re-do the full verification using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); @@ -590,7 +591,7 @@ namespace Duplicati.UnitTest // Compact without issues on the repaired database using (var c = new Library.Main.Controller(target, testopts, null)) { - ICompactResults compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); if (compactResults.DownloadedFileCount == 0) Assert.Inconclusive("No compact operation was performed"); } @@ -598,7 +599,7 @@ namespace Duplicati.UnitTest // Make sure there are no errors after success compacting using (var c = new Library.Main.Controller(target, testopts, null)) { - ITestResults testResults = c.Test(long.MaxValue); + var testResults = await c.TestAsync(long.MaxValue); // Expect no verification errors Assert.IsTrue(testResults.Verifications.All(v => !v.Value.Any()), "Test verification failed:\n {0}", VerificationToString(testResults.Verifications)); diff --git a/Duplicati/UnitTest/ControllerTests.cs b/Duplicati/UnitTest/ControllerTests.cs index 4b674c4dd..bb4d0eb2d 100644 --- a/Duplicati/UnitTest/ControllerTests.cs +++ b/Duplicati/UnitTest/ControllerTests.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main; using NUnit.Framework; @@ -32,15 +33,15 @@ namespace Duplicati.UnitTest { [Test] [Category("Controller")] - public void DeleteAllRemoteFiles() + public async Task DeleteAllRemoteFilesAsync() { - string filePath = Path.Combine(this.DATAFOLDER, "file"); + var filePath = Path.Combine(this.DATAFOLDER, "file"); File.WriteAllBytes(filePath, new byte[] { 0, 1, 2 }); - Dictionary firstOptions = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, firstOptions, null)) + var firstOptions = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, firstOptions, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -48,32 +49,32 @@ namespace Duplicati.UnitTest // Keep track of the backend files from the first backup configuration so that we can // check that they remain after we remove the backend files from the second backup // configuration. - string[] firstBackupFiles = Directory.GetFiles(this.TARGETFOLDER); + var firstBackupFiles = Directory.GetFiles(this.TARGETFOLDER); Assert.Greater(firstBackupFiles.Length, 0); Dictionary secondOptions = new Dictionary(this.TestOptions) { ["dbpath"] = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()) }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) { // An exception should be thrown due to unrecognized files in the target folder. // ReSharper disable once AccessToDisposedClosure - Assert.That(() => c.Backup(new[] { this.DATAFOLDER }), Throws.Exception); + Assert.That(async () => await c.BackupAsync(new[] { this.DATAFOLDER }), Throws.Exception); } // We should be able to safely remove backend files from the second backup by referring // to the local database. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) { - IListRemoteResults listResults = c.DeleteAllRemoteFiles(); + var listResults = await c.DeleteAllRemoteFilesAsync(); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); } // After we delete backend files from the second backup configuration, those from the first // configuration should remain (see issues #3845, and #4244). - foreach (string file in firstBackupFiles) + foreach (var file in firstBackupFiles) { Assert.IsTrue(File.Exists(file)); } @@ -82,9 +83,9 @@ namespace Duplicati.UnitTest File.Delete(secondOptions["dbpath"]); secondOptions["prefix"] = new Options(firstOptions).Prefix + "2"; secondOptions["dbpath"] = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -92,24 +93,24 @@ namespace Duplicati.UnitTest // Even without a local database, we should be able to safely remove backend files from // the second backup due to the prefix. File.Delete(secondOptions["dbpath"]); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, secondOptions, null)) { - IListRemoteResults listResults = c.DeleteAllRemoteFiles(); + var listResults = await c.DeleteAllRemoteFilesAsync(); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); } // After we delete backend files from the second backup configuration, those from the first // configuration should remain (see issue #2678). - foreach (string file in firstBackupFiles) + foreach (var file in firstBackupFiles) { Assert.IsTrue(File.Exists(file)); } // The first backup configuration should still run normally. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, firstOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, firstOptions, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } diff --git a/Duplicati/UnitTest/DatabaseToolTests.cs b/Duplicati/UnitTest/DatabaseToolTests.cs index 8055afa83..76f63bc0b 100644 --- a/Duplicati/UnitTest/DatabaseToolTests.cs +++ b/Duplicati/UnitTest/DatabaseToolTests.cs @@ -35,42 +35,42 @@ namespace Duplicati.UnitTest { [Test] [Category("DatabaseTool")] - public async Task TestLocalDbMethods() + public async Task TestLocalDbMethodsAsync() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); using var cmd = db.CreateCommand(); cmd.CommandText = LocalSchemaV12; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); - Assert.AreEqual(0, await Program.Main(["upgrade", dbfile, "--no-backups"])); - Assert.AreEqual(0, await Program.Main(["downgrade", dbfile, "--server-version=6", "--local-version=12", "--no-backups"])); - Assert.AreEqual(0, await Program.Main(["upgrade", dbfile, "--no-backups"])); + Assert.AreEqual(0, await Program.MainAsync(["upgrade", dbfile, "--no-backups"])); + Assert.AreEqual(0, await Program.MainAsync(["downgrade", dbfile, "--server-version=6", "--local-version=12", "--no-backups"])); + Assert.AreEqual(0, await Program.MainAsync(["upgrade", dbfile, "--no-backups"])); - Assert.AreEqual(0, await Program.Main(["list", dbfile])); - Assert.AreEqual(0, await Program.Main(["list", dbfile, "RemoteVolume"])); - Assert.AreEqual(0, await Program.Main(["list", dbfile, "RemoteVolume", "--output-json"])); - Assert.AreEqual(0, await Program.Main(["execute", dbfile, "SELECT * FROM RemoteVolume", "--output-json"])); + Assert.AreEqual(0, await Program.MainAsync(["list", dbfile])); + Assert.AreEqual(0, await Program.MainAsync(["list", dbfile, "RemoteVolume"])); + Assert.AreEqual(0, await Program.MainAsync(["list", dbfile, "RemoteVolume", "--output-json"])); + Assert.AreEqual(0, await Program.MainAsync(["execute", dbfile, "SELECT * FROM RemoteVolume", "--output-json"])); } [Test] [Category("DatabaseTool")] - public async Task TestServerDbMethods() + public async Task TestServerDbMethodsAsync() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); using var cmd = db.CreateCommand(); cmd.CommandText = ServerSchemaV6; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); - Assert.AreEqual(0, await Program.Main(["upgrade", dbfile, "--no-backups"])); - Assert.AreEqual(0, await Program.Main(["downgrade", dbfile, "--server-version=6", "--local-version=12", "--no-backups"])); - Assert.AreEqual(0, await Program.Main(["upgrade", dbfile, "--no-backups"])); + Assert.AreEqual(0, await Program.MainAsync(["upgrade", dbfile, "--no-backups"])); + Assert.AreEqual(0, await Program.MainAsync(["downgrade", dbfile, "--server-version=6", "--local-version=12", "--no-backups"])); + Assert.AreEqual(0, await Program.MainAsync(["upgrade", dbfile, "--no-backups"])); - Assert.AreEqual(0, await Program.Main(["list", dbfile])); - Assert.AreEqual(0, await Program.Main(["list", dbfile, "Source"])); - Assert.AreEqual(0, await Program.Main(["list", dbfile, "Source", "--output-json"])); - Assert.AreEqual(0, await Program.Main(["execute", dbfile, "SELECT * FROM Source", "--output-json"])); + Assert.AreEqual(0, await Program.MainAsync(["list", dbfile])); + Assert.AreEqual(0, await Program.MainAsync(["list", dbfile, "Source"])); + Assert.AreEqual(0, await Program.MainAsync(["list", dbfile, "Source", "--output-json"])); + Assert.AreEqual(0, await Program.MainAsync(["execute", dbfile, "SELECT * FROM Source", "--output-json"])); } /// @@ -342,7 +342,7 @@ INSERT INTO ""Version"" (""Version"") VALUES (12); [Test] [Category("DatabaseTool")] - public async Task TestVerifyCommand() + public async Task TestVerifyCommandAsync() { using var tempFolder = new Library.Utility.TempFolder(); string tempDir = tempFolder; @@ -353,34 +353,34 @@ INSERT INTO ""Version"" (""Version"") VALUES (12); var orphanDb = Path.Combine(tempDir, "ABCDEFGHIJ.sqlite"); // Random name = Orphaned // Create server database - using (var db = SQLiteLoader.LoadConnection(serverDb)) + using (var db = await SQLiteLoader.LoadConnectionAsync(serverDb)) using (var cmd = db.CreateCommand()) { cmd.CommandText = ServerSchemaV6; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a backup referencing localDb1 cmd.CommandText = $@" INSERT INTO ""Backup"" (""Name"", ""Tags"", ""TargetURL"", ""DBPath"") VALUES ('Test Backup', '', 'file:///test', '{localDb1.Replace("'", "''")}'); "; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Create local database - using (var db = SQLiteLoader.LoadConnection(localDb1)) + using (var db = await SQLiteLoader.LoadConnectionAsync(localDb1)) using (var cmd = db.CreateCommand()) { cmd.CommandText = LocalSchemaV12; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Create orphan database (exists but not referenced) - using (var db = SQLiteLoader.LoadConnection(orphanDb)) + using (var db = await SQLiteLoader.LoadConnectionAsync(orphanDb)) using (var cmd = db.CreateCommand()) { cmd.CommandText = LocalSchemaV12; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Copy server database to expected location @@ -423,7 +423,7 @@ INSERT INTO ""Version"" (""Version"") VALUES (12); try { - Assert.AreEqual(0, await Program.Main(["verify", "--datafolder", tempDir, "--output-json"])); + Assert.AreEqual(0, await Program.MainAsync(["verify", "--datafolder", tempDir, "--output-json"])); } finally { @@ -444,7 +444,7 @@ INSERT INTO ""Version"" (""Version"") VALUES (12); [Category("DatabaseTool")] [TestCase(true)] // Dry run - no files should be deleted [TestCase(false)] // Force - only orphaned files should be deleted - public async Task TestCleanupCommand(bool dryRun) + public async Task TestCleanupCommandAsync(bool dryRun) { using var tempFolder = new Library.Utility.TempFolder(); var tempDir = (string)tempFolder; @@ -460,33 +460,33 @@ INSERT INTO ""Version"" (""Version"") VALUES (12); var serverDb = Path.Combine(tempDir, "Duplicati-server.sqlite"); // Create server database with a backup referencing foundDb - using (var db = SQLiteLoader.LoadConnection(serverDb)) + using (var db = await SQLiteLoader.LoadConnectionAsync(serverDb)) using (var cmd = db.CreateCommand()) { cmd.CommandText = ServerSchemaV6; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); cmd.CommandText = $@" INSERT INTO ""Backup"" (""Name"", ""Tags"", ""TargetURL"", ""DBPath"") VALUES ('Test Backup', '', 'file:///test', '{foundDb.Replace("'", "''")}'); "; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Create "Found" database (referenced in server DB and exists) - using (var db = SQLiteLoader.LoadConnection(foundDb)) + using (var db = await SQLiteLoader.LoadConnectionAsync(foundDb)) using (var cmd = db.CreateCommand()) { cmd.CommandText = LocalSchemaV12; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Create "Orphaned" database (exists but not referenced) - using (var db = SQLiteLoader.LoadConnection(orphanDb)) + using (var db = await SQLiteLoader.LoadConnectionAsync(orphanDb)) using (var cmd = db.CreateCommand()) { cmd.CommandText = LocalSchemaV12; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Create dbconfig.json with missingDb reference (file won't exist = Missing) @@ -528,7 +528,7 @@ INSERT INTO ""Version"" (""Version"") VALUES (12); ? ["cleanup", "--datafolder", tempDir, "--dry-run"] : ["cleanup", "--datafolder", tempDir, "--force"]; - Assert.AreEqual(0, await Program.Main(args)); + Assert.AreEqual(0, await Program.MainAsync(args)); if (dryRun) { diff --git a/Duplicati/UnitTest/DirectListHandlerTests.cs b/Duplicati/UnitTest/DirectListHandlerTests.cs index 11754e4d0..ea7341807 100644 --- a/Duplicati/UnitTest/DirectListHandlerTests.cs +++ b/Duplicati/UnitTest/DirectListHandlerTests.cs @@ -15,7 +15,7 @@ namespace Duplicati.UnitTest public class DirectListHandlerTests : BasicSetupHelper { [Test] - public void ListFilesets() + public async Task ListFilesetsAsync() { var options = new Dictionary(this.TestOptions) { @@ -26,15 +26,15 @@ namespace Duplicati.UnitTest { using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - var sets = c.ListFilesets(); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + var sets = await c.ListFilesetsAsync(); Assert.That(sets.Filesets.Count(), Is.EqualTo(i + 1)); } } } [Test] - public void ListFolderContents() + public async Task ListFolderContentsAsync() { var options = new Dictionary(this.TestOptions) { @@ -92,10 +92,10 @@ namespace Duplicati.UnitTest foreach (var round in rounds) { createStructure(round); - TestUtils.AssertResults(c.Backup(rootItems(round).Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); + TestUtils.AssertResults(await c.BackupAsync(rootItems(round).Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); } - var sets = c.ListFilesets(); + var sets = await c.ListFilesetsAsync(); Assert.That(sets.Filesets.Count(), Is.EqualTo(rounds.Length)); } @@ -104,7 +104,7 @@ namespace Duplicati.UnitTest { using (var c = new Controller("file://" + this.TARGETFOLDER, options.Expand(new { version = version }), null)) { - var files = c.ListFolder([""], 0, 0, false); + var files = await c.ListFolderAsync([""], 0, 0, false); var roots = rootItems(round).Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray(); Assert.That(files.Entries.Items.Count(), Is.EqualTo(roots.Length)); @@ -116,7 +116,7 @@ namespace Duplicati.UnitTest while (work.Count > 0) { var path = work.Dequeue(); - var files2 = c.ListFolder(new[] { Path.Combine(this.DATAFOLDER, path.Replace("/", Path.DirectorySeparatorChar.ToString())) }, 0, 0, false); + var files2 = await c.ListFolderAsync(new[] { Path.Combine(this.DATAFOLDER, path.Replace("/", Path.DirectorySeparatorChar.ToString())) }, 0, 0, false); var matches = round.Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))) .Where(x => x.StartsWith(path) && x.Length > path.Length && !x.Substring(path.Length, x.Length - path.Length - 1).Contains(Path.DirectorySeparatorChar)) .ToArray(); @@ -136,7 +136,7 @@ namespace Duplicati.UnitTest } [Test] - public void ListFileVersions_LifecycleTest() + public async Task ListFileVersions_LifecycleTestAsync() { var options = new Dictionary(this.TestOptions) { @@ -174,16 +174,16 @@ namespace Duplicati.UnitTest { // Round 0 - initial createStructure(rounds[0]); - TestUtils.AssertResults(c.Backup(rounds[0].Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); + TestUtils.AssertResults(await c.BackupAsync(rounds[0].Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); // Round 1 - modify and create new file modifyFile(modify, "modified-content"); createStructure(new[] { create }); File.Delete(Path.Combine(this.DATAFOLDER, delete.Replace("/", Path.DirectorySeparatorChar.ToString()))); // Delete file - TestUtils.AssertResults(c.Backup(rounds[1].Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); + TestUtils.AssertResults(await c.BackupAsync(rounds[1].Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); // Round 2 - no changes - TestUtils.AssertResults(c.Backup(rounds[2].Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); + TestUtils.AssertResults(await c.BackupAsync(rounds[2].Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); } var pathsToCheck = new[] @@ -196,7 +196,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var allVersions = c.ListFileVersions(pathsToCheck, 0, 0); + var allVersions = await c.ListFileVersionsAsync(pathsToCheck, 0, 0); var grouped = allVersions.FileVersions.Items.GroupBy(x => x.Path); foreach (var group in grouped) @@ -219,7 +219,7 @@ namespace Duplicati.UnitTest } [Test] - public void SearchFilesTest() + public async Task SearchFilesTestAsync() { var options = new Dictionary(this.TestOptions) { @@ -256,51 +256,51 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { createStructure(initial); - TestUtils.AssertResults(c.Backup(rootItems(initial).Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); + TestUtils.AssertResults(await c.BackupAsync(rootItems(initial).Select(x => Path.Combine(this.DATAFOLDER, x.Replace("/", Path.DirectorySeparatorChar.ToString()))).ToArray())); // Simple Search for 'file' - var search = c.SearchEntries(null, ParseFilters("+file"), 0, 0, false); + var search = await c.SearchEntriesAsync(null, ParseFilters("+file"), 0, 0, false); Assert.That(search.FileVersions.Items.Count(), Is.EqualTo(3)); // file1.txt, file2.txt, file3.txt // Simple Search for 'notes' - var searchNotes = c.SearchEntries(null, ParseFilters("+notes"), 0, 0, false); + var searchNotes = await c.SearchEntriesAsync(null, ParseFilters("+notes"), 0, 0, false); Assert.That(searchNotes.FileVersions.Items.Count(), Is.EqualTo(1)); Assert.That(searchNotes.FileVersions.Items.First().Path.EndsWith("notes.txt")); // Simple Search for NOT 'notes' - var searchNotNotes = c.SearchEntries(null, ParseFilters("-notes"), 0, 0, false); + var searchNotNotes = await c.SearchEntriesAsync(null, ParseFilters("-notes"), 0, 0, false); Assert.That(searchNotNotes.FileVersions.Items.Count(), Is.EqualTo(5)); Assert.That(searchNotNotes.FileVersions.Items.All(x => !x.Path.Contains("notes")), Is.True); // Simple Search for 'folder1' folder contents - var searchFolder = c.SearchEntries(null, ParseFilters("+folder1"), 0, 0, false); + var searchFolder = await c.SearchEntriesAsync(null, ParseFilters("+folder1"), 0, 0, false); Assert.That(searchFolder.FileVersions.Items.All(x => x.Path.Contains("folder1")), Is.True); // Simple Search with exact file name - var searchExact = c.SearchEntries(null, ParseFilters("+file1.txt"), 0, 0, false); + var searchExact = await c.SearchEntriesAsync(null, ParseFilters("+file1.txt"), 0, 0, false); Assert.That(searchExact.FileVersions.Items.Count(), Is.EqualTo(1)); Assert.That(searchExact.FileVersions.Items.First().Path.EndsWith("file1.txt")); // Mixed include and exclude - var searchMixed = c.SearchEntries(null, ParseFilters("+file;-file2"), 0, 0, false); + var searchMixed = await c.SearchEntriesAsync(null, ParseFilters("+file;-file2"), 0, 0, false); // Include has precedence over exclude, but we include non-matches as well Assert.That(searchMixed.FileVersions.Items.Count(), Is.EqualTo(6)); Assert.That(searchMixed.FileVersions.Items.Any(x => x.Path.Contains("file2")), Is.True); // Mixed include and exclude - var searchMixed2 = c.SearchEntries(null, ParseFilters("+file2;-file"), 0, 0, false); + var searchMixed2 = await c.SearchEntriesAsync(null, ParseFilters("+file2;-file"), 0, 0, false); // Include has precedence over exclude, but we include non-matches as well Assert.That(searchMixed2.FileVersions.Items.Count(), Is.EqualTo(4)); Assert.That(searchMixed2.FileVersions.Items.Any(x => x.Path.Contains("file2")), Is.True); // Mixed include and exclude, wildcards - var searchMixed3 = c.SearchEntries(null, ParseFilters("+file2;-*"), 0, 0, false); + var searchMixed3 = await c.SearchEntriesAsync(null, ParseFilters("+file2;-*"), 0, 0, false); // Include has precedence over exclude, but we include non-matches as well Assert.That(searchMixed3.FileVersions.Items.Count(), Is.EqualTo(1)); Assert.That(searchMixed3.FileVersions.Items.All(x => x.Path.Contains("file2")), Is.True); // NEW: Search inside specific folder prefix: folder1 - var searchInFolder1 = c.SearchEntries( + var searchInFolder1 = await c.SearchEntriesAsync( new[] { Path.Combine(this.DATAFOLDER, "folder1") }, ParseFilters("+file"), 0, 0, false); @@ -308,7 +308,7 @@ namespace Duplicati.UnitTest Assert.That(searchInFolder1.FileVersions.Items.All(x => x.Path.Contains("folder1")), Is.True); // NEW: Search inside multiple folder prefixes: folder1 and folder2 - var searchInFolders = c.SearchEntries( + var searchInFolders = await c.SearchEntriesAsync( new[] { Path.Combine(this.DATAFOLDER, "folder1"), @@ -323,7 +323,7 @@ namespace Duplicati.UnitTest } [Test] - public async Task GetMinimalUniquePrefixEntries_ShouldReturnCorrectLinuxPrefixes() + public async Task GetMinimalUniquePrefixEntries_ShouldReturnCorrectLinuxPrefixesAsync() { using var tempFile = new TempFile(); using var db = await LocalListDatabase.CreateAsync(tempFile, null, CancellationToken.None) @@ -337,7 +337,7 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .GetMinimalUniquePrefixEntriesAsync(1, CancellationToken.None) .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -351,7 +351,7 @@ namespace Duplicati.UnitTest } [Test] - public async Task GetMinimalUniquePrefixEntries_ShouldReturnCorrectWindowsDrivePrefixes() + public async Task GetMinimalUniquePrefixEntries_ShouldReturnCorrectWindowsDrivePrefixes_Async() { using var tempFile = new TempFile(); using var db = await LocalListDatabase.CreateAsync(tempFile, null, CancellationToken.None) @@ -364,7 +364,7 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .GetMinimalUniquePrefixEntriesAsync(1, CancellationToken.None) .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -377,7 +377,7 @@ namespace Duplicati.UnitTest } [Test] - public async Task GetMinimalUniquePrefixEntries_ShouldReturnCorrectWindowsUncPrefixes() + public async Task GetMinimalUniquePrefixEntries_ShouldReturnCorrectWindowsUncPrefixes_Async() { using var tempFile = new TempFile(); using var db = await LocalListDatabase.CreateAsync(tempFile, null, CancellationToken.None) @@ -389,7 +389,7 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .GetMinimalUniquePrefixEntriesAsync(1, CancellationToken.None) .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -401,7 +401,7 @@ namespace Duplicati.UnitTest } [Test] - public async Task GetMinimalUniquePrefixEntries_ShouldHandleMixedWindowsDriveAndUncPaths() + public async Task GetMinimalUniquePrefixEntries_ShouldHandleMixedWindowsDriveAndUncPathsAsync() { using var tempFile = new TempFile(); using var db = await LocalListDatabase.CreateAsync(tempFile, null, CancellationToken.None) @@ -417,7 +417,7 @@ namespace Duplicati.UnitTest ]); var result = await db - .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .GetMinimalUniquePrefixEntriesAsync(1, CancellationToken.None) .Select(e => e.Path) .ToListAsync() .ConfigureAwait(false); @@ -433,7 +433,7 @@ namespace Duplicati.UnitTest } [Test] - public async Task GetMinimalUniquePrefixEntries_ShouldReturnExpectedMinimalRoots() + public async Task GetMinimalUniquePrefixEntries_ShouldReturnExpectedMinimalRootsAsync() { // Arrange: Prepare prefixes (minimal unique) and contents var testPrefixes = new[] @@ -457,7 +457,7 @@ namespace Duplicati.UnitTest SeedTestData(db, testPrefixes); var resultItems = await db - .GetMinimalUniquePrefixEntries(1, CancellationToken.None) + .GetMinimalUniquePrefixEntriesAsync(1, CancellationToken.None) .ToListAsync() .ConfigureAwait(false); @@ -554,7 +554,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Database")] - public async Task ListFolder_WithLargePrefixIds_UsesTemporaryTable() + public async Task ListFolder_WithLargePrefixIds_UsesTemporaryTableAsync() { using var tempFile = new TempFile(); await using var db = await LocalListDatabase.CreateAsync(tempFile, null, CancellationToken.None) @@ -563,11 +563,11 @@ namespace Duplicati.UnitTest // Insert fileset entry using (var cmd = db.Connection.CreateCommand()) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT OR IGNORE INTO Fileset (ID, OperationID, VolumeID, IsFullBackup, Timestamp) VALUES (@filesetId, 1, 1, 1, 0);") .SetParameterValue("@filesetId", 1L) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Create 150 prefix IDs (exceeds CHUNK_SIZE of 128) to trigger temporary table path @@ -576,13 +576,13 @@ namespace Duplicati.UnitTest { var prefix = $"/test/prefix/{i}/"; using var cmd = db.Connection.CreateCommand(); - + // Insert prefix - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT OR IGNORE INTO PathPrefix (Prefix) VALUES (@prefix);") .SetParameterValue("@prefix", prefix) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Get prefix ID var prefixId = cmd.SetCommandAndParameters("SELECT ID FROM PathPrefix WHERE Prefix = @prefix") @@ -592,26 +592,26 @@ namespace Duplicati.UnitTest prefixIds.Add(prefixId); // Insert FileLookup - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FileLookup (ID, PrefixID, Path, BlocksetID, MetadataID) VALUES (@fileId, @prefixId, @path, 1, 1);") .SetParameterValue("@fileId", i + 1) .SetParameterValue("@prefixId", prefixId) .SetParameterValue("@path", $"file{i}.txt") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert FilesetEntry - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FilesetEntry (FilesetID, FileID, Lastmodified) VALUES (@filesetId, @fileId, 0);") .SetParameterValue("@filesetId", 1L) .SetParameterValue("@fileId", i + 1) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Act: Call ListFolder with 150 prefix IDs // This should trigger the temporary table code path - var result = await db.ListFolder(prefixIds, 1, 0, 1000, CancellationToken.None) + var result = await db.ListFolderAsync(prefixIds, 1, 0, 1000, CancellationToken.None) .ConfigureAwait(false); // Assert: Should return all 150 files @@ -619,13 +619,13 @@ namespace Duplicati.UnitTest } /// - /// Tests that works correctly + /// Tests that works correctly /// with a large number of fileset IDs that triggers the temporary table code path /// (when count > CHUNK_SIZE = 128). /// [Test] [Category("Database")] - public async Task ListFileVersions_WithLargeFilesetIds_UsesTemporaryTable() + public async Task ListFileVersions_WithLargeFilesetIds_UsesTemporaryTableAsync() { // Arrange using var tempFile = new TempFile(); @@ -635,10 +635,10 @@ namespace Duplicati.UnitTest // Insert operation entry using (var cmd = db.Connection.CreateCommand()) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT OR IGNORE INTO Operation (ID, Description, Timestamp) VALUES (1, 'TestOperation', 0);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Create 150 filesets (exceeds CHUNK_SIZE of 128) to trigger temporary table path @@ -648,7 +648,7 @@ namespace Duplicati.UnitTest using var cmd = db.Connection.CreateCommand(); // Insert RemoteVolume for the fileset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO RemoteVolume (ID, OperationID, Name, Type, State, Size, VerificationCount, DeleteGraceTime, ArchiveTime, LockExpirationTime) VALUES (@id, @operationId, @name, @type, @state, @size, @verificationCount, @deleteGraceTime, @archiveTime, @lockExpirationTime);") .SetParameterValue("@id", i + 1) @@ -661,10 +661,10 @@ namespace Duplicati.UnitTest .SetParameterValue("@deleteGraceTime", 0) .SetParameterValue("@archiveTime", 0) .SetParameterValue("@lockExpirationTime", 0) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert Fileset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Fileset (ID, OperationID, VolumeID, IsFullBackup, Timestamp) VALUES (@id, @operationId, @volumeId, @isFullBackup, @timestamp);") .SetParameterValue("@id", i + 1) @@ -672,7 +672,7 @@ namespace Duplicati.UnitTest .SetParameterValue("@volumeId", i + 1) .SetParameterValue("@isFullBackup", 1) .SetParameterValue("@timestamp", i) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); filesetIds.Add(i + 1); } @@ -681,45 +681,45 @@ namespace Duplicati.UnitTest using (var cmd = db.Connection.CreateCommand()) { // Insert Blockset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Blockset (ID, Length, FullHash) VALUES (1, 1024, 'fullhash');") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert Metadataset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Metadataset (ID, BlocksetID) VALUES (1, 1);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert PathPrefix - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO PathPrefix (ID, Prefix) VALUES (1, '/test/');") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert FileLookup - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FileLookup (ID, PrefixID, Path, BlocksetID, MetadataID) VALUES (1, 1, 'file.txt', 1, 1);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert FilesetEntry for each fileset for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FilesetEntry (FilesetID, FileID, Lastmodified) VALUES (@filesetId, @fileId, @lastModified);") .SetParameterValue("@filesetId", i + 1) .SetParameterValue("@fileId", 1) .SetParameterValue("@lastModified", 0) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } } // Act: Call ListFileVersions with 150 fileset IDs // This should trigger the temporary table code path - var result = await db.ListFileVersions( + var result = await db.ListFileVersionsAsync( new[] { "file.txt" }, filesetIds.ToArray(), 0, @@ -732,13 +732,13 @@ namespace Duplicati.UnitTest } /// - /// Tests that works correctly + /// Tests that works correctly /// with a large number of fileset IDs that triggers the temporary table code path /// (when count > CHUNK_SIZE = 128). /// [Test] [Category("Database")] - public async Task SearchEntries_WithLargeFilesetIds_UsesTemporaryTable() + public async Task SearchEntries_WithLargeFilesetIds_UsesTemporaryTableAsync() { // Arrange using var tempFile = new TempFile(); @@ -748,10 +748,10 @@ namespace Duplicati.UnitTest // Insert operation entry using (var cmd = db.Connection.CreateCommand()) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT OR IGNORE INTO Operation (ID, Description, Timestamp) VALUES (1, 'TestOperation', 0);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Create 150 filesets (exceeds CHUNK_SIZE of 128) to trigger temporary table path @@ -761,7 +761,7 @@ namespace Duplicati.UnitTest using var cmd = db.Connection.CreateCommand(); // Insert RemoteVolume for the fileset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO RemoteVolume (ID, OperationID, Name, Type, State, Size, VerificationCount, DeleteGraceTime, ArchiveTime, LockExpirationTime) VALUES (@id, @operationId, @name, @type, @state, @size, @verificationCount, @deleteGraceTime, @archiveTime, @lockExpirationTime);") .SetParameterValue("@id", i + 1) @@ -774,10 +774,10 @@ namespace Duplicati.UnitTest .SetParameterValue("@deleteGraceTime", 0) .SetParameterValue("@archiveTime", 0) .SetParameterValue("@lockExpirationTime", 0) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert Fileset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Fileset (ID, OperationID, VolumeID, IsFullBackup, Timestamp) VALUES (@id, @operationId, @volumeId, @isFullBackup, @timestamp);") .SetParameterValue("@id", i + 1) @@ -785,7 +785,7 @@ namespace Duplicati.UnitTest .SetParameterValue("@volumeId", i + 1) .SetParameterValue("@isFullBackup", 1) .SetParameterValue("@timestamp", i) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); filesetIds.Add(i + 1); } @@ -794,27 +794,27 @@ namespace Duplicati.UnitTest using (var cmd = db.Connection.CreateCommand()) { // Insert Blockset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Blockset (ID, Length, FullHash) VALUES (1, 1024, 'fullhash');") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert Metadataset - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Metadataset (ID, BlocksetID) VALUES (1, 1);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert PathPrefix - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO PathPrefix (ID, Prefix) VALUES (1, '/test/');") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert FileLookup entries for (int i = 0; i < 10; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FileLookup (ID, PrefixID, Path, BlocksetID, MetadataID) VALUES (@id, @prefixId, @path, @blocksetId, @metadataId);") .SetParameterValue("@id", i + 1) @@ -822,7 +822,7 @@ namespace Duplicati.UnitTest .SetParameterValue("@path", $"file{i}.txt") .SetParameterValue("@blocksetId", 1) .SetParameterValue("@metadataId", 1) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert FilesetEntry for each fileset and file @@ -830,13 +830,13 @@ namespace Duplicati.UnitTest { for (int j = 0; j < 10; j++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FilesetEntry (FilesetID, FileID, Lastmodified) VALUES (@filesetId, @fileId, @lastModified);") .SetParameterValue("@filesetId", i + 1) .SetParameterValue("@fileId", j + 1) .SetParameterValue("@lastModified", 0) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } } } @@ -844,7 +844,7 @@ namespace Duplicati.UnitTest // Act: Call SearchEntries with 150 fileset IDs // This should trigger the temporary table code path // Use a simple filter that matches all files (empty filter matches everything) - var result = await db.SearchEntries( + var result = await db.SearchEntriesAsync( null, new FilterExpression("*", true), filesetIds.ToArray(), diff --git a/Duplicati/UnitTest/DiskImage/DiskImageTests.cs b/Duplicati/UnitTest/DiskImage/DiskImageTests.cs index f235bc4ac..b083859dd 100644 --- a/Duplicati/UnitTest/DiskImage/DiskImageTests.cs +++ b/Duplicati/UnitTest/DiskImage/DiskImageTests.cs @@ -122,75 +122,75 @@ public class DiskImageTests : BasicSetupHelper #region GPT Single Partition [Test, Category("DiskImage")] - public Task Test_GPT_FAT32() => - FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.FAT32, 0)]); + public Task Test_GPT_FAT32_Async() => + FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.FAT32, 0)]); [Test, Category("DiskImage")] - public Task Test_GPT_NTFS() + public Task Test_GPT_NTFS_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("Test_GPT_NTFS is only supported on Windows."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.NTFS, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.NTFS, 0)]); } [Test, Category("DiskImage")] - public Task Test_GPT_APFS() + public Task Test_GPT_APFS_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS is only supported on macOS."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.APFS, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.APFS, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_HFSPlus() + public Task Test_GPT_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.HFSPlus, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.HFSPlus, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_ExFAT() => - FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.ExFAT, 0)]); + public Task Test_GPT_ExFAT_Async() => + FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.ExFAT, 0)]); [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Ext2() + public Task Test_GPT_Ext2_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext2 is only supported on Linux."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.Ext2, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.Ext2, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Ext3() + public Task Test_GPT_Ext3_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext3 is only supported on Linux."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.Ext3, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.Ext3, 0)]); } [Test, Category("DiskImage")] - public Task Test_GPT_Ext4() + public Task Test_GPT_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext4 is only supported on Linux."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.Ext4, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.GPT, [(FileSystemType.Ext4, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_XFS() + public Task Test_GPT_XFS_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("XFS is only supported on Linux."); - return FullRoundTrip((int)(310 * MiB), PartitionTableType.GPT, [(FileSystemType.XFS, 0)]); + return FullRoundTripAsync((int)(310 * MiB), PartitionTableType.GPT, [(FileSystemType.XFS, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Btrfs() + public Task Test_GPT_Btrfs_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Btrfs is only supported on Linux."); - return FullRoundTrip((int)(110 * MiB), PartitionTableType.GPT, [(FileSystemType.Btrfs, 0)]); + return FullRoundTripAsync((int)(110 * MiB), PartitionTableType.GPT, [(FileSystemType.Btrfs, 0)]); } #endregion @@ -200,67 +200,67 @@ public class DiskImageTests : BasicSetupHelper // APFS is only supported on GPT partition tables. [Test, Category("DiskImage")] - public Task Test_MBR_FAT32() => - FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.FAT32, 0)]); + public Task Test_MBR_FAT32_Async() => + FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.FAT32, 0)]); [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_HFSPlus() + public Task Test_MBR_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.HFSPlus, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.HFSPlus, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_ExFAT() => - FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.ExFAT, 0)]); + public Task Test_MBR_ExFAT_Async() => + FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.ExFAT, 0)]); [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_NTFS() + public Task Test_MBR_NTFS_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("Test_MBR_NTFS is only supported on Windows."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.NTFS, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.NTFS, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Ext2() + public Task Test_MBR_Ext2_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext2 is only supported on Linux."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.Ext2, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.Ext2, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Ext3() + public Task Test_MBR_Ext3_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext3 is only supported on Linux."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.Ext3, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.Ext3, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Ext4() + public Task Test_MBR_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext4 is only supported on Linux."); - return FullRoundTrip((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.Ext4, 0)]); + return FullRoundTripAsync((int)(50 * MiB), PartitionTableType.MBR, [(FileSystemType.Ext4, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_XFS() + public Task Test_MBR_XFS_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("XFS is only supported on Linux."); - return FullRoundTrip((int)(310 * MiB), PartitionTableType.MBR, [(FileSystemType.XFS, 0)]); + return FullRoundTripAsync((int)(310 * MiB), PartitionTableType.MBR, [(FileSystemType.XFS, 0)]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Btrfs() + public Task Test_MBR_Btrfs_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Btrfs is only supported on Linux."); - return FullRoundTrip((int)(110 * MiB), PartitionTableType.MBR, [(FileSystemType.Btrfs, 0)]); + return FullRoundTripAsync((int)(110 * MiB), PartitionTableType.MBR, [(FileSystemType.Btrfs, 0)]); } #endregion @@ -268,154 +268,154 @@ public class DiskImageTests : BasicSetupHelper #region Unknown Partition Table [Test, Category("DiskImage")] - public Task Test_Unknown_NoPartitions() => - FullRoundTrip((int)(50 * MiB), PartitionTableType.Unknown, []); + public Task Test_Unknown_NoPartitions_Async() => + FullRoundTripAsync((int)(50 * MiB), PartitionTableType.Unknown, []); #endregion #region GPT Two Partitions [Test, Category("DiskImage")] - public Task Test_GPT_FAT32_FAT32() => - FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + public Task Test_GPT_FAT32_FAT32_Async() => + FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 0) ]); [Test, Category("DiskImage")] - public Task Test_GPT_APFS_APFS() + public Task Test_GPT_APFS_APFS_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS is only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.APFS, 50 * MiB), (FileSystemType.APFS, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_HFSPlus_HFSPlus() + public Task Test_GPT_HFSPlus_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.HFSPlus, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_APFS_HFSPlus() + public Task Test_GPT_APFS_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS and HFSPlus are only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.APFS, 50 * MiB), (FileSystemType.HFSPlus, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_FAT32_APFS() + public Task Test_GPT_FAT32_APFS_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS is only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.APFS, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_ExFAT_HFSPlus() + public Task Test_GPT_ExFAT_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.ExFAT, 50 * MiB), (FileSystemType.HFSPlus, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_FAT32_ExFAT() => - FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + public Task Test_GPT_FAT32_ExFAT_Async() => + FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.ExFAT, 0) ]); [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_NTFS_FAT32() + public Task Test_GPT_NTFS_FAT32_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("This test is only supported on Windows."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.NTFS, 50 * MiB), (FileSystemType.FAT32, 0) ]); } [Test, Category("DiskImage")] - public Task Test_GPT_NTFS_NTFS() + public Task Test_GPT_NTFS_NTFS_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("This test is only supported on Windows."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.NTFS, 50 * MiB), (FileSystemType.NTFS, 0) ]); } [Test, Category("DiskImage")] - public Task Test_GPT_Ext4_Ext4() + public Task Test_GPT_Ext4_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext4 is only supported on Linux."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Ext4_FAT32() + public Task Test_GPT_Ext4_FAT32_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.GPT, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.FAT32, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Ext4_XFS() + public Task Test_GPT_Ext4_XFS_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(360 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(360 * MiB), PartitionTableType.GPT, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.XFS, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_XFS_FAT32() + public Task Test_GPT_XFS_FAT32_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(360 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(360 * MiB), PartitionTableType.GPT, [ (FileSystemType.XFS, 310 * MiB), (FileSystemType.FAT32, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Btrfs_FAT32() + public Task Test_GPT_Btrfs_FAT32_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(160 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(160 * MiB), PartitionTableType.GPT, [ (FileSystemType.Btrfs, 110 * MiB), (FileSystemType.FAT32, 0) ]); @@ -426,8 +426,8 @@ public class DiskImageTests : BasicSetupHelper #region MBR Two Partitions [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_FAT32() => - FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + public Task Test_MBR_FAT32_FAT32_Async() => + FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 0) ]); @@ -435,11 +435,11 @@ public class DiskImageTests : BasicSetupHelper // APFS is only supported on GPT partition tables. [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_HFSPlus_HFSPlus() + public Task Test_MBR_HFSPlus_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.HFSPlus, 0) ]); @@ -447,86 +447,86 @@ public class DiskImageTests : BasicSetupHelper [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_ExFAT() + public Task Test_MBR_FAT32_ExFAT_Async() { - return FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.ExFAT, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_HFSPlus() + public Task Test_MBR_FAT32_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.HFSPlus, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_NTFS_FAT32() + public Task Test_MBR_NTFS_FAT32_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("This test is only supported on Windows."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.NTFS, 50 * MiB), (FileSystemType.FAT32, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Ext4_Ext4() + public Task Test_MBR_Ext4_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext4 is only supported on Linux."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_Ext4() + public Task Test_MBR_FAT32_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(100 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(100 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.Ext4, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_XFS_Ext4() + public Task Test_MBR_XFS_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(360 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(360 * MiB), PartitionTableType.MBR, [ (FileSystemType.XFS, 310 * MiB), (FileSystemType.Ext4, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Btrfs_Ext4() + public Task Test_MBR_Btrfs_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(160 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(160 * MiB), PartitionTableType.MBR, [ (FileSystemType.Btrfs, 110 * MiB), (FileSystemType.Ext4, 0) ]); } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_XFS_Btrfs() + public Task Test_MBR_XFS_Btrfs_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(420 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(420 * MiB), PartitionTableType.MBR, [ (FileSystemType.XFS, 310 * MiB), (FileSystemType.Btrfs, 0) ]); @@ -537,9 +537,9 @@ public class DiskImageTests : BasicSetupHelper #region GPT Three Partitions [Test, Category("DiskImage")] - public Task Test_GPT_FAT32_FAT32_FAT32() + public Task Test_GPT_FAT32_FAT32_FAT32_Async() { - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 0) @@ -547,11 +547,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage")] - public Task Test_GPT_APFS_APFS_APFS() + public Task Test_GPT_APFS_APFS_APFS_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS is only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.APFS, 50 * MiB), (FileSystemType.APFS, 50 * MiB), (FileSystemType.APFS, 0) @@ -559,11 +559,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage")] - public Task Test_GPT_NTFS_NTFS_NTFS() + public Task Test_GPT_NTFS_NTFS_NTFS_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("NTFS is only supported on Windows."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.NTFS, 50 * MiB), (FileSystemType.NTFS, 50 * MiB), (FileSystemType.NTFS, 0) @@ -571,11 +571,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_HFSPlus_HFSPlus_HFSPlus() + public Task Test_GPT_HFSPlus_HFSPlus_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.HFSPlus, 0) @@ -583,11 +583,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_APFS_HFSPlus_APFS() + public Task Test_GPT_APFS_HFSPlus_APFS_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS and HFSPlus are only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.APFS, 50 * MiB), (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.APFS, 0) @@ -595,11 +595,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_FAT32_APFS_HFSPlus() + public Task Test_GPT_FAT32_APFS_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS and HFSPlus are only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.APFS, 50 * MiB), (FileSystemType.HFSPlus, 0) @@ -607,11 +607,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_ExFAT_APFS_HFSPlus() + public Task Test_GPT_ExFAT_APFS_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("APFS and HFSPlus are only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.ExFAT, 50 * MiB), (FileSystemType.APFS, 50 * MiB), (FileSystemType.HFSPlus, 0) @@ -619,11 +619,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage")] - public Task Test_GPT_Ext4_Ext4_Ext4() + public Task Test_GPT_Ext4_Ext4_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext4 is only supported on Linux."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 0) @@ -631,11 +631,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_FAT32_Ext4_XFS() + public Task Test_GPT_FAT32_Ext4_XFS_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(410 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(410 * MiB), PartitionTableType.GPT, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.XFS, 0) @@ -643,11 +643,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Ext4_XFS_Btrfs() + public Task Test_GPT_Ext4_XFS_Btrfs_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(470 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(470 * MiB), PartitionTableType.GPT, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.XFS, 310 * MiB), (FileSystemType.Btrfs, 0) @@ -655,11 +655,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_XFS_Ext4_Ext4() + public Task Test_GPT_XFS_Ext4_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(410 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(410 * MiB), PartitionTableType.GPT, [ (FileSystemType.XFS, 310 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 0) @@ -667,11 +667,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_Btrfs_Ext4_FAT32() + public Task Test_GPT_Btrfs_Ext4_FAT32_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(210 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(210 * MiB), PartitionTableType.GPT, [ (FileSystemType.Btrfs, 110 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.FAT32, 0) @@ -685,9 +685,9 @@ public class DiskImageTests : BasicSetupHelper // APFS is only supported on GPT partition tables. [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_FAT32_FAT32() + public Task Test_MBR_FAT32_FAT32_FAT32_Async() { - return FullRoundTrip((int)(150 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 0) @@ -695,11 +695,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_HFSPlus_HFSPlus_HFSPlus() + public Task Test_MBR_HFSPlus_HFSPlus_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.MBR, [ (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.HFSPlus, 50 * MiB), (FileSystemType.HFSPlus, 0) @@ -707,11 +707,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_ExFAT_HFSPlus() + public Task Test_MBR_FAT32_ExFAT_HFSPlus_Async() { if (!OperatingSystem.IsMacOS()) Assert.Ignore("HFSPlus is only supported on macOS."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.ExFAT, 50 * MiB), (FileSystemType.HFSPlus, 0) @@ -720,11 +720,11 @@ public class DiskImageTests : BasicSetupHelper // GPT NTFS for Windows - local-only (tested via single partition) [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_GPT_NTFS_FAT32_ExFAT() + public Task Test_GPT_NTFS_FAT32_ExFAT_Async() { if (!OperatingSystem.IsWindows()) Assert.Ignore("This test is only supported on Windows."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.GPT, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.GPT, [ (FileSystemType.NTFS, 50 * MiB), (FileSystemType.FAT32, 50 * MiB), (FileSystemType.ExFAT, 0) @@ -732,11 +732,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Ext4_Ext4_Ext4() + public Task Test_MBR_Ext4_Ext4_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("Ext4 is only supported on Linux."); - return FullRoundTrip((int)(150 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(150 * MiB), PartitionTableType.MBR, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.Ext4, 0) @@ -744,11 +744,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_FAT32_Ext4_XFS() + public Task Test_MBR_FAT32_Ext4_XFS_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(410 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(410 * MiB), PartitionTableType.MBR, [ (FileSystemType.FAT32, 50 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.XFS, 0) @@ -756,11 +756,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Ext4_XFS_Btrfs() + public Task Test_MBR_Ext4_XFS_Btrfs_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(470 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(470 * MiB), PartitionTableType.MBR, [ (FileSystemType.Ext4, 50 * MiB), (FileSystemType.XFS, 310 * MiB), (FileSystemType.Btrfs, 0) @@ -768,11 +768,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_XFS_Btrfs_Ext4() + public Task Test_MBR_XFS_Btrfs_Ext4_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(470 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(470 * MiB), PartitionTableType.MBR, [ (FileSystemType.XFS, 310 * MiB), (FileSystemType.Btrfs, 110 * MiB), (FileSystemType.Ext4, 0) @@ -780,11 +780,11 @@ public class DiskImageTests : BasicSetupHelper } [Test, Category("DiskImage"), Category("DiskImageLocal")] - public Task Test_MBR_Btrfs_Ext4_XFS() + public Task Test_MBR_Btrfs_Ext4_XFS_Async() { if (!OperatingSystem.IsLinux()) Assert.Ignore("This test is only supported on Linux."); - return FullRoundTrip((int)(470 * MiB), PartitionTableType.MBR, [ + return FullRoundTripAsync((int)(470 * MiB), PartitionTableType.MBR, [ (FileSystemType.Btrfs, 110 * MiB), (FileSystemType.Ext4, 50 * MiB), (FileSystemType.XFS, 0) @@ -793,7 +793,7 @@ public class DiskImageTests : BasicSetupHelper #endregion - public async Task FullRoundTrip(int size, PartitionTableType tableType, (FileSystemType, long)[] partitions) + public async Task FullRoundTripAsync(int size, PartitionTableType tableType, (FileSystemType, long)[] partitions) { await TestContext.Progress.WriteLineAsync("Test: Full Round-Trip Backup + Restore"); @@ -805,13 +805,13 @@ public class DiskImageTests : BasicSetupHelper // Populate source partition with test data foreach (var partition in sourcePartitions) - await ToolTests.GenerateTestData(partition, 10, 5, 2, 1024); + await ToolTests.GenerateTestDataAsync(partition, 10, 5, 2, 1024); _diskHelper.FlushDisk(sourceDrivePath); _diskHelper.Unmount(sourceDrivePath); await TestContext.Progress.WriteLineAsync($"Test data generated on source partition(s)"); // Backup - var backupResults = RunBackup(sourceDrivePath); + var backupResults = await RunBackupAsync(sourceDrivePath); TestUtils.AssertResults(backupResults); await TestContext.Progress.WriteLineAsync($"Backup completed successfully"); @@ -822,7 +822,7 @@ public class DiskImageTests : BasicSetupHelper await TestContext.Progress.WriteLineAsync($"Restore disk image created at: {_restoreImagePath}"); // Restore - var restoreResults = RunRestore(restoreDrivePath); + var restoreResults = await RunRestoreAsync(restoreDrivePath); TestUtils.AssertResults(restoreResults); await TestContext.Progress.WriteLineAsync($"Restore completed successfully"); @@ -851,7 +851,7 @@ public class DiskImageTests : BasicSetupHelper [Test] [Category("DiskImage")] - public async Task Test_GeometryMetadata_Verification() + public async Task Test_GeometryMetadata_Verification_Async() { await TestContext.Progress.WriteLineAsync("Test: Full Round-Trip Backup + Restore"); @@ -864,13 +864,13 @@ public class DiskImageTests : BasicSetupHelper await TestContext.Progress.WriteLineAsync($"Source Disk initialized with partition(s): {string.Join(", ", sourcePartitions)}"); // Backup - var backupResults = RunBackup(sourceDrivePath); + var backupResults = await RunBackupAsync(sourceDrivePath); TestUtils.AssertResults(backupResults); // List backup contents and verify geometry.json is present using (var c = new Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var listResults = c.List("*"); + var listResults = await c.ListAsync("*").ConfigureAwait(false); TestUtils.AssertResults(listResults); // Check that geometry.json is in the backup @@ -881,19 +881,19 @@ public class DiskImageTests : BasicSetupHelper } // Verify geometry metadata contains correct information - VerifyGeometryMetadata(TARGETFOLDER, TestOptions, partitions); + await VerifyGeometryMetadataAsync(TARGETFOLDER, TestOptions, partitions); } [Test] [Category("DiskImage")] - public async Task Test_SourceProvider_Enumeration() + public async Task Test_SourceProvider_Enumeration_Async() { - TestContext.Progress.WriteLine("Test: SourceProvider Enumeration"); + await TestContext.Progress.WriteLineAsync("Test: SourceProvider Enumeration"); // Setup: Create GPT disk with one FAT32 partition var sourceDrivePath = _diskHelper.CreateDisk(_sourceImagePath, (100 * MiB)); var sourcePartitions = _diskHelper.InitializeDisk(sourceDrivePath, PartitionTableType.GPT, [(FileSystemType.FAT32, 0)]); - TestContext.Progress.WriteLine($"Source disk image created at: {_sourceImagePath}"); + await TestContext.Progress.WriteLineAsync($"Source disk image created at: {_sourceImagePath}"); // Directly instantiate SourceProvider // Note: When directly instantiating SourceProvider, we don't use the @ prefix @@ -909,7 +909,7 @@ public class DiskImageTests : BasicSetupHelper await foreach (var entry in provider.Enumerate(CancellationToken.None)) { diskEntries.Add(entry); - TestContext.Progress.WriteLine($"Found entry: {entry.Path} (IsFolder: {entry.IsFolder}, Size: {entry.Size})"); + await TestContext.Progress.WriteLineAsync($"Found entry: {entry.Path} (IsFolder: {entry.IsFolder}, Size: {entry.Size})"); } // Verify hierarchy: disk → partition → filesystem → files @@ -924,7 +924,7 @@ public class DiskImageTests : BasicSetupHelper await foreach (var entry in diskEntry!.Enumerate(CancellationToken.None)) entries.Add(entry); - TestContext.Progress.WriteLine($"Total entries found: {entries.Count}"); + await TestContext.Progress.WriteLineAsync($"Total entries found: {entries.Count}"); // Check for geometry.json var geometryEntry = entries.FirstOrDefault(e => e.Path.EndsWith("geometry.json")); @@ -947,25 +947,25 @@ public class DiskImageTests : BasicSetupHelper [Test] [Category("DiskImage")] - public async Task Test_AutoUnmountOption_RestoreWhileOnline() + public async Task Test_AutoUnmountOption_RestoreWhileOnline_Async() { - TestContext.Progress.WriteLine("Test: Auto Unmount Option - Restore While Disk Online"); + await TestContext.Progress.WriteLineAsync("Test: Auto Unmount Option - Restore While Disk Online"); // Setup source disk image: 100MB, GPT, single FAT32 partition var sourceDrivePath = _diskHelper.CreateDisk(_sourceImagePath, (100 * MiB)); var sourcePartitions = _diskHelper.InitializeDisk(sourceDrivePath, PartitionTableType.GPT, [(FileSystemType.FAT32, 0)]); - TestContext.Progress.WriteLine($"Source disk image created at: {_sourceImagePath}"); + await TestContext.Progress.WriteLineAsync($"Source disk image created at: {_sourceImagePath}"); // Generate some test data - await ToolTests.GenerateTestData(sourcePartitions.First(), 10, 5, 2, 1024); + await ToolTests.GenerateTestDataAsync(sourcePartitions.First(), 10, 5, 2, 1024); _diskHelper.FlushDisk(sourceDrivePath); _diskHelper.Unmount(sourceDrivePath); - TestContext.Progress.WriteLine($"Test data generated on source partition"); + await TestContext.Progress.WriteLineAsync($"Test data generated on source partition"); // Backup - var backupResults = RunBackup(sourceDrivePath); + var backupResults = await RunBackupAsync(sourceDrivePath); TestUtils.AssertResults(backupResults); - TestContext.Progress.WriteLine($"Backup completed successfully"); + await TestContext.Progress.WriteLineAsync($"Backup completed successfully"); // Create and attach disk image var restoreDrivePath = _diskHelper.CreateDisk(_restoreImagePath, (100 * MiB)); @@ -973,11 +973,11 @@ public class DiskImageTests : BasicSetupHelper // Initialize disk (the restore will overwrite this, but we need it formatted) _diskHelper.InitializeDisk(restoreDrivePath, PartitionTableType.GPT, [(FileSystemType.FAT32, 50 * MiB), (FileSystemType.FAT32, 0)]); _diskHelper.Mount(restoreDrivePath, _restoreMountPath); - TestContext.Progress.WriteLine($"Restore disk image created and kept online at: {_restoreImagePath}"); + await TestContext.Progress.WriteLineAsync($"Restore disk image created and kept online at: {_restoreImagePath}"); // First attempt: Restore without auto-unmount option should fail // because the disk is online and in use - TestContext.Progress.WriteLine("Attempting restore without auto-unmount (should fail)..."); + await TestContext.Progress.WriteLineAsync("Attempting restore without auto-unmount (should fail)..."); var options = new Dictionary(TestOptions) { ["restore-path"] = $"@diskimage://{restoreDrivePath}", @@ -991,12 +991,12 @@ public class DiskImageTests : BasicSetupHelper { try { - var results = c.Restore(["*"]); + var results = await c.RestoreAsync(["*"]).ConfigureAwait(false); // Verify that the restore failed (has errors) Assert.That(results.Errors.Any(), Is.True, "Restore should fail when target disk is online and auto-unmount is not enabled"); - TestContext.Progress.WriteLine($"Restore failed as expected with errors: {string.Join(", ", results.Errors)}"); + await TestContext.Progress.WriteLineAsync($"Restore failed as expected with errors: {string.Join(", ", results.Errors)}"); } catch (IOException) { @@ -1009,16 +1009,16 @@ public class DiskImageTests : BasicSetupHelper } // Second attempt: Restore with auto-unmount option should succeed - TestContext.Progress.WriteLine("Attempting restore with auto-unmount enabled (should succeed)..."); + await TestContext.Progress.WriteLineAsync("Attempting restore with auto-unmount enabled (should succeed)..."); options["diskimage-restore-auto-unmount"] = "true"; using (var c = new Controller("file://" + TARGETFOLDER, options, null)) { - var results = c.Restore(["*"]); + var results = await c.RestoreAsync(["*"]).ConfigureAwait(false); Assert.That(!results.Errors.Any() && results.Warnings.Count() <= 1); } - TestContext.Progress.WriteLine($"Restore completed successfully with auto-unmount option"); + await TestContext.Progress.WriteLineAsync($"Restore completed successfully with auto-unmount option"); // Mount before verification, to make disks online on Windows. sourcePartitions = _diskHelper.Mount(sourceDrivePath, _sourceMountPath, readOnly: true); @@ -1041,7 +1041,7 @@ public class DiskImageTests : BasicSetupHelper /// The physical drive path to backup. /// If true, treats filesystem as unknown (forces raw block-based backup). /// The backup results. - private IBackupResults RunBackup(string physicalDrivePath, bool treatFilesystemAsUnknown = false) + private async Task RunBackupAsync(string physicalDrivePath, bool treatFilesystemAsUnknown = false) { var options = new Dictionary(TestOptions); options["enable-module"] = "diskimage"; @@ -1054,7 +1054,7 @@ public class DiskImageTests : BasicSetupHelper using var c = new Controller("file://" + TARGETFOLDER, options, null); var sourceUrl = $"@/testdisk|diskimage://{physicalDrivePath}"; - return c.Backup(new[] { sourceUrl }); + return await c.BackupAsync(new[] { sourceUrl }).ConfigureAwait(false); } /// @@ -1062,7 +1062,7 @@ public class DiskImageTests : BasicSetupHelper /// /// The physical drive path to restore to. /// The restore results. - private IRestoreResults RunRestore(string restoreDrivePath) + private Task RunRestoreAsync(string restoreDrivePath) { var options = new Dictionary(TestOptions); options["restore-path"] = $"@diskimage://{restoreDrivePath}"; @@ -1070,9 +1070,7 @@ public class DiskImageTests : BasicSetupHelper options["restore-file-processors"] = "1"; using var c = new Controller("file://" + TARGETFOLDER, options, null); - var results = c.Restore(new[] { "*" }); - - return results; + return c.RestoreAsync(new[] { "*" }); } /// @@ -1142,13 +1140,13 @@ public class DiskImageTests : BasicSetupHelper /// The backup target URL. /// The options to use when accessing the backup. /// The expected partitions and their sizes. - private void VerifyGeometryMetadata(string target, Dictionary options, (FileSystemType, long)[] partitions) + private async Task VerifyGeometryMetadataAsync(string target, Dictionary options, (FileSystemType, long)[] partitions) { - TestContext.Progress.WriteLine("Verifying geometry metadata..."); + await TestContext.Progress.WriteLineAsync("Verifying geometry metadata..."); using (var c = new Controller("file://" + target, options, null)) { - var listResults = c.List("*"); + var listResults = await c.ListAsync("*").ConfigureAwait(false); Assert.That(listResults.Files, Is.Not.Null); var geometryFile = listResults.Files.FirstOrDefault(f => f.Path.EndsWith("geometry.json")); @@ -1166,7 +1164,7 @@ public class DiskImageTests : BasicSetupHelper using (var restoreController = new Controller("file://" + target, restoreOptions, null)) { - var restoreResults = restoreController.Restore(new[] { geometryFile!.Path }); + var restoreResults = await restoreController.RestoreAsync(new[] { geometryFile!.Path }); TestUtils.AssertResults(restoreResults); } @@ -1185,14 +1183,14 @@ public class DiskImageTests : BasicSetupHelper Assert.That(geometry.Partitions, Is.Not.Null, "Partitions list should be present"); Assert.That(geometry.Partitions!.Count, Is.GreaterThan(0), "Should have at least one partition"); - TestContext.Progress.WriteLine($"Geometry metadata verified: Disk size={geometry.Disk.Size}, " + + await TestContext.Progress.WriteLineAsync($"Geometry metadata verified: Disk size={geometry.Disk.Size}, " + $"Sector size={geometry.Disk.SectorSize}, Partitions={geometry.Partitions.Count}, " + $"Table type={geometry.PartitionTable!.Type}"); for (int i = 0; i < geometry.Partitions.Count; i++) { var partition = geometry.Partitions[i]; - TestContext.Progress.WriteLine($"Partition {i}: Type={partition.Type}, Start={partition.StartOffset}, Size={partition.Size}"); + await TestContext.Progress.WriteLineAsync($"Partition {i}: Type={partition.Type}, Start={partition.StartOffset}, Size={partition.Size}"); Assert.That(partition.Size, Is.GreaterThan(0), $"Partition {i} size should be greater than 0"); if (partitions[i].Item2 > 0) @@ -1276,7 +1274,7 @@ public class DiskImageTests : BasicSetupHelper [TestCase((int)(50 * MiB), PartitionTableType.GPT, FileSystemType.Unknown)] [Category("DiskImage")] [Category("DiskImageFileSystem")] - public async Task Test_FileSystem_IncrementalBackup_UnchangedBlocks_Skipped(int size, PartitionTableType tableType, FileSystemType fsType) + public async Task Test_FileSystem_IncrementalBackup_UnchangedBlocks_Skipped_Async(int size, PartitionTableType tableType, FileSystemType fsType) { await TestContext.Progress.WriteLineAsync($"Test: {fsType} Incremental Backup - Unchanged Blocks Skipped"); @@ -1291,13 +1289,13 @@ public class DiskImageTests : BasicSetupHelper await TestContext.Progress.WriteLineAsync($"Source disk created at: {_sourceImagePath}"); // Generate initial test data - await ToolTests.GenerateTestData(sourcePartitions.First(), 10, 3, 1, 1024); + await ToolTests.GenerateTestDataAsync(sourcePartitions.First(), 10, 3, 1, 1024); _diskHelper.FlushDisk(sourceDrivePath); _diskHelper.Unmount(sourceDrivePath); await TestContext.Progress.WriteLineAsync("Initial test data generated"); // First backup - var firstBackupResults = RunBackup(sourceDrivePath, treatFilesystemAsUnknown); + var firstBackupResults = await RunBackupAsync(sourceDrivePath, treatFilesystemAsUnknown); // When treating filesystem as unknown, the option may not be recognized by the Controller // so we only check for errors, not warnings @@ -1325,7 +1323,7 @@ public class DiskImageTests : BasicSetupHelper _diskHelper.Unmount(restoreDrivePath); await TestContext.Progress.WriteLineAsync($"Restore disk created at: {_restoreImagePath}"); - var restoreResults = RunRestore(restoreDrivePath); + var restoreResults = await RunRestoreAsync(restoreDrivePath); TestUtils.AssertResults(restoreResults); await TestContext.Progress.WriteLineAsync("Initial restore completed successfully"); @@ -1396,7 +1394,7 @@ public class DiskImageTests : BasicSetupHelper await TestContext.Progress.WriteLineAsync($"Source disk re-attached for second backup at: {sourceDrivePath}"); // Second backup (incremental) - var secondBackupResults = RunBackup(sourceDrivePath, treatFilesystemAsUnknown); + var secondBackupResults = await RunBackupAsync(sourceDrivePath, treatFilesystemAsUnknown); // When treating filesystem as unknown, the option may not be recognized by the Controller // so we only check for errors, not warnings @@ -1442,7 +1440,7 @@ public class DiskImageTests : BasicSetupHelper _diskHelper.Unmount(restoreDrivePath); await TestContext.Progress.WriteLineAsync($"Fresh restore disk created for incremental restore"); - var incrementalRestoreResults = RunRestore(restoreDrivePath); + var incrementalRestoreResults = await RunRestoreAsync(restoreDrivePath); TestUtils.AssertResults(incrementalRestoreResults); await TestContext.Progress.WriteLineAsync("Incremental restore completed successfully"); @@ -1467,7 +1465,7 @@ public class DiskImageTests : BasicSetupHelper [TestCase((int)(50 * MiB), PartitionTableType.GPT, FileSystemType.Unknown)] [Category("DiskImage")] [Category("DiskImageFileSystem")] - public async Task Test_FileSystem_UnallocatedSpace_CompressesWell(int size, PartitionTableType tableType, FileSystemType fsType) + public async Task Test_FileSystem_UnallocatedSpace_CompressesWell_Async(int size, PartitionTableType tableType, FileSystemType fsType) { await TestContext.Progress.WriteLineAsync($"Test: {fsType} Unallocated Space Compression"); @@ -1483,13 +1481,13 @@ public class DiskImageTests : BasicSetupHelper // Generate minimal test data (sparse data - only a few small files) // This leaves most of the 50MB as unallocated space - await ToolTests.GenerateTestData(sourcePartitions.First(), 3, 0, 0, 512); + await ToolTests.GenerateTestDataAsync(sourcePartitions.First(), 3, 0, 0, 512); _diskHelper.FlushDisk(sourceDrivePath); _diskHelper.Unmount(sourceDrivePath); await TestContext.Progress.WriteLineAsync("Sparse test data generated (few files, lots of free space)"); // Backup - var backupResults = RunBackup(sourceDrivePath, treatFilesystemAsUnknown); + var backupResults = await RunBackupAsync(sourceDrivePath, treatFilesystemAsUnknown); // When treating filesystem as unknown, the option may not be recognized by the Controller // so we only check for errors, not warnings if (treatFilesystemAsUnknown) @@ -1523,7 +1521,7 @@ public class DiskImageTests : BasicSetupHelper _diskHelper.InitializeDisk(restoreDrivePath, tableType, []); _diskHelper.Unmount(restoreDrivePath); - var restoreResults = RunRestore(restoreDrivePath); + var restoreResults = await RunRestoreAsync(restoreDrivePath); TestUtils.AssertResults(restoreResults); // Reattach and verify @@ -1547,7 +1545,7 @@ public class DiskImageTests : BasicSetupHelper [TestCase((int)(50 * MiB), PartitionTableType.GPT, FileSystemType.Unknown)] [Category("DiskImage")] [Category("DiskImageFileSystem")] - public async Task Test_FileSystem_FullDisk_AllBlocksAllocated(int size, PartitionTableType tableType, FileSystemType fsType) + public async Task Test_FileSystem_FullDisk_AllBlocksAllocated_Async(int size, PartitionTableType tableType, FileSystemType fsType) { await TestContext.Progress.WriteLineAsync($"Test: {fsType} Full Disk - All Blocks Allocated"); @@ -1570,14 +1568,14 @@ public class DiskImageTests : BasicSetupHelper var batchCount = fsType == FileSystemType.Unknown ? 5 : 10; for (int batch = 0; batch < batchCount; batch++) { - await ToolTests.GenerateTestData(Path.Combine(partitionPath, $"batch_{batch}"), 10, 0, 0, 4096); + await ToolTests.GenerateTestDataAsync(Path.Combine(partitionPath, $"batch_{batch}"), 10, 0, 0, 4096); } _diskHelper.FlushDisk(sourceDrivePath); _diskHelper.Unmount(sourceDrivePath); await TestContext.Progress.WriteLineAsync("Disk filled with data"); // Backup - var backupResults = RunBackup(sourceDrivePath, treatFilesystemAsUnknown); + var backupResults = await RunBackupAsync(sourceDrivePath, treatFilesystemAsUnknown); // When treating filesystem as unknown, the option may not be recognized by the Controller // so we only check for errors, not warnings @@ -1605,7 +1603,7 @@ public class DiskImageTests : BasicSetupHelper _diskHelper.InitializeDisk(restoreDrivePath, tableType, []); _diskHelper.Unmount(restoreDrivePath); - var restoreResults = RunRestore(restoreDrivePath); + var restoreResults = await RunRestoreAsync(restoreDrivePath); TestUtils.AssertResults(restoreResults); // Reattach and verify diff --git a/Duplicati/UnitTest/DiskImage/Fat32/Fat32ZeroStreamTests.cs b/Duplicati/UnitTest/DiskImage/Fat32/Fat32ZeroStreamTests.cs index 17df64ca1..d61a73b04 100644 --- a/Duplicati/UnitTest/DiskImage/Fat32/Fat32ZeroStreamTests.cs +++ b/Duplicati/UnitTest/DiskImage/Fat32/Fat32ZeroStreamTests.cs @@ -38,7 +38,7 @@ public class Fat32ZeroStreamTests } [Test] - public async Task Test_Fat32ZeroStream_ReadAsync_ReturnsZeros() + public async Task Test_Fat32ZeroStream_ReadAsync_ReturnsZeros_Async() { const int blockSize = 1024 * 1024; // 1MB using var stream = new Fat32Filesystem.Fat32ZeroStream(blockSize); diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/CoverageIncreasingTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/CoverageIncreasingTests.cs index 11a81ba28..57b391828 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/CoverageIncreasingTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/CoverageIncreasingTests.cs @@ -17,7 +17,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_GPT_GetProtectiveMbrAsync_ReturnsValidMbr() + public async Task Test_GPT_GetProtectiveMbrAsync_ReturnsValidMbr_Async() { var sectorSize = s_gptRawDisk!.SectorSize; @@ -53,7 +53,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_GPT_GetPartitionTableDataAsync_ReturnsValidData() + public async Task Test_GPT_GetPartitionTableDataAsync_ReturnsValidData_Async() { var sectorSize = s_gptRawDisk!.SectorSize; @@ -87,7 +87,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystem_GetFilesystemMetadataAsync_ReturnsCorrectMetadata() + public async Task Test_UnknownFilesystem_GetFilesystemMetadataAsync_ReturnsCorrectMetadata_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -112,7 +112,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystem_GetFileLengthAsync_ReturnsCorrectSize() + public async Task Test_UnknownFilesystem_GetFileLengthAsync_ReturnsCorrectSize_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -142,7 +142,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_SetLength_ThrowsNotSupported() + public async Task Test_UnknownFilesystemStream_SetLength_ThrowsNotSupported_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -171,7 +171,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_WriteOnReadOnly_ThrowsNotSupported() + public async Task Test_UnknownFilesystemStream_WriteOnReadOnly_ThrowsNotSupported_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -201,7 +201,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_ReadOnWriteOnly_ThrowsNotSupported() + public async Task Test_UnknownFilesystemStream_ReadOnWriteOnly_ThrowsNotSupported_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -231,7 +231,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_DisposeAsync_FlushesDirtyData() + public async Task Test_UnknownFilesystemStream_DisposeAsync_FlushesDirtyData_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -315,7 +315,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_PositionSetter_ValidatesBounds() + public async Task Test_UnknownFilesystemStream_PositionSetter_ValidatesBounds_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -359,7 +359,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_Flush_DoesNotThrow() + public async Task Test_UnknownFilesystemStream_Flush_DoesNotThrow_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -396,7 +396,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_WriteBeyondSize_ThrowsArgumentException() + public async Task Test_UnknownFilesystemStream_WriteBeyondSize_ThrowsArgumentException_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -429,7 +429,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_SyncRead_ReturnsCorrectData() + public async Task Test_UnknownFilesystemStream_SyncRead_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -464,7 +464,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper // Read using synchronous Read method using var readStream = await fs.OpenReadStreamAsync(firstFile!, CancellationToken.None); var readBuffer = new byte[256]; - var bytesRead = readStream.Read(readBuffer, 0, 256); + var bytesRead = await readStream.ReadAsync(readBuffer, 0, 256); Assert.AreEqual(256, bytesRead, "Sync Read should return 256 bytes."); for (int i = 0; i < 256; i++) @@ -472,7 +472,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_UnknownFilesystemStream_SyncWrite_DataMatches() + public async Task Test_UnknownFilesystemStream_SyncWrite_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -501,12 +501,12 @@ public partial class DiskImageUnitTests : BasicSetupHelper new Random(42).NextBytes(writeData); using (var writeStream = await fs.OpenWriteStreamAsync(firstFile!, CancellationToken.None)) - writeStream.Write(writeData, 0, writeData.Length); + await writeStream.WriteAsync(writeData, 0, writeData.Length); // Read back and verify using var readStream = await fs.OpenReadStreamAsync(firstFile!, CancellationToken.None); var readBuffer = new byte[100]; - var bytesRead = readStream.Read(readBuffer, 0, 100); + var bytesRead = await readStream.ReadAsync(readBuffer, 0, 100); Assert.AreEqual(100, bytesRead, "Should read 100 bytes."); Assert.AreEqual(writeData, readBuffer, "Sync-written data should match on read-back."); @@ -566,11 +566,11 @@ public partial class DiskImageUnitTests : BasicSetupHelper var metadata = new GeometryMetadata(); using var table = new ReconstructedPartitionTable(_writableRawDisk, metadata, PartitionTableType.GPT); - Assert.Throws(() => + Assert.ThrowsAsync(async () => { // Force enumeration to trigger the throw var enumerator = table.EnumeratePartitions(CancellationToken.None).GetAsyncEnumerator(); - enumerator.MoveNextAsync().AsTask().GetAwaiter().GetResult(); + await enumerator.MoveNextAsync().AsTask(); }, "EnumeratePartitions should throw NotSupportedException."); } @@ -652,7 +652,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that BasePartition.OpenReadAsync returns a readable stream. /// [Test] - public async Task Test_BasePartition_OpenReadAsync_ReturnsReadableStream() + public async Task Test_BasePartition_OpenReadAsync_ReturnsReadableStream_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 10L * sectorSize; @@ -690,7 +690,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that BasePartition.OpenWriteAsync returns a writable PartitionWriteStream. /// [Test] - public async Task Test_BasePartition_OpenWriteAsync_ReturnsWritableStream() + public async Task Test_BasePartition_OpenWriteAsync_ReturnsWritableStream_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 10L * sectorSize; @@ -732,7 +732,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownPartitionTable.GetPartitionTableDataAsync returns empty data. /// [Test] - public async Task Test_UnknownPartitionTable_GetPartitionTableDataAsync_ReturnsEmptyStream() + public async Task Test_UnknownPartitionTable_GetPartitionTableDataAsync_ReturnsEmptyStream_Async() { using var table = new UnknownPartitionTable(null); @@ -756,7 +756,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownPartitionTable.GetPartitionAsync returns null for invalid partition numbers. /// [Test] - public async Task Test_UnknownPartitionTable_GetPartitionAsync_InvalidNumber_ReturnsNull() + public async Task Test_UnknownPartitionTable_GetPartitionAsync_InvalidNumber_ReturnsNull_Async() { using var table = new UnknownPartitionTable(null); @@ -771,7 +771,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownPartitionTable.GetPartitionAsync returns null when no raw disk is set. /// [Test] - public async Task Test_UnknownPartitionTable_GetPartitionAsync_NullDisk_ReturnsNull() + public async Task Test_UnknownPartitionTable_GetPartitionAsync_NullDisk_ReturnsNull_Async() { using var table = new UnknownPartitionTable(null); @@ -783,7 +783,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownPartitionTable.GetPartitionAsync returns a valid partition when disk is set. /// [Test] - public async Task Test_UnknownPartitionTable_GetPartitionAsync_ValidDisk_ReturnsPartition() + public async Task Test_UnknownPartitionTable_GetPartitionAsync_ValidDisk_ReturnsPartition_Async() { using var table = new UnknownPartitionTable(_writableRawDisk); @@ -911,7 +911,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystem path-based OpenReadStreamAsync works correctly. /// [Test] - public async Task Test_UnknownFilesystem_OpenReadStreamAsync_ByPath_ReturnsStream() + public async Task Test_UnknownFilesystem_OpenReadStreamAsync_ByPath_ReturnsStream_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -956,7 +956,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystem path-based OpenWriteStreamAsync works correctly. /// [Test] - public async Task Test_UnknownFilesystem_OpenWriteStreamAsync_ByPath_ReturnsStream() + public async Task Test_UnknownFilesystem_OpenWriteStreamAsync_ByPath_ReturnsStream_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -982,7 +982,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystem path-based OpenReadWriteStreamAsync works correctly. /// [Test] - public async Task Test_UnknownFilesystem_OpenReadWriteStreamAsync_ByPath_ReturnsStream() + public async Task Test_UnknownFilesystem_OpenReadWriteStreamAsync_ByPath_ReturnsStream_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -1009,7 +1009,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystem path-based GetFileLengthAsync returns correct size. /// [Test] - public async Task Test_UnknownFilesystem_GetFileLengthAsync_ByPath_ReturnsCorrectSize() + public async Task Test_UnknownFilesystem_GetFileLengthAsync_ByPath_ReturnsCorrectSize_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -1095,7 +1095,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystemStream.Seek with invalid origin throws ArgumentException. /// [Test] - public async Task Test_UnknownFilesystemStream_Seek_InvalidOrigin_ThrowsArgumentException() + public async Task Test_UnknownFilesystemStream_Seek_InvalidOrigin_ThrowsArgumentException_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -1128,7 +1128,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystemStream.Seek beyond bounds throws ArgumentOutOfRangeException. /// [Test] - public async Task Test_UnknownFilesystemStream_Seek_BeyondBounds_ThrowsArgumentOutOfRange() + public async Task Test_UnknownFilesystemStream_Seek_BeyondBounds_ThrowsArgumentOutOfRange_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -1223,7 +1223,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that UnknownFilesystem.ParsePathToAddress throws for invalid path format. /// [Test] - public async Task Test_UnknownFilesystem_OpenReadStreamAsync_InvalidPath_ThrowsArgumentException() + public async Task Test_UnknownFilesystem_OpenReadStreamAsync_InvalidPath_ThrowsArgumentException_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/EdgeCaseBoundaryTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/EdgeCaseBoundaryTests.cs index cc3ff408f..ce16943ee 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/EdgeCaseBoundaryTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/EdgeCaseBoundaryTests.cs @@ -14,7 +14,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading at offset 0 (first sector) returns correct data. /// [Test] - public async Task Test_RawDisk_ReadAtOffsetZero_ReturnsCorrectData() + public async Task Test_RawDisk_ReadAtOffsetZero_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -38,7 +38,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests writing at offset 0 (first sector) works correctly. /// [Test] - public async Task Test_RawDisk_WriteAtOffsetZero_DataMatches() + public async Task Test_RawDisk_WriteAtOffsetZero_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -62,7 +62,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading at the last sector of the disk returns correct data. /// [Test] - public async Task Test_RawDisk_ReadAtLastSector_ReturnsCorrectData() + public async Task Test_RawDisk_ReadAtLastSector_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; var diskSize = _writableRawDisk.Size; @@ -89,7 +89,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests writing at the last sector of the disk works correctly. /// [Test] - public async Task Test_RawDisk_WriteAtLastSector_DataMatches() + public async Task Test_RawDisk_WriteAtLastSector_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var diskSize = _writableRawDisk.Size; @@ -115,7 +115,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading exactly one sector returns correct data. /// [Test] - public async Task Test_RawDisk_ReadExactlyOneSector_ReturnsCorrectData() + public async Task Test_RawDisk_ReadExactlyOneSector_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -141,7 +141,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests writing exactly one sector works correctly. /// [Test] - public async Task Test_RawDisk_WriteExactlyOneSector_DataMatches() + public async Task Test_RawDisk_WriteExactlyOneSector_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -167,7 +167,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading the full disk size works correctly. /// [Test] - public async Task Test_RawDisk_ReadFullDiskSize_ReturnsCorrectData() + public async Task Test_RawDisk_ReadFullDiskSize_ReturnsCorrectData_Async() { // Use a smaller test disk for this test to avoid memory issues var sectorSize = _writableRawDisk.SectorSize; @@ -198,7 +198,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests writing the full disk size works correctly. /// [Test] - public async Task Test_RawDisk_WriteFullDiskSize_DataMatches() + public async Task Test_RawDisk_WriteFullDiskSize_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var diskSize = _writableRawDisk.Size; @@ -231,7 +231,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests writing empty data (0 bytes) returns 0 without error. /// [Test] - public async Task Test_RawDisk_WriteEmptyData_ReturnsZero() + public async Task Test_RawDisk_WriteEmptyData_ReturnsZero_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -246,7 +246,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading empty data (0 bytes) returns 0 without error. /// [Test] - public async Task Test_RawDisk_ReadEmptyData_ReturnsZero() + public async Task Test_RawDisk_ReadEmptyData_ReturnsZero_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -264,7 +264,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Creates a small test disk and verifies read/write operations work. /// [Test] - public async Task Test_RawDisk_MinimumSizeDisk_OperationsWork() + public async Task Test_RawDisk_MinimumSizeDisk_OperationsWork_Async() { // The minimum practical disk size is at least a few sectors // We'll use the writable disk which is already created at 100 MiB @@ -343,7 +343,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading/writing a single byte at various boundary positions. /// [Test] - public async Task Test_RawDisk_SingleByteAtBoundaries_DataMatches() + public async Task Test_RawDisk_SingleByteAtBoundaries_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableFactoryTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableFactoryTests.cs index 16aa212b8..4158b6221 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableFactoryTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableFactoryTests.cs @@ -11,7 +11,7 @@ namespace Duplicati.UnitTest.DiskImage.UnitTests; public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_PartitionTableFactory_GPTBytes_DetectsGPT() + public async Task Test_PartitionTableFactory_GPTBytes_DetectsGPT_Async() { var sectorSize = 512; var diskBytes = new byte[sectorSize * 4]; @@ -32,7 +32,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableFactory_MBRBytes_DetectsMBR() + public async Task Test_PartitionTableFactory_MBRBytes_DetectsMBR_Async() { var sectorSize = 512; var diskBytes = new byte[sectorSize]; @@ -48,7 +48,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableFactory_InvalidBytes_ReturnsUnknown() + public async Task Test_PartitionTableFactory_InvalidBytes_ReturnsUnknown_Async() { var sectorSize = 512; var diskBytes = new byte[sectorSize]; @@ -60,7 +60,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableFactory_ProtectiveMBRType_DetectsGPT() + public async Task Test_PartitionTableFactory_ProtectiveMBRType_DetectsGPT_Async() { var sectorSize = 512; var diskBytes = new byte[sectorSize * 4]; @@ -79,7 +79,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableFactory_ProtectiveMBRWithoutGptHeader_FallsBackToMBR() + public async Task Test_PartitionTableFactory_ProtectiveMBRWithoutGptHeader_FallsBackToMBR_Async() { var sectorSize = 512; var diskBytes = new byte[sectorSize * 2]; @@ -95,7 +95,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableFactory_GPTFromRealDisk_DetectsGPT() + public async Task Test_PartitionTableFactory_GPTFromRealDisk_DetectsGPT_Async() { var sectorSize = s_gptRawDisk!.SectorSize; var sectorsToRead = 34; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableParsingTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableParsingTests.cs index 28a9dbd18..5b57eadaf 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableParsingTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableParsingTests.cs @@ -11,7 +11,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_GPT_ParseFromRealDisk_ReturnsCorrectPartitions() + public async Task Test_GPT_ParseFromRealDisk_ReturnsCorrectPartitions_Async() { var sectorSize = s_gptRawDisk!.SectorSize; @@ -53,7 +53,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_MBR_ParseFromRealDisk_ReturnsCorrectPartitions() + public async Task Test_MBR_ParseFromRealDisk_ReturnsCorrectPartitions_Async() { var sectorSize = s_mbrRawDisk!.SectorSize; @@ -97,7 +97,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_GPT_EnumeratePartitions_ReturnsCorrectCount() + public async Task Test_GPT_EnumeratePartitions_ReturnsCorrectCount_Async() { var sectorSize = s_gptRawDisk!.SectorSize; @@ -122,7 +122,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_MBR_EnumeratePartitions_ReturnsCorrectCount() + public async Task Test_MBR_EnumeratePartitions_ReturnsCorrectCount_Async() { var sectorSize = s_mbrRawDisk!.SectorSize; @@ -147,7 +147,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_GPT_GetPartitionAsync_ReturnsCorrectPartition() + public async Task Test_GPT_GetPartitionAsync_ReturnsCorrectPartition_Async() { var sectorSize = s_gptRawDisk!.SectorSize; @@ -180,7 +180,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_MBR_GetPartitionAsync_ReturnsCorrectPartition() + public async Task Test_MBR_GetPartitionAsync_ReturnsCorrectPartition_Async() { var sectorSize = s_mbrRawDisk!.SectorSize; @@ -213,7 +213,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_GPT_PartitionAlignment_IsSectorAligned() + public async Task Test_GPT_PartitionAlignment_IsSectorAligned_Async() { var sectorSize = s_gptRawDisk!.SectorSize; @@ -237,7 +237,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_MBR_PartitionAlignment_IsSectorAligned() + public async Task Test_MBR_PartitionAlignment_IsSectorAligned_Async() { var sectorSize = s_mbrRawDisk!.SectorSize; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableSynthesizerTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableSynthesizerTests.cs index ba2322e0a..51344e84f 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableSynthesizerTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionTableSynthesizerTests.cs @@ -122,7 +122,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableSynthesizer_MBRRoundTrip_PreservesPartitionData() + public async Task Test_PartitionTableSynthesizer_MBRRoundTrip_PreservesPartitionData_Async() { // Create a GeometryMetadata with known partition info var sectorSize = 512; @@ -193,7 +193,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionTableSynthesizer_GPTRoundTrip_PreservesPartitionData() + public async Task Test_PartitionTableSynthesizer_GPTRoundTrip_PreservesPartitionData_Async() { // Create a GeometryMetadata with known partition info var sectorSize = 512; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionWriteStreamTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionWriteStreamTests.cs index 68da7fba6..2aedd4ccb 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/PartitionWriteStreamTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/PartitionWriteStreamTests.cs @@ -13,7 +13,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_PartitionWriteStream_WriteAndDispose_DataFlushedToDisk() + public async Task Test_PartitionWriteStream_WriteAndDispose_DataFlushedToDisk_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 10 * sectorSize; @@ -137,7 +137,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionWriteStream_WriteAtUnalignedPosition_DataMatches() + public async Task Test_PartitionWriteStream_WriteAtUnalignedPosition_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 15 * sectorSize; @@ -147,15 +147,15 @@ public partial class DiskImageUnitTests : BasicSetupHelper using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize)) { // Write at position 0 - stream.Write([0xAA, 0xBB], 0, 2); + await stream.WriteAsync([0xAA, 0xBB], 0, 2); // Seek to unaligned position stream.Seek(7, SeekOrigin.Begin); - stream.Write([0xCC, 0xDD, 0xEE], 0, 3); + await stream.WriteAsync([0xCC, 0xDD, 0xEE], 0, 3); // Seek to another unaligned position stream.Seek(20, SeekOrigin.Begin); - stream.Write([0x11, 0x22, 0x33, 0x44], 0, 4); + await stream.WriteAsync([0x11, 0x22, 0x33, 0x44], 0, 4); } // Read back and verify @@ -182,7 +182,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionWriteStream_WriteMultipleTimes_DataMatches() + public async Task Test_PartitionWriteStream_WriteMultipleTimes_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 20 * sectorSize; @@ -192,13 +192,13 @@ public partial class DiskImageUnitTests : BasicSetupHelper using (var stream = new PartitionWriteStream(_writableRawDisk, partitionOffset, partitionSize)) { // First write - stream.Write([0x01, 0x02, 0x03], 0, 3); + await stream.WriteAsync([0x01, 0x02, 0x03], 0, 3); // Second write (should append) - stream.Write([0x04, 0x05], 0, 2); + await stream.WriteAsync([0x04, 0x05], 0, 2); // Third write - stream.Write([0x06, 0x07, 0x08, 0x09], 0, 4); + await stream.WriteAsync([0x06, 0x07, 0x08, 0x09], 0, 4); } // Read back and verify all data was written @@ -230,7 +230,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionWriteStream_SetLengthWithinMaxSize_SetsLength() + public async Task Test_PartitionWriteStream_SetLengthWithinMaxSize_SetsLength_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 10 * sectorSize; @@ -243,7 +243,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper Assert.AreEqual(100, stream.Length, "Length should be set to 100."); // Write some data at position 0 - stream.Write([0xAA, 0xBB, 0xCC], 0, 3); + await stream.WriteAsync([0xAA, 0xBB, 0xCC], 0, 3); // Length should be max(SetLength(100), written position) = 100 Assert.AreEqual(100, stream.Length, "Length should remain 100 (SetLength value)."); } @@ -300,7 +300,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_PartitionWriteStream_WriteEmptyData_NoFlush() + public async Task Test_PartitionWriteStream_WriteEmptyData_NoFlush_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionOffset = 10 * sectorSize; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskSectorAlignedTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskSectorAlignedTests.cs index 0ed44bc42..6cbff2535 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskSectorAlignedTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskSectorAlignedTests.cs @@ -11,7 +11,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_RawDisk_ReadSector_ReturnsNonEmptyData() + public async Task Test_RawDisk_ReadSector_ReturnsNonEmptyData_Async() { var sectorSize = s_gptRawDisk!.SectorSize; using var stream = await s_gptRawDisk.ReadSectorsAsync(0, 1, CancellationToken.None); @@ -23,7 +23,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadBytes_ReturnsData() + public async Task Test_RawDisk_ReadBytes_ReturnsData_Async() { var sectorSize = s_gptRawDisk!.SectorSize; using var stream = await s_gptRawDisk.ReadBytesAsync(sectorSize, sectorSize, CancellationToken.None); @@ -31,7 +31,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadBytesAsync_CallerProvidedBuffer() + public async Task Test_RawDisk_ReadBytesAsync_CallerProvidedBuffer_Async() { var sectorSize = s_gptRawDisk!.SectorSize; var buffer = new byte[sectorSize]; @@ -40,7 +40,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteSectors_DataMatches() + public async Task Test_RawDisk_WriteSectors_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var writeBuffer = new byte[sectorSize]; @@ -54,7 +54,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteBytes_DataMatches() + public async Task Test_RawDisk_WriteBytes_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var writeBuffer = new byte[sectorSize]; @@ -68,7 +68,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteBytes_Memory() + public async Task Test_RawDisk_WriteBytes_Memory_Async() { var sectorSize = _writableRawDisk.SectorSize; var writeBuffer = new byte[sectorSize]; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedReadTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedReadTests.cs index 4f0cb474a..6fbef908b 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedReadTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedReadTests.cs @@ -9,7 +9,7 @@ namespace Duplicati.UnitTest.DiskImage.UnitTests; public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_RawDisk_ReadUnalignedOffset_ReturnsCorrectData() + public async Task Test_RawDisk_ReadUnalignedOffset_ReturnsCorrectData_Async() { // Use writable disk for this test var sectorSize = _writableRawDisk.SectorSize; @@ -35,7 +35,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadUnalignedLength_ReturnsCorrectData() + public async Task Test_RawDisk_ReadUnalignedLength_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -58,7 +58,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadShortLength_ReturnsCorrectData() + public async Task Test_RawDisk_ReadShortLength_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -81,7 +81,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadStraddlingSectors_ReturnsCorrectData() + public async Task Test_RawDisk_ReadStraddlingSectors_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -121,7 +121,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadNearEndOfDisk_ReturnsCorrectData() + public async Task Test_RawDisk_ReadNearEndOfDisk_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; var diskSize = _writableRawDisk.Size; @@ -144,7 +144,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadUnalignedOffsetWithMemory_ReturnsCorrectData() + public async Task Test_RawDisk_ReadUnalignedOffsetWithMemory_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -167,7 +167,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_ReadUnalignedLengthWithMemory_ReturnsCorrectData() + public async Task Test_RawDisk_ReadUnalignedLengthWithMemory_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedWriteTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedWriteTests.cs index a73d1569d..40349d906 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedWriteTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/RawDiskUnalignedWriteTests.cs @@ -10,7 +10,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper { [Test] - public async Task Test_RawDisk_WriteUnalignedOffset_DataMatches() + public async Task Test_RawDisk_WriteUnalignedOffset_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -54,7 +54,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteUnalignedLength_DataMatches() + public async Task Test_RawDisk_WriteUnalignedLength_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -92,7 +92,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteSingleByte_VerifySingleByteChanged() + public async Task Test_RawDisk_WriteSingleByte_VerifySingleByteChanged_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -126,7 +126,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteSpanningSectorBoundary_DataMatches() + public async Task Test_RawDisk_WriteSpanningSectorBoundary_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; @@ -170,7 +170,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteAtLastSector_NoOverflow() + public async Task Test_RawDisk_WriteAtLastSector_NoOverflow_Async() { var sectorSize = _writableRawDisk.SectorSize; var diskSize = _writableRawDisk.Size; @@ -195,7 +195,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper } [Test] - public async Task Test_RawDisk_WriteUnalignedWithMemory_DataMatches() + public async Task Test_RawDisk_WriteUnalignedWithMemory_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/SetupTeardown.cs b/Duplicati/UnitTest/DiskImage/UnitTests/SetupTeardown.cs index 2b9029a05..9bc9d60f7 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/SetupTeardown.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/SetupTeardown.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Threading; +using System.Threading.Tasks; using Duplicati.Proprietary.DiskImage.Disk; using Duplicati.Proprietary.DiskImage.General; using Duplicati.UnitTest.DiskImage.Helpers; @@ -54,7 +55,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Creates read-only GPT and MBR disks with 2 FAT32 partitions each. /// [OneTimeSetUp] - public void OneTimeSetUp() + public async Task OneTimeSetUpAsync() { base.BasicHelperSetUp(); @@ -86,12 +87,12 @@ public partial class DiskImageUnitTests : BasicSetupHelper // Create and initialize raw disk for GPT (read-only) s_gptRawDisk = DiskImageTestHelpers.CreateRawDiskForIdentifier(s_gptDiskIdentifier); - if (!s_gptRawDisk.InitializeAsync(true, CancellationToken.None).Result) + if (!await s_gptRawDisk.InitializeAsync(true, CancellationToken.None)) throw new InvalidOperationException("Failed to initialize GPT raw disk"); // Fill GPT partitions with well-known test data - DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_gptRawDisk, s_gptPartition1Offset, s_gptPartition1Size, CancellationToken.None).Wait(); - DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_gptRawDisk, s_gptPartition2Offset, s_gptPartition2Size, CancellationToken.None).Wait(); + await DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_gptRawDisk, s_gptPartition1Offset, s_gptPartition1Size, CancellationToken.None); + await DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_gptRawDisk, s_gptPartition2Offset, s_gptPartition2Size, CancellationToken.None); s_diskHelper.FlushDisk(s_gptDiskIdentifier); // Create MBR disk with 2 FAT32 partitions @@ -111,12 +112,12 @@ public partial class DiskImageUnitTests : BasicSetupHelper // Create and initialize raw disk for MBR (read-only) s_mbrRawDisk = DiskImageTestHelpers.CreateRawDiskForIdentifier(s_mbrDiskIdentifier); - if (!s_mbrRawDisk.InitializeAsync(true, CancellationToken.None).Result) + if (!await s_mbrRawDisk.InitializeAsync(true, CancellationToken.None)) throw new InvalidOperationException("Failed to initialize MBR raw disk"); // Fill MBR partitions with well-known test data - DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_mbrRawDisk, s_mbrPartition1Offset, s_mbrPartition1Size, CancellationToken.None).Wait(); - DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_mbrRawDisk, s_mbrPartition2Offset, s_mbrPartition2Size, CancellationToken.None).Wait(); + await DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_mbrRawDisk, s_mbrPartition1Offset, s_mbrPartition1Size, CancellationToken.None); + await DiskImageTestHelpers.FillPartitionWithTestDataAsync(s_mbrRawDisk, s_mbrPartition2Offset, s_mbrPartition2Size, CancellationToken.None); s_diskHelper.FlushDisk(s_mbrDiskIdentifier); // Create writable disk path (will be created per-test) @@ -128,7 +129,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper // Create raw disk interface (with write access) s_writableRawDisk = DiskImageTestHelpers.CreateRawDiskForIdentifier(s_writableDiskIdentifier); - if (!s_writableRawDisk.InitializeAsync(true, CancellationToken.None).Result) + if (!await s_writableRawDisk.InitializeAsync(true, CancellationToken.None)) throw new InvalidOperationException("Failed to initialize writable raw disk"); } diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemStreamTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemStreamTests.cs index 2da35e043..c7312a127 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemStreamTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemStreamTests.cs @@ -17,7 +17,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that writing partial data (less than block size) is correctly flushed on dispose. /// [Test] - public async Task Test_UnknownFilesystemStream_WritePartialData_FlushesOnDispose() + public async Task Test_UnknownFilesystemStream_WritePartialData_FlushesOnDispose_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -65,7 +65,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests writing at position 0, seeking to middle, writing more data, and verifying both regions. /// [Test] - public async Task Test_UnknownFilesystemStream_WriteSeekWrite_BothRegionsPreserved() + public async Task Test_UnknownFilesystemStream_WriteSeekWrite_BothRegionsPreserved_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -128,7 +128,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests reading after seeking to a non-zero position. /// [Test] - public async Task Test_UnknownFilesystemStream_ReadAfterSeek_ReturnsCorrectData() + public async Task Test_UnknownFilesystemStream_ReadAfterSeek_ReturnsCorrectData_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -184,7 +184,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// while the remaining bytes retain their previous values. /// [Test] - public async Task Test_UnknownFilesystemStream_WritePartial_PreservesData() + public async Task Test_UnknownFilesystemStream_WritePartial_PreservesData_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -240,7 +240,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests the lazy-load behavior: first read triggers disk read, subsequent reads use buffer. /// [Test] - public async Task Test_UnknownFilesystemStream_LazyLoad_FirstReadTriggersDiskRead() + public async Task Test_UnknownFilesystemStream_LazyLoad_FirstReadTriggersDiskRead_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -305,7 +305,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests seeking with SeekOrigin.Current works correctly. /// [Test] - public async Task Test_UnknownFilesystemStream_SeekCurrent_UpdatesPosition() + public async Task Test_UnknownFilesystemStream_SeekCurrent_UpdatesPosition_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -366,7 +366,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests seeking with SeekOrigin.End works correctly. /// [Test] - public async Task Test_UnknownFilesystemStream_SeekEnd_UpdatesPosition() + public async Task Test_UnknownFilesystemStream_SeekEnd_UpdatesPosition_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -421,7 +421,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that reading past the end of the stream returns 0 bytes. /// [Test] - public async Task Test_UnknownFilesystemStream_ReadPastEnd_ReturnsZero() + public async Task Test_UnknownFilesystemStream_ReadPastEnd_ReturnsZero_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; @@ -473,7 +473,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that unaligned writes within the stream work correctly. /// [Test] - public async Task Test_UnknownFilesystemStream_UnalignedWrite_DataMatches() + public async Task Test_UnknownFilesystemStream_UnalignedWrite_DataMatches_Async() { var sectorSize = _writableRawDisk.SectorSize; var partitionSize = 5 * MiB; diff --git a/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemTests.cs b/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemTests.cs index ce6fb8280..72ff5f582 100644 --- a/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemTests.cs +++ b/Duplicati/UnitTest/DiskImage/UnitTests/UnknownFilesystemTests.cs @@ -22,7 +22,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that ListFilesAsync returns the expected block-based files for an UnknownFilesystem. /// [Test] - public async Task Test_UnknownFilesystem_ListFilesAsync_ReturnsBlockBasedFiles() + public async Task Test_UnknownFilesystem_ListFilesAsync_ReturnsBlockBasedFiles_Async() { // Create a partition on the writable disk var sectorSize = _writableRawDisk.SectorSize; @@ -68,7 +68,7 @@ public partial class DiskImageUnitTests : BasicSetupHelper /// Tests that write and read round-trip preserves data correctly. /// [Test] - public async Task Test_UnknownFilesystem_WriteRead_RoundTrip_DataMatches() + public async Task Test_UnknownFilesystem_WriteRead_RoundTrip_DataMatches_Async() { // Create a partition on the writable disk var sectorSize = _writableRawDisk.SectorSize; diff --git a/Duplicati/UnitTest/DisruptionTests.cs b/Duplicati/UnitTest/DisruptionTests.cs index 5709b72d6..ba8b10498 100644 --- a/Duplicati/UnitTest/DisruptionTests.cs +++ b/Duplicati/UnitTest/DisruptionTests.cs @@ -53,7 +53,7 @@ namespace Duplicati.UnitTest } } - private async Task<(IBackupResults Result, List ModifiedFiles)> RunPartialBackup(Controller controller) + private async Task<(IBackupResults Result, List ModifiedFiles)> RunPartialBackupAsync(Controller controller) { this.ModifySourceFiles(); @@ -71,7 +71,7 @@ namespace Duplicati.UnitTest if (modifiedFiles.Count == 3) { Thread.Sleep(500); - controller.Stop(); + controller.StopAsync().GetAwaiter().GetResult(); stopped.TrySetResult(true); } }; @@ -79,7 +79,7 @@ namespace Duplicati.UnitTest }; // ReSharper disable once AccessToDisposedClosure - Task backupTask = Task.Run(() => controller.Backup(new[] { this.DATAFOLDER })); + Task backupTask = controller.BackupAsync(new[] { this.DATAFOLDER }); var t = await Task.WhenAny(backupTask, stopped.Task).ConfigureAwait(false); if (t != stopped.Task) @@ -96,7 +96,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task FilesetFiles() + public async Task FilesetFilesAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -113,9 +113,9 @@ namespace Duplicati.UnitTest }; // Run a full backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -124,20 +124,20 @@ namespace Duplicati.UnitTest Thread.Sleep(2000); // Run a partial backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); } - Dictionary GetBackupTypesFromRemoteFiles(Controller c, out List filelistFiles) + async Task<(Dictionary map, List filelistFiles)> GetBackupTypesFromRemoteFilesAsync(Controller c) { - Dictionary map = new Dictionary(); - filelistFiles = new List(); + var map = new Dictionary(); + var filelistFiles = new List(); - IListRemoteResults remoteFiles = c.ListRemote(); - foreach (IFileEntry file in remoteFiles.Files) + var remoteFiles = await c.ListRemoteAsync(); + foreach (var file in remoteFiles.Files) { IParsedVolume volume = VolumeBase.ParseFilename(file); if (volume != null && volume.FileType == RemoteVolumeType.Files) @@ -149,25 +149,25 @@ namespace Duplicati.UnitTest } } - return map; + return (map, filelistFiles); } // Purge a file and verify that the fileset file exists in the new dlist files. List dlistFiles; Dictionary backupTypeMap; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.PurgeFiles(new Library.Utility.FilterExpression($"{this.DATAFOLDER}/*{this.fileSizes[0]}*"))); + TestUtils.AssertResults(await c.PurgeFilesAsync(new Library.Utility.FilterExpression($"{this.DATAFOLDER}/*{this.fileSizes[0]}*"))); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(BackupType.FULL_BACKUP, filesets.Single(x => x.Version == 1).IsFullBackup); Assert.AreEqual(BackupType.PARTIAL_BACKUP, filesets.Single(x => x.Version == 0).IsFullBackup); - backupTypeMap = GetBackupTypesFromRemoteFiles(c, out dlistFiles); + (backupTypeMap, dlistFiles) = await GetBackupTypesFromRemoteFilesAsync(c); } - int[] backupTypes = backupTypeMap.OrderByDescending(x => x.Key).Select(x => x.Value).ToArray(); + var backupTypes = backupTypeMap.OrderByDescending(x => x.Key).Select(x => x.Value).ToArray(); Assert.AreEqual(2, backupTypes.Length); Assert.AreEqual(BackupType.FULL_BACKUP, backupTypes[1]); Assert.AreEqual(BackupType.PARTIAL_BACKUP, backupTypes[0]); @@ -179,16 +179,16 @@ namespace Duplicati.UnitTest } // Run a repair and verify that the fileset file exists in the new dlist files. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(BackupType.FULL_BACKUP, filesets.Single(x => x.Version == 1).IsFullBackup); Assert.AreEqual(BackupType.PARTIAL_BACKUP, filesets.Single(x => x.Version == 0).IsFullBackup); - backupTypeMap = GetBackupTypesFromRemoteFiles(c, out _); + (backupTypeMap, _) = await GetBackupTypesFromRemoteFilesAsync(c); } backupTypes = backupTypeMap.OrderByDescending(x => x.Key).Select(x => x.Value).ToArray(); @@ -199,7 +199,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task KeepTimeRetention() + public async Task KeepTimeRetentionAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -216,41 +216,41 @@ namespace Duplicati.UnitTest // First, run two complete backups followed by a partial backup. We will then set the keep-time // option so that the threshold lies between the first and second backups. DateTime firstBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - firstBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + firstBackupTime = (await c.ListAsync()).Filesets.First().Time; } // Wait before the second backup so that we can more easily define the keep-time threshold // to lie between the first and second backups. Thread.Sleep(5000); DateTime secondBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - secondBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + secondBackupTime = (await c.ListAsync()).Filesets.First().Time; } // Run a partial backup. DateTime thirdBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); - thirdBackupTime = c.List().Filesets.First().Time; + thirdBackupTime = (await c.ListAsync()).Filesets.First().Time; } // Set the keep-time option so that the threshold lies between the first and second backups // and run the delete operation. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { options["keep-time"] = $"{(int)((DateTime.Now - firstBackupTime).TotalSeconds - (secondBackupTime - firstBackupTime).TotalSeconds / 2)}s"; - TestUtils.AssertResults(c.Delete()); + TestUtils.AssertResults(await c.DeleteAsync()); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(secondBackupTime, filesets[1].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup); @@ -260,19 +260,19 @@ namespace Duplicati.UnitTest // Run another partial backup. We will then verify that a full backup is retained // even when all the "recent" backups are partial. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); - DateTime fourthBackupTime = c.List().Filesets.First().Time; + var fourthBackupTime = (await c.ListAsync()).Filesets.First().Time; // Set the keep-time option so that the threshold lies after the most recent full backup // and run the delete operation. options["keep-time"] = "1s"; - TestUtils.AssertResults(c.Delete()); + TestUtils.AssertResults(await c.DeleteAsync()); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(3, filesets.Count); Assert.AreEqual(secondBackupTime, filesets[2].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[2].IsFullBackup); @@ -285,7 +285,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task KeepVersionsRetention() + public async Task KeepVersionsRetentionAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -301,51 +301,51 @@ namespace Duplicati.UnitTest // Run a full backup. DateTime firstBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - firstBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + firstBackupTime = (await c.ListAsync()).Filesets.First().Time; } // Run a partial backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); } // Run a partial backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); } // Run a full backup. DateTime fourthBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - fourthBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + fourthBackupTime = (await c.ListAsync()).Filesets.First().Time; } // Run a partial backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { options["keep-versions"] = "2"; - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); - DateTime fifthBackupTime = c.List().Filesets.First().Time; + DateTime fifthBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since we stopped the operation, files were not deleted automatically - c.Delete(); + await c.DeleteAsync(); // Partial backups that are followed by a full backup can be deleted. - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(3, filesets.Count); Assert.AreEqual(firstBackupTime, filesets[2].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[2].IsFullBackup); @@ -356,14 +356,14 @@ namespace Duplicati.UnitTest } // Run a full backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - DateTime sixthBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + var sixthBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since the last backup was full, we can now expect to have just the 2 most recent full backups. - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(fourthBackupTime, filesets[1].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup); @@ -374,7 +374,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task ListWithoutLocalDb() + public async Task ListWithoutLocalDbAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -390,17 +390,17 @@ namespace Duplicati.UnitTest }; // Run a full backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Run a partial backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup); Assert.AreEqual(BackupType.PARTIAL_BACKUP, filesets[0].IsFullBackup); @@ -409,7 +409,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task RetentionPolicyRetention() + public async Task RetentionPolicyRetentionAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -428,21 +428,21 @@ namespace Duplicati.UnitTest }; DateTime firstBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - firstBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + firstBackupTime = (await c.ListAsync()).Filesets.First().Time; - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(1, filesets.Count); this.ModifySourceFiles(); - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - DateTime secondBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + DateTime secondBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, the only backup in the first time frame // is the initial one. As a result, we should have 2 backups. - filesets = c.List().Filesets.ToList(); + filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(firstBackupTime, filesets[1].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup); @@ -454,20 +454,20 @@ namespace Duplicati.UnitTest Thread.Sleep(new TimeSpan(0, 0, 1, 0)); DateTime thirdBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, _) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, _) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); - thirdBackupTime = c.List().Filesets.First().Time; + thirdBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since we stopped the backup, files were not deleted automatically - c.Delete(); + await c.DeleteAsync(); // Since the most recent backup is not considered in the retention logic, there are no backups in the first time // frame. The original 2 backups have now spilled over to the U:1m specification. Since we keep the first // backup in the interval, we should be left with the first backup, as well as the third partial one. - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(firstBackupTime, filesets[1].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup); @@ -476,16 +476,16 @@ namespace Duplicati.UnitTest } DateTime fourthBackupTime; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - fourthBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + fourthBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, the third backup is the only backup // in the first time frame. There is no further spillover, so we simply add the fourth backup to the // collection of retained backups. - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(3, filesets.Count); Assert.AreEqual(firstBackupTime, filesets[2].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[2].IsFullBackup); @@ -495,14 +495,14 @@ namespace Duplicati.UnitTest Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup); this.ModifySourceFiles(); - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - DateTime fifthBackupTime = c.List().Filesets.First().Time; + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + DateTime fifthBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, we now have two backups in the // first time frame: the third (partial) and fourth (full). Since the first backup in each interval is // kept, we would typically keep just the third backup. However, since we should not discard a full // backup in favor of a partial one, we keep the fourth as well. We also still have the initial backup. - filesets = c.List().Filesets.ToList(); + filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(4, filesets.Count); Assert.AreEqual(firstBackupTime, filesets[3].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[3].IsFullBackup); @@ -517,18 +517,18 @@ namespace Duplicati.UnitTest // Wait so that the next backups fall in the next retention interval. Thread.Sleep(new TimeSpan(0, 0, 1, 0)); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { this.ModifySourceFiles(); - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); - DateTime sixthBackupTime = c.List().Filesets.First().Time; + var sixthBackupTime = (await c.ListAsync()).Filesets.First().Time; // Since the most recent backup is not considered in the retention logic, we now have three backups in the // second time frame: the third (partial), fourth (full), and fifth (full). Since we keep up to the first // full backup in each time frame, we now drop the fifth backup. - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(4, filesets.Count); Assert.AreEqual(firstBackupTime, filesets[3].Time); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[3].IsFullBackup); @@ -543,7 +543,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task StopAfterCurrentFile() + public async Task StopAfterCurrentFileAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -558,18 +558,18 @@ namespace Duplicati.UnitTest }; // Run a complete backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); - Assert.AreEqual(1, c.List().Filesets.Count()); - Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup); + Assert.AreEqual(1, (await c.ListAsync()).Filesets.Count()); + Assert.AreEqual(BackupType.FULL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 0).IsFullBackup); } // Run a partial backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - (var backupResults, var modified) = await this.RunPartialBackup(c).ConfigureAwait(false); + (var backupResults, var modified) = await this.RunPartialBackupAsync(c).ConfigureAwait(false); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(1, backupResults.Warnings.Count()); if (backupResults.ModifiedFiles == 0) @@ -578,19 +578,19 @@ namespace Duplicati.UnitTest Assert.That(backupResults.ModifiedFiles, Is.LessThan(fileSizes.Length), "All files were added, likely the stop was issued too late"); // If we interrupt the backup, the most recent Fileset should be marked as partial. - Assert.AreEqual(2, c.List().Filesets.Count()); - Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup); - Assert.AreEqual(BackupType.PARTIAL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); + Assert.AreEqual(BackupType.FULL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 1).IsFullBackup); + Assert.AreEqual(BackupType.PARTIAL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 0).IsFullBackup); } // Restore files from the partial backup set. Dictionary restoreOptions = new Dictionary(options) { ["restore-path"] = this.RESTOREFOLDER }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - var lastResults = c.List("*"); + var lastResults = await c.ListAsync("*"); var partialVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); Assert.GreaterOrEqual(partialVersionFiles.Length, 1); - c.Restore(partialVersionFiles); + await c.RestoreAsync(partialVersionFiles); foreach (string filepath in partialVersionFiles) { @@ -601,35 +601,35 @@ namespace Duplicati.UnitTest // Recreating the database should preserve the backup types. File.Delete(this.DBFILE); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); - Assert.AreEqual(2, c.List().Filesets.Count()); - Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup); - Assert.AreEqual(BackupType.PARTIAL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); + Assert.AreEqual(BackupType.FULL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 1).IsFullBackup); + Assert.AreEqual(BackupType.PARTIAL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 0).IsFullBackup); } // Run a complete backup. Listing the Filesets should include both full and partial backups. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); - Assert.AreEqual(3, c.List().Filesets.Count()); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); + Assert.AreEqual(3, (await c.ListAsync()).Filesets.Count()); - Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 2).IsFullBackup); - Assert.AreEqual(BackupType.PARTIAL_BACKUP, c.List().Filesets.Single(x => x.Version == 1).IsFullBackup); - Assert.AreEqual(BackupType.FULL_BACKUP, c.List().Filesets.Single(x => x.Version == 0).IsFullBackup); + Assert.AreEqual(BackupType.FULL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 2).IsFullBackup); + Assert.AreEqual(BackupType.PARTIAL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 1).IsFullBackup); + Assert.AreEqual(BackupType.FULL_BACKUP, (await c.ListAsync()).Filesets.Single(x => x.Version == 0).IsFullBackup); } // Restore files from the full backup set. restoreOptions["overwrite"] = "true"; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - var lastResults = c.List("*"); + var lastResults = await c.ListAsync("*"); var fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); Assert.AreEqual(this.fileSizes.Length, fullVersionFiles.Length); - TestUtils.AssertResults(c.Restore(fullVersionFiles)); + TestUtils.AssertResults(await c.RestoreAsync(fullVersionFiles)); foreach (string filepath in fullVersionFiles) { @@ -641,7 +641,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task StopNow() + public async Task StopNowAsync() { #if !DEBUG Assert.Ignore("This test requires DEBUG to be defined"); @@ -657,18 +657,18 @@ namespace Duplicati.UnitTest }; // Run a complete backup. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(1, filesets.Count); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup); } // Interrupt a backup with "abort". this.ModifySourceFiles(); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { var startedTcs = new TaskCompletionSource(); c.OnOperationStarted += results => @@ -680,12 +680,12 @@ namespace Duplicati.UnitTest }; }; - Task backupTask = Task.Run(() => c.Backup([DATAFOLDER])); + Task backupTask = c.BackupAsync([DATAFOLDER]); Task completedTask = await Task.WhenAny(startedTcs.Task, Task.Delay(5000)); if (completedTask == startedTcs.Task) { - c.Abort(); + await c.AbortAsync(); try { await backupTask.ConfigureAwait(false); @@ -703,11 +703,11 @@ namespace Duplicati.UnitTest } // The next backup should proceed without issues. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); - List filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.AreEqual(2, filesets.Count); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[1].IsFullBackup); Assert.AreEqual(BackupType.FULL_BACKUP, filesets[0].IsFullBackup); @@ -715,13 +715,13 @@ namespace Duplicati.UnitTest // Restore from the backup that followed the interruption. Dictionary restoreOptions = new Dictionary(options) { ["restore-path"] = this.RESTOREFOLDER }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - var lastResults = c.List("*"); + var lastResults = await c.ListAsync("*"); var fullVersionFiles = lastResults.Files.Select(x => x.Path).Where(x => !Utility.IsFolder(x, File.GetAttributes)).ToArray(); Assert.AreEqual(this.fileSizes.Length, fullVersionFiles.Length); - TestUtils.AssertResults(c.Restore(fullVersionFiles)); + TestUtils.AssertResults(await c.RestoreAsync(fullVersionFiles)); foreach (string filepath in fullVersionFiles) { string filename = Path.GetFileName(filepath); @@ -732,7 +732,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public async Task TestUploadExceptionWithNoRetries() + public async Task TestUploadExceptionWithNoRetriesAsync() { var testopts = TestOptions; testopts["number-of-retries"] = "0"; @@ -740,7 +740,7 @@ namespace Duplicati.UnitTest // Make a base backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Ensure that the target folder only has a single dlist file Assert.AreEqual(1, Directory.EnumerateFiles(TARGETFOLDER, "*.dlist.*").Count(), "There should be only one dlist file in the target folder"); @@ -784,7 +784,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller(failtarget, testopts, null)) - Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + Assert.ThrowsAsync(() => c.BackupAsync(new string[] { DATAFOLDER })); Assert.That(secondUploadStarted, Is.True, "Second upload was not started"); Assert.That(secondUploadCompleted, Is.True, "Second upload was not started"); @@ -796,13 +796,13 @@ namespace Duplicati.UnitTest // Create a regular backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Verify that all is in order using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) try { - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); } catch (TestUtils.TestVerificationException e) { @@ -831,12 +831,12 @@ namespace Duplicati.UnitTest testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that we have 3 versions using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(3, listResults.Filesets.Count()); } @@ -848,7 +848,7 @@ namespace Duplicati.UnitTest [TestCase(false, false)] [TestCase(true, true)] [TestCase(false, true)] - public void TestUploadExceptionOnFirstDlistWithRepair(bool before, bool failOnLastDblock) + public async Task TestUploadExceptionOnFirstDlistWithRepairAsync(bool before, bool failOnLastDblock) { var testopts = TestOptions; testopts["number-of-retries"] = "0"; @@ -894,7 +894,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller(failtarget, testopts, null)) - Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + Assert.ThrowsAsync(() => c.BackupAsync(new string[] { DATAFOLDER })); Assert.That(failed, Is.True, "Failed to fail the upload"); @@ -903,11 +903,11 @@ namespace Duplicati.UnitTest // Issue repair using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Verify that all is in order using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // If we fail on the dlist after the upload, it is promoted to a regular backup var expectedFilesets = before || failOnLastDblock ? 2 : 1; @@ -915,9 +915,9 @@ namespace Duplicati.UnitTest // Make a new backup, should continue as normal using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); - Assert.AreEqual(expectedFilesets, c.List().Filesets.Count()); + Assert.AreEqual(expectedFilesets, (await c.ListAsync()).Filesets.Count()); } // Test that we can recreate @@ -928,12 +928,12 @@ namespace Duplicati.UnitTest testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that we have the correct versions using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(expectedFilesets, listResults.Filesets.Count()); } @@ -945,7 +945,7 @@ namespace Duplicati.UnitTest [TestCase(false, false)] [TestCase(true, true)] [TestCase(false, true)] - public void TestUploadExceptionOnFirstDlist(bool before, bool failOnLastDblock) + public async Task TestUploadExceptionOnFirstDlistAsync(bool before, bool failOnLastDblock) { var testopts = TestOptions; testopts["number-of-retries"] = "0"; @@ -991,7 +991,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller(failtarget, testopts, null)) - Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + Assert.ThrowsAsync(() => c.BackupAsync(new string[] { DATAFOLDER })); Assert.That(failed, Is.True, "Failed to fail the upload"); @@ -1004,14 +1004,14 @@ namespace Duplicati.UnitTest // Make a new backup, should continue as normal using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); - Assert.AreEqual(expectedFilesets, c.List().Filesets.Count()); + Assert.AreEqual(expectedFilesets, (await c.ListAsync()).Filesets.Count()); } // Verify that all is in order using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Test that we can recreate var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); @@ -1021,12 +1021,12 @@ namespace Duplicati.UnitTest testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that we have 2 versions using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(expectedFilesets, listResults.Filesets.Count()); } @@ -1036,7 +1036,7 @@ namespace Duplicati.UnitTest [Category("Disruption")] [TestCase(true)] [TestCase(false)] - public void TestRepairWithNoDlist(bool before) + public async Task TestRepairWithNoDlistAsync(bool before) { var testopts = TestOptions; testopts["number-of-retries"] = "0"; @@ -1059,22 +1059,21 @@ namespace Duplicati.UnitTest }; using (var c = new Controller(failtarget, testopts, null)) - Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + Assert.ThrowsAsync(() => c.BackupAsync(new string[] { DATAFOLDER })); // Test that we can repair using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that we have 1 version using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(1, listResults.Filesets.Count()); } } - [Test] [Category("Disruption")] [TestCase(true, true, 3, false)] @@ -1085,7 +1084,7 @@ namespace Duplicati.UnitTest [TestCase(false, true, 3, true)] [TestCase(false, false, 3, true)] [TestCase(true, false, 3, true)] - public void TestMultiExceptionOnDlist(bool before, bool modifyInBetween, int runs, bool withBase) + public async Task TestMultiExceptionOnDlistAsync(bool before, bool modifyInBetween, int runs, bool withBase) { var testopts = TestOptions; testopts["number-of-retries"] = "0"; @@ -1121,7 +1120,7 @@ namespace Duplicati.UnitTest { // Make a regular backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); } @@ -1149,7 +1148,7 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, testopts, null)) try { - c.Backup(new string[] { DATAFOLDER }); + await c.BackupAsync(new string[] { DATAFOLDER }); } catch (DeterministicErrorBackend.DeterministicErrorBackendException) { @@ -1165,14 +1164,14 @@ namespace Duplicati.UnitTest // Make a new backup, should continue as normal using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); - Assert.AreEqual(expectedFilesets, c.List().Filesets.Count()); + Assert.AreEqual(expectedFilesets, (await c.ListAsync()).Filesets.Count()); } // Verify that all is in order using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Test that we can recreate var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); @@ -1182,12 +1181,12 @@ namespace Duplicati.UnitTest testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that we have 2 versions using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(expectedFilesets, listResults.Filesets.Count()); } @@ -1224,27 +1223,27 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption")] - public void TestDblockUploadWithOperationCancellation() + public async Task TestDblockUploadWithOperationCancellationAsync() { - TestUploadWithOperationCancellation(".dblock."); + await TestUploadWithOperationCancellationAsync(".dblock."); } [Test] [Category("Disruption")] - public void TestDindexUploadWithOperationCancellation() + public async Task TestDindexUploadWithOperationCancellationAsync() { - TestUploadWithOperationCancellation(".dindex."); + await TestUploadWithOperationCancellationAsync(".dindex."); } [Test] [Category("Disruption")] - public void TestDlistUploadWithOperationCancellation() + public async Task TestDlistUploadWithOperationCancellationAsync() { - TestUploadWithOperationCancellation(".dlist."); + await TestUploadWithOperationCancellationAsync(".dlist."); } - private void TestUploadWithOperationCancellation(string filefragment) + private async Task TestUploadWithOperationCancellationAsync(string filefragment) { var testopts = TestOptions; testopts["number-of-retries"] = "1"; @@ -1252,7 +1251,7 @@ namespace Duplicati.UnitTest // Make a base backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Make a new backup that fails uploading a dblock file ModifySourceFiles(); @@ -1279,12 +1278,12 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, testopts, null)) { var sink = new LogSink(); - c.AppendSink(sink); - var res = c.Backup(new string[] { DATAFOLDER }); + await c.AppendSinkAsync(sink); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); if (uploadCount == 0) Assert.Fail("Upload count was not incremented"); - Assert.AreEqual(2, c.List().Filesets.Count()); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); Assert.AreEqual(2, Directory.GetFiles(TARGETFOLDER, "*.dlist.*", SearchOption.TopDirectoryOnly).Count()); Assert.AreEqual(7 * 2 + 1, sink.CompletedFiles.Count); // 7 completed (dblock + dindex) + 1 dlist Assert.AreEqual(7 * 2 + 2, sink.StartedFiles.Count); // 1 retry @@ -1299,12 +1298,12 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, testopts, null)) { var sink = new LogSink(); - c.AppendSink(sink); - var res = c.Backup(new string[] { DATAFOLDER }); + await c.AppendSinkAsync(sink); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); if (uploadCount == 0) Assert.Fail("Upload count was not incremented"); - Assert.AreEqual(3, c.List().Filesets.Count()); + Assert.AreEqual(3, (await c.ListAsync()).Filesets.Count()); Assert.AreEqual(3, Directory.GetFiles(TARGETFOLDER, "*.dlist.*", SearchOption.TopDirectoryOnly).Count()); Assert.AreEqual(7 * 2 + 1, sink.CompletedFiles.Count); // 7 completed (dblock + dindex) + 1 dlist Assert.AreEqual(7 * 2 + 2, sink.StartedFiles.Count); // 1 retry @@ -1315,11 +1314,11 @@ namespace Duplicati.UnitTest ModifySourceFiles(); // Create a regular backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Verify that all is in order using (var c = new Controller("file://" + TARGETFOLDER, testopts.Expand(new { full_remote_verification = true }), null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Test that we can recreate var recreatedDatabaseFile = Path.Combine(BASEFOLDER, "recreated-database.sqlite"); @@ -1329,12 +1328,12 @@ namespace Duplicati.UnitTest testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that we have 3 versions using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listResults = c.List(); + var listResults = await c.ListAsync(); TestUtils.AssertResults(listResults); Assert.AreEqual(4, listResults.Filesets.Count()); } diff --git a/Duplicati/UnitTest/DryRunTests.cs b/Duplicati/UnitTest/DryRunTests.cs index 59a65c515..8f269021a 100644 --- a/Duplicati/UnitTest/DryRunTests.cs +++ b/Duplicati/UnitTest/DryRunTests.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; using System.Threading.Tasks; using Duplicati.Library.Main; using NUnit.Framework; @@ -12,7 +11,7 @@ namespace Duplicati.UnitTest public class DryRunTests : BasicSetupHelper { [Test] - public void TestBackupDryRunDoesNotUpload() + public async Task TestBackupDryRunDoesNotUploadAsync() { var sourceFolder = Path.Combine(DATAFOLDER, "source"); var backendFolder = Path.Combine(TARGETFOLDER, "backend"); @@ -29,7 +28,7 @@ namespace Duplicati.UnitTest }; using (var controller = new Controller($"file://{backendFolder}", options, null)) - controller.Backup(new[] { sourceFolder }); + await controller.BackupAsync(new[] { sourceFolder }); // Verify no files were uploaded var uploadedFiles = Directory.GetFiles(backendFolder); @@ -37,7 +36,7 @@ namespace Duplicati.UnitTest } [Test] - public void TestDeleteAllRemoteFilesDryRunDoesNotDelete() + public async Task TestDeleteAllRemoteFilesDryRunDoesNotDeleteAsync() { var sourceFolder = Path.Combine(DATAFOLDER, "source"); var backendFolder = Path.Combine(TARGETFOLDER, "backend"); @@ -49,11 +48,8 @@ namespace Duplicati.UnitTest File.WriteAllText(Path.Combine(sourceFolder, "test.txt"), "test content"); using (var controller = new Controller($"file://{backendFolder}", this.TestOptions, null)) - { - controller.Backup(new[] { sourceFolder }); - } + await controller.BackupAsync(new[] { sourceFolder }); - // Count files before dry-run delete var filesBefore = Directory.GetFiles(backendFolder).Length; Assert.Greater(filesBefore, 0, "Should have files in backend after backup"); @@ -64,9 +60,7 @@ namespace Duplicati.UnitTest }; using (var controller = new Controller($"file://{backendFolder}", options, null)) - { - controller.DeleteAllRemoteFiles(); - } + await controller.DeleteAllRemoteFilesAsync(); // Verify files still exist var filesAfter = Directory.GetFiles(backendFolder).Length; @@ -74,7 +68,7 @@ namespace Duplicati.UnitTest } [Test] - public void TestVerifyLocalListDryRunDoesNotDelete() + public async Task TestVerifyLocalListDryRunDoesNotDeleteAsync() { var sourceFolder = Path.Combine(DATAFOLDER, "source"); var backendFolder = Path.Combine(TARGETFOLDER, "backend"); @@ -86,9 +80,7 @@ namespace Duplicati.UnitTest File.WriteAllText(Path.Combine(sourceFolder, "test.txt"), "test content"); using (var controller = new Controller($"file://{backendFolder}", this.TestOptions, null)) - { - controller.Backup(new[] { sourceFolder }); - } + await controller.BackupAsync(new[] { sourceFolder }); var filesBefore = Directory.GetFiles(backendFolder).Length; Assert.Greater(filesBefore, 0); @@ -96,11 +88,11 @@ namespace Duplicati.UnitTest // Manually mark some files as "Uploading" in the database using (var connection = new Microsoft.Data.Sqlite.SqliteConnection($"Data Source={DBFILE};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); using (var command = connection.CreateCommand()) { command.CommandText = "UPDATE RemoteVolume SET State = \"Uploading\" WHERE Type = \"Files\""; - command.ExecuteNonQuery(); + await command.ExecuteNonQueryAsync(); } } @@ -111,9 +103,7 @@ namespace Duplicati.UnitTest }; using (var controller = new Controller($"file://{backendFolder}", options, null)) - { - controller.Backup(new[] { sourceFolder }); - } + await controller.BackupAsync(new[] { sourceFolder }); // Verify that the files were not deleted var filesAfter = Directory.GetFiles(backendFolder).Length; @@ -121,7 +111,7 @@ namespace Duplicati.UnitTest } [Test] - public void TestInterruptedBackupRecoveryDryRunDoesNotUpload() + public async Task TestInterruptedBackupRecoveryDryRunDoesNotUploadAsync() { var sourceFolder = Path.Combine(DATAFOLDER, "source"); var backendFolder = Path.Combine(TARGETFOLDER, "backend"); @@ -137,13 +127,13 @@ namespace Duplicati.UnitTest // Start a backup and interrupt it using (var controller = new Controller($"file://{backendFolder}", this.TestOptions, null)) { - var backupTask = Task.Run(() => controller.Backup(new[] { sourceFolder })); - Thread.Sleep(2000); // Wait 2 seconds - controller.Stop(); + var backupTask = controller.BackupAsync(new[] { sourceFolder }); + await Task.Delay(2000); // Wait 2 seconds + await controller.StopAsync(); try { - backupTask.Wait(); + await backupTask; } catch (AggregateException) { @@ -160,9 +150,7 @@ namespace Duplicati.UnitTest var filesBefore = Directory.GetFiles(backendFolder).Length; using (var controller = new Controller($"file://{backendFolder}", options, null)) - { - controller.Backup(new[] { sourceFolder }); - } + await controller.BackupAsync(new[] { sourceFolder }); // Verify no new files were uploaded var filesAfter = Directory.GetFiles(backendFolder).Length; diff --git a/Duplicati/UnitTest/DuplicatePathTests.cs b/Duplicati/UnitTest/DuplicatePathTests.cs index f241f7b49..277288347 100644 --- a/Duplicati/UnitTest/DuplicatePathTests.cs +++ b/Duplicati/UnitTest/DuplicatePathTests.cs @@ -41,7 +41,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Targeted")] - public async Task TestRemoveDuplicatePathsFromFileset() + public async Task TestRemoveDuplicatePathsFromFilesetAsync() { // Create test files var testFile = Path.Combine(DATAFOLDER, "duplicate.txt"); @@ -50,7 +50,7 @@ namespace Duplicati.UnitTest // Run initial backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var results = c.Backup([DATAFOLDER]); + var results = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, results.Errors.Count(), "Initial backup should succeed"); } @@ -165,7 +165,7 @@ namespace Duplicati.UnitTest // Now run the repair command which should fix the duplicates using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var results = c.Repair(); + var results = await c.RepairAsync(); Assert.AreEqual(0, results.Errors.Count(), "Repair should succeed"); } @@ -195,7 +195,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Targeted")] - public void TestChangedFilesDoesNotCreateDuplicates() + public async Task TestChangedFilesDoesNotCreateDuplicatesAsync() { // Setup: Create test files var testFile1 = Path.Combine(DATAFOLDER, "file1.txt"); @@ -207,7 +207,7 @@ namespace Duplicati.UnitTest // Step 1: Do initial backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); } @@ -222,7 +222,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, changedFilesOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); } @@ -230,7 +230,7 @@ namespace Duplicati.UnitTest var dbPath = DBFILE; using (var connection = new SqliteConnection($"Data Source={dbPath};Pooling=False")) { - connection.Open(); + await connection.OpenAsync(); using (var cmd = connection.CreateCommand()) { cmd.CommandText = @" @@ -242,7 +242,7 @@ namespace Duplicati.UnitTest HAVING cnt > 1 ) "; - var duplicateCount = (long)(cmd.ExecuteScalar() ?? 0); + var duplicateCount = (long)((await cmd.ExecuteScalarAsync()) ?? 0); Assert.AreEqual(0, duplicateCount, "Database should have no duplicate paths"); } } @@ -253,7 +253,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Targeted")] - public async Task TestRepairFixesDuplicatesAcrossMultipleFilesets() + public async Task TestRepairFixesDuplicatesAcrossMultipleFilesetsAsync() { // Create test files var testFile = Path.Combine(DATAFOLDER, "test.txt"); @@ -265,7 +265,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, $"content v{i + 1}"); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var results = c.Backup([DATAFOLDER]); + var results = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, results.Errors.Count(), $"Backup {i + 1} should succeed"); } } @@ -459,7 +459,7 @@ namespace Duplicati.UnitTest // Run repair using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var results = c.Repair(); + var results = await c.RepairAsync(); Assert.AreEqual(0, results.Errors.Count(), "Repair should succeed"); } @@ -489,7 +489,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Targeted")] - public void TestNormalBackupHasNoDuplicates() + public async Task TestNormalBackupHasNoDuplicatesAsync() { // Create test files var testFile1 = Path.Combine(DATAFOLDER, "file1.txt"); @@ -503,7 +503,7 @@ namespace Duplicati.UnitTest // Run backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var results = c.Backup([DATAFOLDER]); + var results = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, results.Errors.Count()); } @@ -511,7 +511,7 @@ namespace Duplicati.UnitTest var dbPath = DBFILE; using (var connection = new SqliteConnection($"Data Source={dbPath};Pooling=False")) { - connection.Open(); + await connection.OpenAsync(); using (var cmd = connection.CreateCommand()) { cmd.CommandText = @" @@ -523,7 +523,7 @@ namespace Duplicati.UnitTest HAVING cnt > 1 ) "; - var duplicateCount = (long)(cmd.ExecuteScalar() ?? 0); + var duplicateCount = (long)((await cmd.ExecuteScalarAsync()) ?? 0); Assert.AreEqual(0, duplicateCount, "Normal backup should not create duplicates"); } } diff --git a/Duplicati/UnitTest/EmptyFileTests.cs b/Duplicati/UnitTest/EmptyFileTests.cs index fd633ee09..96569c3cd 100644 --- a/Duplicati/UnitTest/EmptyFileTests.cs +++ b/Duplicati/UnitTest/EmptyFileTests.cs @@ -20,11 +20,9 @@ // DEALINGS IN THE SOFTWARE. using System.IO; -using System.Collections.Generic; using Duplicati.Library.Main; using Duplicati.Library.Main.Database; using Duplicati.Library.Interface; -using Duplicati.Library.SQLiteHelper; using NUnit.Framework; using System.Threading; using System.Threading.Tasks; @@ -43,7 +41,7 @@ public class EmptyFileTests : BasicSetupHelper /// [Test] [Category("EmptyFile")] - public async Task EmptyFileAfterDatabaseRecreate() + public async Task EmptyFileAfterDatabaseRecreateAsync() { var testopts = TestOptions.Expand(new { no_encryption = true }); @@ -56,11 +54,11 @@ public class EmptyFileTests : BasicSetupHelper // Run initial backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Verify the backup works using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Delete the local database to simulate database loss File.Delete(DBFILE); @@ -73,7 +71,7 @@ public class EmptyFileTests : BasicSetupHelper testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Add a new file to force the backup to process changes // This triggers the code path where the database inconsistency is detected @@ -85,7 +83,7 @@ public class EmptyFileTests : BasicSetupHelper { try { - await db.VerifyConsistency(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); + await db.VerifyConsistencyAsync(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); } catch (DatabaseInconsistencyException ex) { @@ -95,11 +93,11 @@ public class EmptyFileTests : BasicSetupHelper // Run another backup after database recreation using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Verify the backup is still consistent using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); } /// @@ -107,7 +105,7 @@ public class EmptyFileTests : BasicSetupHelper /// [Test] [Category("EmptyFile")] - public async Task OnlyEmptyFileAfterDatabaseRecreate() + public async Task OnlyEmptyFileAfterDatabaseRecreateAsync() { var testopts = TestOptions.Expand(new { no_encryption = true }); @@ -117,7 +115,7 @@ public class EmptyFileTests : BasicSetupHelper // Run initial backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Delete the local database File.Delete(DBFILE); @@ -130,7 +128,7 @@ public class EmptyFileTests : BasicSetupHelper testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Add a new file to force the backup to process changes // This triggers the code path where the database inconsistency is detected @@ -138,11 +136,11 @@ public class EmptyFileTests : BasicSetupHelper // Run another backup after database recreation using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Verify the backup is consistent using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); } /// @@ -151,7 +149,7 @@ public class EmptyFileTests : BasicSetupHelper /// [Test] [Category("EmptyFile")] - public async Task EmptyFileWithSameHashAlgorithm() + public async Task EmptyFileWithSameHashAlgorithmAsync() { // Use same hash algorithm for block and file - this triggers different code path var testopts = TestOptions.Expand(new @@ -172,11 +170,11 @@ public class EmptyFileTests : BasicSetupHelper // Run initial backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Verify the backup works using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Delete the local database File.Delete(DBFILE); @@ -189,7 +187,7 @@ public class EmptyFileTests : BasicSetupHelper testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Add a new file to force the backup to process changes // This triggers the code path where the database inconsistency is detected @@ -201,7 +199,7 @@ public class EmptyFileTests : BasicSetupHelper { try { - await db.VerifyConsistency(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); + await db.VerifyConsistencyAsync(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); } catch (DatabaseInconsistencyException ex) { @@ -211,11 +209,11 @@ public class EmptyFileTests : BasicSetupHelper // Run another backup after database recreation using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Verify the backup is still consistent using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); } /// @@ -223,7 +221,7 @@ public class EmptyFileTests : BasicSetupHelper /// [Test] [Category("EmptyFile")] - public void EmptyFileReportedScenario() + public async Task EmptyFileReportedScenarioAsync() { var testopts = TestOptions.Expand(new { no_encryption = true }); @@ -233,7 +231,7 @@ public class EmptyFileTests : BasicSetupHelper // Run initial backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Delete the local database (simulating database loss) File.Delete(DBFILE); @@ -246,7 +244,7 @@ public class EmptyFileTests : BasicSetupHelper testopts["dbpath"] = recreatedDatabaseFile; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Add a new file to force the backup to process changes // This triggers the code path where the database inconsistency is detected @@ -256,6 +254,6 @@ public class EmptyFileTests : BasicSetupHelper // If the bug is present, this will throw DatabaseInconsistencyException // with message: "Found inconsistency in the following files while validating database" using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); } } diff --git a/Duplicati/UnitTest/EmptyMetadataTests.cs b/Duplicati/UnitTest/EmptyMetadataTests.cs index fc52487d7..913a757c0 100644 --- a/Duplicati/UnitTest/EmptyMetadataTests.cs +++ b/Duplicati/UnitTest/EmptyMetadataTests.cs @@ -21,7 +21,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.IO; using Duplicati.Library.Main; using Duplicati.Library.Main.Database; @@ -38,7 +37,7 @@ public class EmptyMetadataTests : BasicSetupHelper { [Test] [Category("Targeted")] - public async Task ReplaceMissingMetadataRestoresConsistency() + public async Task ReplaceMissingMetadataRestoresConsistencyAsync() { var testopts = TestOptions.Expand(new { no_encryption = true }); @@ -47,12 +46,12 @@ public class EmptyMetadataTests : BasicSetupHelper File.WriteAllText(Path.Combine(DATAFOLDER, "folder", "file.txt"), "data"); using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); long metaBlocksetId; long filesetId; - using (var db = SQLiteLoader.LoadConnection(DBFILE)) + using (var db = await SQLiteLoader.LoadConnectionAsync(DBFILE)) using (var cmd = db.CreateCommand()) { // Find metadata blockset ID and fileset ID for the backed up folder @@ -63,9 +62,9 @@ public class EmptyMetadataTests : BasicSetupHelper WHERE F.""Path"" LIKE @Path AND F.""BlocksetID"" = {LocalDatabase.FOLDER_BLOCKSET_ID} ORDER BY FE.""FilesetID"" DESC LIMIT 1"); cmd.SetParameterValue("@Path", "%folder%"); - using (var rd = cmd.ExecuteReader()) + using (var rd = await cmd.ExecuteReaderAsync()) { - Assert.That(rd.Read(), Is.True, "Folder entry not found"); + Assert.That(await rd.ReadAsync(), Is.True, "Folder entry not found"); metaBlocksetId = rd.ConvertValueToInt64(0); filesetId = rd.ConvertValueToInt64(1); } @@ -73,38 +72,38 @@ public class EmptyMetadataTests : BasicSetupHelper // Remove metadata blockset entries to simulate missing metadata cmd.SetCommandAndParameters("DELETE FROM \"BlocksetEntry\" WHERE \"BlocksetID\" = @Id"); cmd.SetParameterValue("@Id", metaBlocksetId); - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Remove the now orphaned blockset record cmd.SetCommandAndParameters("DELETE FROM \"Blockset\" WHERE \"ID\" = @Id"); cmd.SetParameterValue("@Id", metaBlocksetId); - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } var opts = new Options(testopts); var emptyMeta = Duplicati.Library.Main.Utility.WrapMetadata(new Dictionary(), opts); - Assert.Throws(() => + Assert.ThrowsAsync(async () => { - using var db = LocalDatabase.CreateLocalDatabaseAsync(DBFILE, "verify", true, null, CancellationToken.None).Await(); - db.VerifyConsistency(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None).Await(); + using var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, "verify", true, null, CancellationToken.None); + await db.VerifyConsistencyAsync(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); }); await using (var db = await LocalListBrokenFilesDatabase.CreateAsync(DBFILE, null, CancellationToken.None).ConfigureAwait(false)) { var blockVolumeIds = Array.Empty(); - var emptyId = await db.GetEmptyMetadataBlocksetId(blockVolumeIds, emptyMeta.FileHash, emptyMeta.Blob.Length, CancellationToken.None); + var emptyId = await db.GetEmptyMetadataBlocksetIdAsync(blockVolumeIds, emptyMeta.FileHash, emptyMeta.Blob.Length, CancellationToken.None); Assert.That(emptyId, Is.GreaterThanOrEqualTo(0), "Empty metadata blockset not found"); - var replaced = await db.ReplaceMetadata(filesetId, emptyId, CancellationToken.None); + var replaced = await db.ReplaceMetadataAsync(filesetId, emptyId, CancellationToken.None); Assert.That(replaced, Is.GreaterThan(0), "No metadata rows replaced"); await db.Transaction.CommitAsync(CancellationToken.None).ConfigureAwait(false); } await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, "verify", true, null, CancellationToken.None)) - await db.VerifyConsistency(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); + await db.VerifyConsistencyAsync(opts.Blocksize, opts.BlockhashSize, true, CancellationToken.None); } /// @@ -113,10 +112,10 @@ public class EmptyMetadataTests : BasicSetupHelper /// [Test] [Category("Database")] - public async Task GetEmptyMetadataBlocksetId_WithLargeInput_UsesTemporaryTable() + public async Task GetEmptyMetadataBlocksetId_WithLargeInput_UsesTemporaryTable_Async() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); // Use DatabaseUpgrader to create the schema from embedded resources DatabaseUpgrader.UpgradeDatabase(db, dbfile, typeof(DatabaseSchemaMarker)); @@ -125,7 +124,7 @@ public class EmptyMetadataTests : BasicSetupHelper // Insert an operation record (required for LocalDatabase initialization) cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES ('Test', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Create 150 block volume IDs (exceeds CHUNK_SIZE of 128) to trigger temporary table path var blockVolumeIds = new List(); @@ -137,7 +136,7 @@ public class EmptyMetadataTests : BasicSetupHelper cmd.CommandText = $@" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES ({i + 1}, 1, 'block-volume-{i}.zip', 'Blocks', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Insert a blockset entry that represents empty metadata (hash of empty data) @@ -145,17 +144,17 @@ public class EmptyMetadataTests : BasicSetupHelper cmd.CommandText = @" INSERT INTO ""Blockset"" (""ID"", ""FullHash"", ""Length"") VALUES (1, 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Close the connection so LocalDatabase can open it - db.Close(); + await db.CloseAsync(); // Create LocalListBrokenFilesDatabase instance (which inherits GetEmptyMetadataBlocksetId from LocalDatabase) await using var localDb = await LocalListBrokenFilesDatabase.CreateAsync(dbfile, null, CancellationToken.None).ConfigureAwait(false); // Act: Call GetEmptyMetadataBlocksetId with 150 block volume IDs // This should trigger the temporary table code path - var emptyId = await localDb.GetEmptyMetadataBlocksetId(blockVolumeIds, "da39a3ee5e6b4b0d3255bfef95601890afd80709", 0, CancellationToken.None); + var emptyId = await localDb.GetEmptyMetadataBlocksetIdAsync(blockVolumeIds, "da39a3ee5e6b4b0d3255bfef95601890afd80709", 0, CancellationToken.None); // Assert: Should find the empty metadata blockset (ID = 1) Assert.That(emptyId, Is.EqualTo(1)); diff --git a/Duplicati/UnitTest/FilterTest.cs b/Duplicati/UnitTest/FilterTest.cs index c26f83b92..c55d0d450 100644 --- a/Duplicati/UnitTest/FilterTest.cs +++ b/Duplicati/UnitTest/FilterTest.cs @@ -28,6 +28,7 @@ using Duplicati.Library.Interface; using Duplicati.Library.Utility; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -35,7 +36,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Filter")] - public void TestEmptyFolderExclude() + public async Task TestEmptyFolderExcludeAsync() { var source = DATAFOLDER; // Top level folder with no contents @@ -68,7 +69,7 @@ namespace Duplicati.UnitTest // Create a fileset with all data present using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -76,7 +77,7 @@ namespace Duplicati.UnitTest // Check that we have 4 files and 7 folders using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); var folders = r.Files.Count(x => x.Path.EndsWith(Util.DirectorySeparatorString, StringComparison.Ordinal)); @@ -93,7 +94,7 @@ namespace Duplicati.UnitTest testopts["ignore-filenames"] = "exclude.me"; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -101,7 +102,7 @@ namespace Duplicati.UnitTest // Check that we have 2 files and 6 folders after excluding the "excludefile" folder using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); var folders = r.Files.Count(x => x.Path.EndsWith(Util.DirectorySeparatorString, StringComparison.Ordinal)); @@ -118,7 +119,7 @@ namespace Duplicati.UnitTest testopts["exclude-empty-folders"] = "true"; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -126,7 +127,7 @@ namespace Duplicati.UnitTest // Check that the two empty folders are now removed using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); var folders = r.Files.Count(x => x.Path.EndsWith(Util.DirectorySeparatorString, StringComparison.Ordinal)); @@ -143,7 +144,7 @@ namespace Duplicati.UnitTest var excludefilter = new Library.Utility.FilterExpression($"*{System.IO.Path.DirectorySeparatorChar}myfile.txt", false); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }, excludefilter); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }, excludefilter); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -151,7 +152,7 @@ namespace Duplicati.UnitTest // Check that the empty folder is now removed using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); var folders = r.Files.Count(x => x.Path.EndsWith(Util.DirectorySeparatorString, StringComparison.Ordinal)); @@ -168,7 +169,7 @@ namespace Duplicati.UnitTest File.Delete(Path.Combine(source, "toplevel", "normal", "standard.txt")); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }, excludefilter); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }, excludefilter); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -176,7 +177,7 @@ namespace Duplicati.UnitTest // Check we now have only one folder and no files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); var folders = r.Files.Count(x => x.Path.EndsWith(Util.DirectorySeparatorString, StringComparison.Ordinal)); diff --git a/Duplicati/UnitTest/FolderStatusServiceTests.cs b/Duplicati/UnitTest/FolderStatusServiceTests.cs index cb923a3d1..0cd5f5b31 100644 --- a/Duplicati/UnitTest/FolderStatusServiceTests.cs +++ b/Duplicati/UnitTest/FolderStatusServiceTests.cs @@ -32,6 +32,7 @@ using Duplicati.WebserverCore.Dto; using Duplicati.Library.Interface; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest; @@ -61,7 +62,7 @@ public class FolderStatusServiceTests public void Resume() { } public void Pause() { } public IList> GetQueueWithIds() => new List>(); - public IBasicResults? RunImmediately(IQueuedTask task) => null; + public Task RunImmediatelyAsync(IQueuedTask task) => Task.FromResult(null); } /// @@ -77,11 +78,11 @@ public class FolderStatusServiceTests public DateTime? TaskStarted { get; set; } public DateTime? TaskFinished { get; set; } - public void UpdateThrottleSpeeds(string? uploadSpeed, string? downloadSpeed) { } - public void Stop() { } - public void Abort() { } - public void Pause(bool alsoTransfers) { } - public void Resume() { } + public Task UpdateThrottleSpeedsAsync(string? uploadSpeed, string? downloadSpeed) => Task.CompletedTask; + public Task StopAsync() => Task.CompletedTask; + public Task AbortAsync() => Task.CompletedTask; + public Task PauseAsync(bool alsoTransfers) => Task.CompletedTask; + public Task ResumeAsync() => Task.CompletedTask; } /// diff --git a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs index 4bcd74bfb..cfd37e9be 100644 --- a/Duplicati/UnitTest/GeneralBlackBoxTesting.cs +++ b/Duplicati/UnitTest/GeneralBlackBoxTesting.cs @@ -22,8 +22,8 @@ using NUnit.Framework; using System.Linq; using System.IO; +using System.Threading.Tasks; using System.Collections.Generic; -using Duplicati.Library.Utility; namespace Duplicati.UnitTest { @@ -66,10 +66,10 @@ namespace Duplicati.UnitTest [OneTimeSetUp] - public void OneTimeSetUp() + public async Task OneTimeSetUpAsync() { this.OneTimeTearDown(); - CommandLineOperationsTests.DownloadS3FileIfNewerAsync(this.zipFilepath, $"{CommandLineOperationsTests.S3_URL}{this.zipFilename}").Await(); + await CommandLineOperationsTests.DownloadS3FileIfNewerAsync(this.zipFilepath, $"{CommandLineOperationsTests.S3_URL}{this.zipFilename}"); System.IO.Compression.ZipFile.ExtractToDirectory(this.zipFilepath, BASEFOLDER); } @@ -85,26 +85,26 @@ namespace Duplicati.UnitTest [Test] [Category("SVNData")] [TestCase("zip")] - public void TestWithSVNShort(string compression) + public async Task TestWithSVNShortAsync(string compression) { var opts = TestOptions; opts["compression-module"] = compression; - SVNCheckoutTest.RunTest(TestFolders.Take(5).ToArray(), opts, TestTarget); + await SVNCheckoutTest.RunTestAsync(TestFolders.Take(5).ToArray(), opts, TestTarget); } [Test] [Category("SVNDataLong")] [TestCase("zip")] - public void TestWithSVNLong(string compression) + public async Task TestWithSVNLongAsync(string compression) { var opts = TestOptions; opts["compression-module"] = compression; - SVNCheckoutTest.RunTest(TestFolders.ToArray(), opts, TestTarget); + await SVNCheckoutTest.RunTestAsync(TestFolders.ToArray(), opts, TestTarget); } [Test] [Category("SVNData")] - public void TestWithErrors() + public async Task TestWithErrorsAsync() { var u = new Library.Utility.Uri(TestUtils.GetDefaultTarget()); RandomErrorBackend.WrappedBackend = u.Scheme; @@ -115,19 +115,19 @@ namespace Duplicati.UnitTest // opts["restore-legacy"] = "true"; opts["retry-delay"] = "0s"; - SVNCheckoutTest.RunTest(TestFolders.Take(5).ToArray(), opts, target); + await SVNCheckoutTest.RunTestAsync(TestFolders.Take(5).ToArray(), opts, target); } [Test] [Category("SVNData")] - public void TestWithoutSizeInfo() + public async Task TestWithoutSizeInfoAsync() { var u = new Library.Utility.Uri(TestUtils.GetDefaultTarget()); SizeOmittingBackend.WrappedBackend = u.Scheme; var target = u.SetScheme(new SizeOmittingBackend().ProtocolKey).ToString(); Library.DynamicLoader.BackendLoader.AddBackend(new SizeOmittingBackend()); - SVNCheckoutTest.RunTest(TestFolders.Take(5).ToArray(), TestOptions, target); + await SVNCheckoutTest.RunTestAsync(TestFolders.Take(5).ToArray(), TestOptions, target); } } diff --git a/Duplicati/UnitTest/HttpsConfigurationTests.cs b/Duplicati/UnitTest/HttpsConfigurationTests.cs index e4a04dc6e..2ab36d46d 100644 --- a/Duplicati/UnitTest/HttpsConfigurationTests.cs +++ b/Duplicati/UnitTest/HttpsConfigurationTests.cs @@ -51,7 +51,7 @@ public class HttpsConfigurationTests private Connection _connection = null!; [SetUp] - public async Task SetUp() + public async Task SetUpAsync() { // Create a temporary folder for test data _tempDataFolder = Path.Combine(Path.GetTempPath(), $"duplicati-test-{Guid.NewGuid()}"); diff --git a/Duplicati/UnitTest/ImportExportTests.cs b/Duplicati/UnitTest/ImportExportTests.cs index 418293299..aeb052adc 100644 --- a/Duplicati/UnitTest/ImportExportTests.cs +++ b/Duplicati/UnitTest/ImportExportTests.cs @@ -125,7 +125,7 @@ namespace Duplicati.UnitTest [Test] [Category("ImportExport")] - public async Task RoundTrip() + public async Task RoundTripAsync() { var metadata = new Dictionary { { "SourceFilesCount", "1" } }; var advancedOptions = new Dictionary { { "server-datafolder", this.serverDatafolder } }; diff --git a/Duplicati/UnitTest/Issue1410.cs b/Duplicati/UnitTest/Issue1410.cs index c63fec294..6302243ad 100644 --- a/Duplicati/UnitTest/Issue1410.cs +++ b/Duplicati/UnitTest/Issue1410.cs @@ -25,6 +25,7 @@ using NUnit.Framework; using System.Linq; using Duplicati.Library.Interface; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -32,7 +33,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void RunCommands() + public async Task RunCommandsAsync() { var testopts = TestOptions; @@ -40,14 +41,14 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Console.WriteLine("In first backup:"); @@ -58,14 +59,14 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Console.WriteLine("Newest before deleting:"); @@ -75,7 +76,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0, no_local_db = true }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Console.WriteLine("Newest without db:"); @@ -87,14 +88,14 @@ namespace Duplicati.UnitTest File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IListResults listResults = c.List(); + var listResults = await c.ListAsync(); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); Assert.AreEqual(listResults.Filesets.Count(), 2); @@ -102,7 +103,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 1 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Console.WriteLine("Oldest after delete:"); @@ -112,7 +113,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 0 }), null)) { - var r = c.List("*"); + var r = await c.ListAsync("*"); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Console.WriteLine("Newest after delete:"); diff --git a/Duplicati/UnitTest/Issue1723.cs b/Duplicati/UnitTest/Issue1723.cs index 7c5f8cc84..4b4d57d86 100644 --- a/Duplicati/UnitTest/Issue1723.cs +++ b/Duplicati/UnitTest/Issue1723.cs @@ -21,7 +21,7 @@ using System; using System.IO; -using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; namespace Duplicati.UnitTest @@ -30,7 +30,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void RunCommands() + public async Task RunCommandsAsync() { var testopts = TestOptions; testopts["no-backend-verification"] = "true"; @@ -39,7 +39,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); TestUtils.AssertResults(r); var pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) diff --git a/Duplicati/UnitTest/Issue4312.cs b/Duplicati/UnitTest/Issue4312.cs index 8839854fb..9a5e1478e 100644 --- a/Duplicati/UnitTest/Issue4312.cs +++ b/Duplicati/UnitTest/Issue4312.cs @@ -23,6 +23,7 @@ using System; using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -32,7 +33,7 @@ public class Issue4312 : BasicSetupHelper { [Test] [Category("Targeted")] - public void ChangeTimestampShouldCreateExtraBackup() + public async Task ChangeTimestampShouldCreateExtraBackupAsync() { var testopts = TestOptions; // Make sure we detect changes from metadata @@ -43,7 +44,7 @@ public class Issue4312 : BasicSetupHelper File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(1, r.AddedFiles); @@ -51,9 +52,9 @@ public class Issue4312 : BasicSetupHelper if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); - System.Threading.Thread.Sleep(3000); + await Task.Delay(3000); - r = c.Backup(new string[] { DATAFOLDER }); + r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(0, r.AddedFiles); @@ -62,12 +63,12 @@ public class Issue4312 : BasicSetupHelper if (pr.KnownFileSize == 0 || pr.KnownFileCount != 3 || pr.BackupListCount != 1) throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); - System.Threading.Thread.Sleep(3000); + await Task.Delay(3000); // Force a change in the file metadata File.SetLastWriteTimeUtc(Path.Combine(DATAFOLDER, "a"), DateTime.Now); - r = c.Backup(new string[] { DATAFOLDER }); + r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(0, r.AddedFiles); @@ -80,7 +81,7 @@ public class Issue4312 : BasicSetupHelper [Test] [Category("Targeted")] - public void BackupFromRecreatedDatabaseShouldUpdateMetadata() + public async Task BackupFromRecreatedDatabaseShouldUpdateMetadataAsync() { var testopts = TestOptions; // Make sure we detect changes from metadata @@ -91,7 +92,7 @@ public class Issue4312 : BasicSetupHelper File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(1, r.AddedFiles); @@ -106,13 +107,13 @@ public class Issue4312 : BasicSetupHelper Thread.Sleep(2000); // Recreate - var rr = c.Repair(); + var rr = await c.RepairAsync(); Assert.AreEqual(0, rr.Errors.Count()); Assert.AreEqual(0, rr.Warnings.Count()); // Because the timestamps are restored with lower precision // this will trigger a rescan of the files - r = c.Backup(new string[] { DATAFOLDER }); + r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(0, r.AddedFiles); @@ -123,7 +124,7 @@ public class Issue4312 : BasicSetupHelper throw new Exception(string.Format("Looks like the metadata scan failed: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); // Make a backup again, and ensure that no files are opened - r = c.Backup(new string[] { DATAFOLDER }); + r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(0, r.AddedFiles); diff --git a/Duplicati/UnitTest/Issue4693.cs b/Duplicati/UnitTest/Issue4693.cs index 0f58d4a0e..97a6a9b11 100644 --- a/Duplicati/UnitTest/Issue4693.cs +++ b/Duplicati/UnitTest/Issue4693.cs @@ -22,6 +22,7 @@ using System; using System.IO; using System.Security.Cryptography; +using System.Threading.Tasks; using Duplicati.Library.Main.Database; using Duplicati.Library.SQLiteHelper; using NUnit.Framework; @@ -32,7 +33,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void RunCommandsOriginal() + public async Task RunCommandsOriginalAsync() { var testopts = TestOptions.Expand(new { no_encryption = true, keep_versions = 1, dblock_size = "5mb" }); @@ -46,7 +47,7 @@ namespace Duplicati.UnitTest // Backup 1 using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); File.Delete(Path.Combine(DATAFOLDER, "A.txt")); File.WriteAllText(Path.Combine(DATAFOLDER, "C.txt"), "C"); @@ -57,7 +58,7 @@ namespace Duplicati.UnitTest // Backup 2 System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); File.WriteAllText(Path.Combine(DATAFOLDER, "A.txt"), "A"); Random.Shared.NextBytes(buffer); @@ -69,7 +70,7 @@ namespace Duplicati.UnitTest // Backup 3 System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); File.Delete(Path.Combine(DATAFOLDER, "b1.bin")); File.Delete(Path.Combine(DATAFOLDER, "b2.bin")); @@ -78,26 +79,26 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup([DATAFOLDER]); + var res = await c.BackupAsync([DATAFOLDER]); TestUtils.AssertResults(res); Assert.That(res.CompactResults.DeletedFileCount, Is.GreaterThan(0), "Compact was not run"); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Test we can recrate without errors File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); } [Test] [Category("Targeted")] - public void RunCommandsSimplified() + public async Task RunCommandsSimplifiedAsync() { var testopts = TestOptions.Expand(new { no_encryption = true, keep_versions = 1, dblock_size = "5mb" }); @@ -106,7 +107,7 @@ namespace Duplicati.UnitTest // Backup 1 using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); File.Delete(Path.Combine(DATAFOLDER, "A.txt")); File.WriteAllText(Path.Combine(DATAFOLDER, "C.txt"), "C"); @@ -114,25 +115,25 @@ namespace Duplicati.UnitTest // Backup 2 System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); File.WriteAllText(Path.Combine(DATAFOLDER, "A.txt"), "A"); // Backup 3 System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Test we can recrate without errors File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); } [Test] @@ -143,7 +144,7 @@ namespace Duplicati.UnitTest [TestCase(0)] // Use in-memory cache [TestCase(1000)] - public void RunWithCaches(int cacheSize) + public async Task RunWithCachesAsync(int cacheSize) { Environment.SetEnvironmentVariable("DUPLICATI_DELETEDBLOCKCACHESIZE", cacheSize.ToString()); var testopts = TestOptions.Expand(new { no_encryption = true, keep_versions = 1, dblock_size = "5mb" }); @@ -154,9 +155,9 @@ namespace Duplicati.UnitTest // Backup 1 using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); - using (var db = SQLiteLoader.LoadConnection(DBFILE)) + using (var db = await SQLiteLoader.LoadConnectionAsync(DBFILE)) using (var cmd = db.CreateCommand()) { var deletedBlocks = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""DeletedBlock"""); @@ -170,16 +171,16 @@ namespace Duplicati.UnitTest // Backup 2 System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); - using (var db = SQLiteLoader.LoadConnection(DBFILE)) + using (var db = await SQLiteLoader.LoadConnectionAsync(DBFILE)) using (var cmd = db.CreateCommand()) { var deletedBlocks = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""DeletedBlock"""); Assert.That(deletedBlocks, Is.GreaterThan(0), "DeletedBlock table is empty"); } - using (var db = SQLiteLoader.LoadConnection(DBFILE)) + using (var db = await SQLiteLoader.LoadConnectionAsync(DBFILE)) using (var cmd = db.CreateCommand()) { var deletedBlocks = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""DeletedBlock"""); @@ -191,9 +192,9 @@ namespace Duplicati.UnitTest // Backup 3 System.Threading.Thread.Sleep(1000); // Ensure different timestamp using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); - using (var db = SQLiteLoader.LoadConnection(DBFILE)) + using (var db = await SQLiteLoader.LoadConnectionAsync(DBFILE)) using (var cmd = db.CreateCommand()) { var alldeletedBlocks = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM ""DeletedBlock"""); @@ -206,15 +207,15 @@ namespace Duplicati.UnitTest } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); // Test we can recrate without errors File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(long.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(long.MaxValue)); Environment.SetEnvironmentVariable("DUPLICATI_DELETEDBLOCKCACHESIZE", null); } diff --git a/Duplicati/UnitTest/Issue4988.cs b/Duplicati/UnitTest/Issue4988.cs index d5c037a6a..d9fc476c9 100644 --- a/Duplicati/UnitTest/Issue4988.cs +++ b/Duplicati/UnitTest/Issue4988.cs @@ -26,6 +26,7 @@ using System.IO.Compression; using System.Linq; using System.Text.Json; using System.Text.Json.Serialization; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main; using Duplicati.Library.Utility; @@ -45,7 +46,7 @@ public class Issue4988 : BasicSetupHelper [Test] [Category("ManualTamper")] - public void TestManualDindexTamperAndRecreate() + public async Task TestManualDindexTamperAndRecreateAsync() { var testopts = TestOptions.Expand(new { no_encryption = "true" }); var opts = new Options(testopts); @@ -56,7 +57,7 @@ public class Issue4988 : BasicSetupHelper // Step 2: Backup empty folder using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([emptyFolder])); + TestUtils.AssertResults(await c.BackupAsync([emptyFolder])); // Step 3: Find the dblock created var dblockPath = Directory.GetFiles(TARGETFOLDER, "*.dblock*", SearchOption.TopDirectoryOnly).First(); @@ -109,7 +110,7 @@ public class Issue4988 : BasicSetupHelper { try { - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); Assert.Fail("Expected recreate to fail due to corrupted dblock/dindex"); } catch (UserInformationException ex) when (ex.HelpID == "DatabaseIsBrokenConsiderPurge") @@ -121,7 +122,7 @@ public class Issue4988 : BasicSetupHelper // Step 9: List broken files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.ListBrokenFiles(null); + var res = await c.ListBrokenFilesAsync(null); Assert.IsTrue(res.BrokenFiles.Count() > 0, "Expected to find broken files"); foreach (var f in res.BrokenFiles) Console.WriteLine("Broken file: " + f); diff --git a/Duplicati/UnitTest/Issue5066.cs b/Duplicati/UnitTest/Issue5066.cs index 0b9b3dedb..48690dbd7 100644 --- a/Duplicati/UnitTest/Issue5066.cs +++ b/Duplicati/UnitTest/Issue5066.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Utility; using NUnit.Framework; @@ -42,7 +43,7 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase(true)] [TestCase(false)] - public void TestDuplicatedBlocklists1(bool deleteAllIndexFiles) + public async Task TestDuplicatedBlocklists1Async(bool deleteAllIndexFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb" }); var hashes = new List(); @@ -54,7 +55,7 @@ namespace Duplicati.UnitTest hashes.Add(CalculateFileHash(Path.Combine(DATAFOLDER, "a"))); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Make the first blocklist different data[0] = (byte)'b'; @@ -64,7 +65,7 @@ namespace Duplicati.UnitTest // Record existing dindex files var existingDIndexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex*", SearchOption.TopDirectoryOnly).ToList(); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Delete new index files foreach (var file in Directory.GetFiles(TARGETFOLDER, "*.dindex*", SearchOption.TopDirectoryOnly)) @@ -75,8 +76,8 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } for (var version = 0; version < 2; version++) @@ -84,7 +85,7 @@ namespace Duplicati.UnitTest File.Delete(Path.Combine(DATAFOLDER, "a")); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = version }), null)) { - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); var hash = CalculateFileHash(Path.Combine(DATAFOLDER, "a")); Assert.That(hashes[version], Is.EqualTo(hash), "Hash mismatch for version " + version); } @@ -95,7 +96,7 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase(true)] [TestCase(false)] - public void TestDuplicatedBlocklists2(bool deleteAllIndexFiles) + public async Task TestDuplicatedBlocklists2Async(bool deleteAllIndexFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb" }); var hashes = new List(); @@ -107,7 +108,7 @@ namespace Duplicati.UnitTest hashes.Add(CalculateFileHash(Path.Combine(DATAFOLDER, "a"))); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Expand to an new set data = new byte[32769 * 2 + 1]; @@ -119,7 +120,7 @@ namespace Duplicati.UnitTest // Record existing dindex files var existingDIndexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex*", SearchOption.TopDirectoryOnly).ToList(); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Delete new index files foreach (var file in Directory.GetFiles(TARGETFOLDER, "*.dindex*", SearchOption.TopDirectoryOnly)) @@ -130,8 +131,8 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } for (var version = 0; version < 2; version++) @@ -139,7 +140,7 @@ namespace Duplicati.UnitTest File.Delete(Path.Combine(DATAFOLDER, "a")); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = version }), null)) { - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); var hash = CalculateFileHash(Path.Combine(DATAFOLDER, "a")); Assert.That(hashes[version], Is.EqualTo(hash), "Hash mismatch for version " + version); } diff --git a/Duplicati/UnitTest/Issue5097.cs b/Duplicati/UnitTest/Issue5097.cs index db9de1f1d..faddc01e4 100644 --- a/Duplicati/UnitTest/Issue5097.cs +++ b/Duplicati/UnitTest/Issue5097.cs @@ -23,6 +23,7 @@ using System; using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Threading.Tasks; using Duplicati.Library.Interface; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -33,7 +34,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void RunCommands() + public async Task RunCommandsAsync() { var testopts = TestOptions; testopts["upload-unchanged-backups"] = "true"; @@ -45,7 +46,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); } @@ -55,7 +56,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); } @@ -73,11 +74,11 @@ namespace Duplicati.UnitTest File.Delete(DBFILE); // Repair the database, expect an error - Assert.Catch(() => + Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Repair(); + var r = await c.RepairAsync(); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); } @@ -85,11 +86,11 @@ namespace Duplicati.UnitTest // Make a backup, this should fail File.WriteAllBytes(Path.Combine(DATAFOLDER, "c"), data); - var uix = Assert.Catch(() => + var uix = Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(1, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); } diff --git a/Duplicati/UnitTest/Issue5196.cs b/Duplicati/UnitTest/Issue5196.cs index 090869a6e..135909923 100644 --- a/Duplicati/UnitTest/Issue5196.cs +++ b/Duplicati/UnitTest/Issue5196.cs @@ -22,6 +22,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Main.Volumes; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -32,7 +33,7 @@ public class Issue5196 : BasicSetupHelper { [Test] [Category("Targeted")] - public void RunCommands() + public async Task RunCommandsAsync() { var testopts = TestOptions; testopts["upload-unchanged-backups"] = "true"; @@ -42,7 +43,7 @@ public class Issue5196 : BasicSetupHelper File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.AreEqual(1, r.AddedFiles); @@ -52,14 +53,14 @@ public class Issue5196 : BasicSetupHelper System.Threading.Thread.Sleep(3000); - r = c.Backup(new string[] { DATAFOLDER }); + r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); pr = (Library.Interface.IParsedBackendStatistics)r.BackendStatistics; if (pr.KnownFileSize == 0 || pr.KnownFileCount != 4 || pr.BackupListCount != 2) throw new Exception(string.Format("Failed to get stats from remote backend: {0}, {1}, {2}", pr.KnownFileSize, pr.KnownFileCount, pr.BackupListCount)); - var versions = c.List().Filesets.ToList(); + var versions = (await c.ListAsync()).Filesets.ToList(); using (var tempDbPath = new Library.Utility.TempFile()) { @@ -67,17 +68,17 @@ public class Issue5196 : BasicSetupHelper testopts["repair-only-paths"] = "true"; testopts.Remove("blocksize"); testopts["time"] = Library.Utility.Utility.SerializeDateTime(versions[0].Time); - var rcr1 = c.UpdateDatabaseWithVersions(); + var rcr1 = await c.UpdateDatabaseWithVersionsAsync(); testopts["time"] = Library.Utility.Utility.SerializeDateTime(versions[1].Time); - var rcr2 = c.UpdateDatabaseWithVersions(); + var rcr2 = await c.UpdateDatabaseWithVersionsAsync(); File.Delete(tempDbPath); testopts["repair-only-paths"] = "true"; testopts["blocksize"] = "25kb"; try { - c.UpdateDatabaseWithVersions(); + await c.UpdateDatabaseWithVersionsAsync(); throw new Exception("Expected an exception when changing blocksize"); } catch (InvalidManifestException) diff --git a/Duplicati/UnitTest/Issue5845.cs b/Duplicati/UnitTest/Issue5845.cs index aec87d3dd..1f37ca1a2 100644 --- a/Duplicati/UnitTest/Issue5845.cs +++ b/Duplicati/UnitTest/Issue5845.cs @@ -85,7 +85,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void TestDuplicatedBlocksInOrphanIndex() + public async Task TestDuplicatedBlocksInOrphanIndexAsync() { var testopts = TestOptions; testopts.Add("no-encryption", "true"); @@ -97,7 +97,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -167,14 +167,14 @@ namespace Duplicati.UnitTest File.Delete(DBFILE); using (var c = new Library.Main.Controller(ListSortingBackend.Key + "://" + TARGETFOLDER, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(3, repairResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IListResults listResults = c.List(); + var listResults = await c.ListAsync(); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); Assert.AreEqual(listResults.Filesets.Count(), 1); @@ -183,7 +183,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void TestReplicatedBlocksInOrphanIndex() + public async Task TestReplicatedBlocksInOrphanIndexAsync() { var replicas = 4; var goodExtras = 1; @@ -197,7 +197,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -262,14 +262,14 @@ namespace Duplicati.UnitTest File.Delete(DBFILE); using (var c = new Library.Main.Controller(ListSortingBackend.Key + "://" + TARGETFOLDER, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(1 + replicas - goodExtras, repairResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IListResults listResults = c.List(); + var listResults = await c.ListAsync(); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); Assert.AreEqual(1, listResults.Filesets.Count()); diff --git a/Duplicati/UnitTest/Issue5862.cs b/Duplicati/UnitTest/Issue5862.cs index c5091fe2f..3507dae7a 100644 --- a/Duplicati/UnitTest/Issue5862.cs +++ b/Duplicati/UnitTest/Issue5862.cs @@ -81,7 +81,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void TestUploadFailureWithResume() + public async Task TestUploadFailureWithResumeAsync() { var testopts = TestOptions.Expand(new { @@ -95,11 +95,11 @@ namespace Duplicati.UnitTest // Make a failing backup using (var c = new Library.Main.Controller(PreventingBackend.Key + "://" + TARGETFOLDER, testopts, null)) - Assert.Throws(() => c.Backup(new string[] { DATAFOLDER })); + Assert.ThrowsAsync(async () => await c.BackupAsync(new string[] { DATAFOLDER })); // Make a working backup, should not give any errors using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); } } } diff --git a/Duplicati/UnitTest/Issue5987.cs b/Duplicati/UnitTest/Issue5987.cs index c7a51e9c8..399590b26 100644 --- a/Duplicati/UnitTest/Issue5987.cs +++ b/Duplicati/UnitTest/Issue5987.cs @@ -21,6 +21,7 @@ using System.IO; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; namespace Duplicati.UnitTest @@ -31,14 +32,14 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase(true)] [TestCase(false)] - public void TestRestoreWithMissingDblocks(bool deleteIndexFiles) + public async Task TestRestoreWithMissingDblocksAsync(bool deleteIndexFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); // 1. Make a backup of a single file File.WriteAllText(Path.Combine(DATAFOLDER, "a"), "abc"); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete the dblock file var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).ToList(); @@ -55,9 +56,9 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Repair(); + var res = await c.RepairAsync(); Assert.That(res.Errors.Count(), Is.EqualTo(0), "Repair should not have errors"); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.TestAsync()); } } } diff --git a/Duplicati/UnitTest/Issue6070.cs b/Duplicati/UnitTest/Issue6070.cs index 5d6a096de..4c3338473 100644 --- a/Duplicati/UnitTest/Issue6070.cs +++ b/Duplicati/UnitTest/Issue6070.cs @@ -23,9 +23,9 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Interface; using NUnit.Framework; -using Tmds.DBus.Protocol; namespace Duplicati.UnitTest { @@ -33,7 +33,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void TestPurgeFindsCorrectBlockSize() + public async Task TestPurgeFindsCorrectBlockSizeAsync() { // 1. Prepare some data var longdata = new byte[32 * 1024 + 5]; @@ -51,7 +51,7 @@ namespace Duplicati.UnitTest // 3. Backup the large file File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), longdata); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); var existingDIndexAndDblockFiles = Directory.GetFiles(TARGETFOLDER) .Where(f => f.Contains(".dindex.") || f.Contains(".dblock.")) @@ -60,7 +60,7 @@ namespace Duplicati.UnitTest // 4. Backup the small file File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), shortdata); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 5. Delete dindex and dblock files from second backup foreach (var file in Directory.GetFiles(TARGETFOLDER) @@ -87,11 +87,11 @@ namespace Duplicati.UnitTest // 7. Recreate and expect an error due to missing data File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, opts1, null)) - Assert.Throws(() => c.Repair()); + Assert.ThrowsAsync(() => c.RepairAsync()); // 8. Run purge-broken-files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, opts2, null)) - TestUtils.AssertResults(c.PurgeBrokenFiles(null)); + TestUtils.AssertResults(await c.PurgeBrokenFilesAsync(null)); } } diff --git a/Duplicati/UnitTest/Issue6127.cs b/Duplicati/UnitTest/Issue6127.cs index f17c7c6cc..d1c40921a 100644 --- a/Duplicati/UnitTest/Issue6127.cs +++ b/Duplicati/UnitTest/Issue6127.cs @@ -22,6 +22,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using NUnit.Framework; namespace Duplicati.UnitTest @@ -30,7 +31,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void TestMultiRetentionOptions() + public async Task TestMultiRetentionOptionsAsync() { var testopts = TestOptions; testopts.Add("upload-unchanged-backups", "true"); @@ -44,9 +45,9 @@ namespace Duplicati.UnitTest { using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup([DATAFOLDER]); + var res = await c.BackupAsync([DATAFOLDER]); TestUtils.AssertResults(res); - Assert.That(c.List(null).Filesets.Count(), Is.EqualTo(i + 1), "Backup count mismatch after backup " + i); + Assert.That((await c.ListAsync(null)).Filesets.Count(), Is.EqualTo(i + 1), "Backup count mismatch after backup " + i); } Thread.Sleep(1000); } @@ -55,10 +56,10 @@ namespace Duplicati.UnitTest Thread.Sleep(1000); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 2, retention_policy = "1s:U" }), null)) - TestUtils.AssertResults(c.Delete()); + TestUtils.AssertResults(await c.DeleteAsync()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = 2, retention_policy = "1s:U" }), null)) - Assert.That(c.List(null).Filesets.Count(), Is.EqualTo(1), "Backup count mismatch after delete"); + Assert.That((await c.ListAsync(null)).Filesets.Count(), Is.EqualTo(1), "Backup count mismatch after delete"); } } } diff --git a/Duplicati/UnitTest/Issue6200.cs b/Duplicati/UnitTest/Issue6200.cs index 6321f0d86..3f6dafa30 100644 --- a/Duplicati/UnitTest/Issue6200.cs +++ b/Duplicati/UnitTest/Issue6200.cs @@ -24,6 +24,7 @@ using NUnit.Framework; using System; using NUnit.Framework.Internal; using Duplicati.Library.DynamicLoader; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -31,7 +32,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void VerifyMultiVolumeCompactWorks() + public async Task VerifyMultiVolumeCompactWorksAsync() { const int FILESIZE = 500 * 1024; const int FILECOUNT = 40; @@ -57,7 +58,7 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Delete half the files for (var i = 0; i < FILECOUNT / 2; i++) @@ -65,13 +66,13 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); var fullBlockvolumeCount = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).Length; // Now run compact using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Compact()); + TestUtils.AssertResults(await c.CompactAsync()); var newBlockvolumeCount = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).Length; @@ -89,7 +90,7 @@ namespace Duplicati.UnitTest }; using (var c = new Library.Main.Controller(new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); } } } diff --git a/Duplicati/UnitTest/Issue6235.cs b/Duplicati/UnitTest/Issue6235.cs index 597c7f388..2d47cfeb6 100644 --- a/Duplicati/UnitTest/Issue6235.cs +++ b/Duplicati/UnitTest/Issue6235.cs @@ -23,6 +23,7 @@ using System.IO; using NUnit.Framework; using NUnit.Framework.Internal; using Duplicati.Library.DynamicLoader; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -30,7 +31,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void RepairWithDlistRetries([Values(1, 2)] int keepVersions) + public async Task RepairWithDlistRetriesAsync([Values(1, 2)] int keepVersions) { var testopts = TestOptions.Expand(new { @@ -46,7 +47,7 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); BackendLoader.AddBackend(new DeterministicErrorBackend()); var count = 0; @@ -63,13 +64,13 @@ namespace Duplicati.UnitTest // Make a backup where two attemps to upload the dlist file fails using (var c = new Library.Main.Controller(new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); Assert.That(count, Is.EqualTo(3), "Did not retry the dlist upload"); // Check that repair works using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that recreate works File.Delete(DBFILE); @@ -81,7 +82,7 @@ namespace Duplicati.UnitTest }; using (var c = new Library.Main.Controller(new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); } } } diff --git a/Duplicati/UnitTest/Issue6254.cs b/Duplicati/UnitTest/Issue6254.cs index 1a7f2f3e0..cfabafa35 100644 --- a/Duplicati/UnitTest/Issue6254.cs +++ b/Duplicati/UnitTest/Issue6254.cs @@ -26,6 +26,7 @@ using System; using NUnit.Framework.Internal; using System.IO.Compression; using Duplicati.Library.DynamicLoader; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -33,7 +34,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void VerifyCompactKeepsIndexFileBlocklists() + public async Task VerifyCompactKeepsIndexFileBlocklistsAsync() { var testopts = TestOptions.Expand(new { @@ -59,7 +60,7 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Add some second files for (var i = 0; i < 10; i++) @@ -67,7 +68,7 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Delete the first files for (var i = 0; i < 8; i++) @@ -77,11 +78,11 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Now run compact using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Compact()); + TestUtils.AssertResults(await c.CompactAsync()); // Find the new index files var newIndexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex.*", SearchOption.TopDirectoryOnly) @@ -115,7 +116,7 @@ namespace Duplicati.UnitTest }; using (var c = new Library.Main.Controller(new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); } } } diff --git a/Duplicati/UnitTest/Issue6296.cs b/Duplicati/UnitTest/Issue6296.cs index c5a120f34..075dd00f1 100644 --- a/Duplicati/UnitTest/Issue6296.cs +++ b/Duplicati/UnitTest/Issue6296.cs @@ -28,6 +28,7 @@ using System.IO.Compression; using Duplicati.Library.DynamicLoader; using Duplicati.Library.Main; using System.Collections.Generic; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -35,7 +36,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void TestRepairIndexFilesWorks([Values(1, 2, 3)] int fileDistribution) + public async Task TestRepairIndexFilesWorksAsync([Values(1, 2, 3)] int fileDistribution) { var testopts = TestOptions.Expand(new { @@ -77,7 +78,7 @@ namespace Duplicati.UnitTest // Make a backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Make sure the tests succeed var verifyopts = testopts.Expand(new @@ -86,7 +87,7 @@ namespace Duplicati.UnitTest dont_replace_faulty_index_files = true, }); using (var c = new Controller("file://" + TARGETFOLDER, verifyopts, null)) - TestUtils.AssertResults(c.Test(short.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(short.MaxValue)); // Manipulate the index files to remove the blocklist hashes var brokenIndexFiles = new HashSet(); @@ -125,7 +126,7 @@ namespace Duplicati.UnitTest File.Delete(DBFILE); using (var c = new Controller(new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Check that the backup is valid, but missing data in the index files Assert.That(anyblockfiles, Is.True, "No dblock files were loaded during repair"); @@ -141,7 +142,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, repairopts, null)) { - var res = c.Test(short.MaxValue); + var res = await c.TestAsync(short.MaxValue); Assert.That(res.Warnings, Is.Not.Empty, "Expected warnings during test with broken index files"); var faultyResults = res.Verifications .Where(x => x.Value.Any()) @@ -165,7 +166,7 @@ namespace Duplicati.UnitTest // Check that the recreate operation works without downloading dblock files using (var c = new Controller(new DeterministicErrorBackend().ProtocolKey + "://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); } } } diff --git a/Duplicati/UnitTest/Issue6339.cs b/Duplicati/UnitTest/Issue6339.cs index 77fb3c2ad..c574edee6 100644 --- a/Duplicati/UnitTest/Issue6339.cs +++ b/Duplicati/UnitTest/Issue6339.cs @@ -25,6 +25,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using NUnit.Framework; using Duplicati.Library.Utility; +using System.Threading.Tasks; namespace Duplicati.UnitTest; @@ -45,7 +46,7 @@ public class Issue6339 : BasicSetupHelper [Test] [Category("Targeted")] - public void RepairShouldRecreateCompleteDindex([Values(1, 2)] int versions, [Values(true, false)] bool recreatedb) + public async Task RepairShouldRecreateCompleteDindexAsync([Values(1, 2)] int versions, [Values(true, false)] bool recreatedb) { var testopts = TestOptions.Expand(new { no_encryption = true, keep_versions = versions }); @@ -56,10 +57,10 @@ public class Issue6339 : BasicSetupHelper File.WriteAllText(pathb, "B"); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { patha, pathb })); + TestUtils.AssertResults(await c.BackupAsync(new[] { patha, pathb })); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { patha })); + TestUtils.AssertResults(await c.BackupAsync(new[] { patha })); var dindex = Directory.GetFiles(TARGETFOLDER, "*.dindex.*", SearchOption.TopDirectoryOnly).First(); int dataBlocks; @@ -75,14 +76,14 @@ public class Issue6339 : BasicSetupHelper File.Delete(dindex); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); if (recreatedb) { File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); } var newIndex = Directory.GetFiles(TARGETFOLDER, "*.dindex.*", SearchOption.TopDirectoryOnly).First(); @@ -95,6 +96,6 @@ public class Issue6339 : BasicSetupHelper } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(100)); + TestUtils.AssertResults(await c.TestAsync(100)); } } diff --git a/Duplicati/UnitTest/Issue6366.cs b/Duplicati/UnitTest/Issue6366.cs index 3d33c504e..e90e09ef8 100644 --- a/Duplicati/UnitTest/Issue6366.cs +++ b/Duplicati/UnitTest/Issue6366.cs @@ -17,7 +17,7 @@ namespace Duplicati.UnitTest // [Test] // [Category("Purge")] // [Category("Performance")] - public async Task TestPurgeBrokenFilesPerformance() + public async Task TestPurgeBrokenFilesPerformanceAsync() { var blocksize = 1024 * 10; var numberOfVersions = 50; // Simulate many versions as in the issue @@ -48,7 +48,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); } @@ -79,7 +79,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var task = Task.Run(() => c.PurgeBrokenFiles(null)); + var task = Task.Run(async () => await c.PurgeBrokenFilesAsync(null)); if (await Task.WhenAny(task, Task.Delay(60000)) == task) { var brk = await task; @@ -88,7 +88,7 @@ namespace Duplicati.UnitTest } else { - c.Abort(); + await c.AbortAsync(); Assert.Fail("PurgeBrokenFiles timed out"); } } @@ -105,7 +105,7 @@ namespace Duplicati.UnitTest [Test] [Category("Purge")] - public void TestPurgeBrokenFilesReducedStatistics() + public async Task TestPurgeBrokenFilesReducedStatisticsAsync() { var blocksize = 1024 * 10; var numberOfVersions = 3; @@ -141,7 +141,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); } @@ -171,7 +171,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var brk = c.PurgeBrokenFiles(null); + var brk = await c.PurgeBrokenFilesAsync(null); Assert.AreEqual(0, brk.Errors.Count()); Assert.AreEqual(0, brk.Warnings.Count()); diff --git a/Duplicati/UnitTest/Issue6425.cs b/Duplicati/UnitTest/Issue6425.cs index 1c2cf6651..0c89d3c42 100644 --- a/Duplicati/UnitTest/Issue6425.cs +++ b/Duplicati/UnitTest/Issue6425.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -10,7 +11,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Purge")] - public void TestPurgeFileSizeCalculationWithSharedBlocks() + public async Task TestPurgeFileSizeCalculationWithSharedBlocksAsync() { var blocksize = 1024 * 10; // 10KB blocks @@ -29,13 +30,13 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { DATAFOLDER }); + var res = await c.BackupAsync(new[] { DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var purgeResult = c.PurgeFiles(new Duplicati.Library.Utility.FilterExpression(new[] { file1 })); + var purgeResult = await c.PurgeFilesAsync(new Duplicati.Library.Utility.FilterExpression(new[] { file1 })); Assert.AreEqual(0, purgeResult.Errors.Count()); diff --git a/Duplicati/UnitTest/Issue6504.cs b/Duplicati/UnitTest/Issue6504.cs index a3d60b653..1ed6a90cc 100644 --- a/Duplicati/UnitTest/Issue6504.cs +++ b/Duplicati/UnitTest/Issue6504.cs @@ -25,6 +25,7 @@ using NUnit.Framework; using Duplicati.Library.Main.Volumes; using Duplicati.Library.SQLiteHelper; using Duplicati.Library.Main.Database; +using System.Threading.Tasks; namespace Duplicati.UnitTest; @@ -32,7 +33,7 @@ public class Issue6504 : BasicSetupHelper { [Test] [Category("Targeted")] - public void RecreateIndexFilesShouldHandleDuplicatedBlocks() + public async Task RecreateIndexFilesShouldHandleDuplicatedBlocksAsync() { var testopts = TestOptions.Expand(new { no_encryption = true, keep_versions = 1 }); @@ -43,7 +44,7 @@ public class Issue6504 : BasicSetupHelper File.WriteAllText(pathb, "B"); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new[] { patha, pathb })); + TestUtils.AssertResults(await c.BackupAsync(new[] { patha, pathb })); var original_dblock_file = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).First(); var original_dindex_file = Directory.GetFiles(TARGETFOLDER, "*.dindex.*", SearchOption.TopDirectoryOnly).First(); @@ -90,10 +91,10 @@ public class Issue6504 : BasicSetupHelper // Prepare for accepting the new duplicated block by recreating the database File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // Force the error by making sure the duplicate blocks are from both dblock files - using (var db = SQLiteLoader.LoadConnection(DBFILE)) + using (var db = await SQLiteLoader.LoadConnectionAsync(DBFILE)) using (var cmd = db.CreateCommand()) { cmd.CommandText = @"SELECT BlockId,VolumeID FROM DuplicateBlock"; @@ -112,13 +113,13 @@ public class Issue6504 : BasicSetupHelper cmd.CommandText = "UPDATE DuplicateBlock SET VolumeID=@VolumeID WHERE BlockID=@BlockID"; cmd.Parameters.AddWithValue("@VolumeID", otherVolumeId); cmd.Parameters.AddWithValue("@BlockID", blockToChange.BlockId); - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); cmd.Parameters.Clear(); cmd.CommandText = "UPDATE Block SET VolumeID=@VolumeID WHERE ID=@BlockID"; cmd.Parameters.AddWithValue("@VolumeID", duplicateVolumeId); cmd.Parameters.AddWithValue("@BlockID", blockToChange.BlockId); - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } } @@ -126,13 +127,13 @@ public class Issue6504 : BasicSetupHelper // Make sure the test rewrites the faulty index file using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Test(100); + var res = await c.TestAsync(100); Assert.That(res.Warnings.Count, Is.EqualTo(1), "Expected one warning about faulty index files"); Assert.That(res.Warnings.Any(c => c.Contains("FaultyIndexFiles")), Is.True, "Expected a warning about faulty index files"); } // Second run should not have faulty index files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test(100)); + TestUtils.AssertResults(await c.TestAsync(100)); } } diff --git a/Duplicati/UnitTest/Issue6529.cs b/Duplicati/UnitTest/Issue6529.cs index 1fd943b8a..f9665e018 100644 --- a/Duplicati/UnitTest/Issue6529.cs +++ b/Duplicati/UnitTest/Issue6529.cs @@ -25,6 +25,7 @@ using System.IO; using System.Linq; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -37,7 +38,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void TestChangedFilesOptionDoesNotCauseFilesetMismatch() + public async Task TestChangedFilesOptionDoesNotCauseFilesetMismatchAsync() { // Setup: Create test files var testFile1 = Path.Combine(DATAFOLDER, "file1.txt"); @@ -51,7 +52,7 @@ namespace Duplicati.UnitTest // Step 1: Do initial backup without --changed-files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -67,7 +68,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, changedFilesOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -77,7 +78,7 @@ namespace Duplicati.UnitTest { // This is where the bug manifests - the backup should succeed // but currently fails with "Unexpected difference in fileset version" - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count(), "Backup after --changed-files should not cause fileset mismatch errors"); Assert.AreEqual(0, backupResults.Warnings.Count()); @@ -86,14 +87,14 @@ namespace Duplicati.UnitTest // Step 5: Verify data integrity by doing a test using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var testResults = c.Test(); + var testResults = await c.TestAsync(); Assert.AreEqual(0, testResults.Errors.Count()); } } [Test] [Category("Targeted")] - public void TestDeletedFilesOptionDoesNotCauseFilesetMismatch() + public async Task TestDeletedFilesOptionDoesNotCauseFilesetMismatchAsync() { // Setup: Create test files var testFile1 = Path.Combine(DATAFOLDER, "file1.txt"); @@ -107,7 +108,7 @@ namespace Duplicati.UnitTest // Step 1: Do initial backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); } @@ -123,14 +124,14 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, changedFilesOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); } // Step 4: Do another backup (this should NOT fail) using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count(), "Backup after --deleted-files should not cause fileset mismatch errors"); } @@ -138,7 +139,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void TestMultipleChangedFilesBackupsInSequence() + public async Task TestMultipleChangedFilesBackupsInSequenceAsync() { // Setup: Create test files var testFile1 = Path.Combine(DATAFOLDER, "file1.txt"); @@ -150,7 +151,7 @@ namespace Duplicati.UnitTest // Step 1: Initial backup using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - c.Backup([DATAFOLDER]); + await c.BackupAsync([DATAFOLDER]); } // Step 2-4: Multiple sequential backups with --changed-files @@ -166,7 +167,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, changedFilesOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count(), $"Backup iteration {i} with --changed-files should succeed"); } @@ -175,7 +176,7 @@ namespace Duplicati.UnitTest // Step 5: Final backup without --changed-files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup([DATAFOLDER]); + var backupResults = await c.BackupAsync([DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count(), "Final backup should succeed without fileset mismatch"); } diff --git a/Duplicati/UnitTest/Issue6538.cs b/Duplicati/UnitTest/Issue6538.cs index 1f17e99ed..204ae0be9 100644 --- a/Duplicati/UnitTest/Issue6538.cs +++ b/Duplicati/UnitTest/Issue6538.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using NUnit.Framework; using Duplicati.Library.Interface; using Duplicati.Library.Main; +using System.Threading.Tasks; using Microsoft.Data.Sqlite; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -20,7 +21,7 @@ namespace Duplicati.UnitTest [Test] [Category("Issue6538")] - public void JobLogShouldBeWrittenOnError() + public async Task JobLogShouldBeWrittenOnErrorAsync() { // Arrange var testopts = TestOptions; @@ -33,7 +34,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count()); } @@ -46,7 +47,7 @@ namespace Duplicati.UnitTest IBackupResults backupResults = null; try { - backupResults = c.Backup(new[] { invalidSourcePath }); + backupResults = await c.BackupAsync(new[] { invalidSourcePath }); } catch { @@ -56,7 +57,7 @@ namespace Duplicati.UnitTest // Assert - Check that the job log was written to the database using (var connection = new SqliteConnection($"Data Source={testopts["dbpath"]};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); var command = connection.CreateCommand(); // We want to check the LAST log entry, or ensure there is a NEW one. // Since we ran a backup before, there will be logs. @@ -64,9 +65,9 @@ namespace Duplicati.UnitTest command.CommandText = "SELECT Type, Message FROM LogData ORDER BY Timestamp DESC LIMIT 10"; var logEntries = new List(); - using (var reader = command.ExecuteReader()) + using (var reader = await command.ExecuteReaderAsync()) { - while (reader.Read()) + while (await reader.ReadAsync()) { var entry = new LogEntry { diff --git a/Duplicati/UnitTest/Issue6539.cs b/Duplicati/UnitTest/Issue6539.cs index 8e8129415..7c6d357ef 100644 --- a/Duplicati/UnitTest/Issue6539.cs +++ b/Duplicati/UnitTest/Issue6539.cs @@ -25,6 +25,7 @@ using System.Linq; using Duplicati.Library.DynamicLoader; using Duplicati.Library.Main; using Microsoft.Data.Sqlite; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -36,7 +37,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void DeleteFailureShouldNotMarkAsDeleted() + public async Task DeleteFailureShouldNotMarkAsDeletedAsync() { var testopts = TestOptions.Expand(new { @@ -52,12 +53,12 @@ namespace Duplicati.UnitTest // Run initial backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Run second backup to create a version we can delete File.WriteAllText(testFile, "Modified test data for issue 6539"); using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Get the dlist file that will be deleted (Version 0 is the newest) var dlistFiles = Directory.GetFiles(TARGETFOLDER, "*.dlist.*"); @@ -89,7 +90,7 @@ namespace Duplicati.UnitTest null)) { // This should throw because delete fails - c.Delete(); + await c.DeleteAsync(); } } catch @@ -103,7 +104,7 @@ namespace Duplicati.UnitTest // Check database state - file should still be in Deleting state, not Deleted using (var connection = new SqliteConnection($"Data Source={DBFILE};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); using (var cmd = connection.CreateCommand()) { cmd.CommandText = @" @@ -112,9 +113,9 @@ namespace Duplicati.UnitTest WHERE Name = @name"; cmd.Parameters.AddWithValue("@name", fileToDelete); - using (var reader = cmd.ExecuteReader()) + using (var reader = await cmd.ExecuteReaderAsync()) { - if (reader.Read()) + if (await reader.ReadAsync()) { var state = reader.GetString(1); // State should be "Deleting", not "Deleted" @@ -136,12 +137,12 @@ namespace Duplicati.UnitTest // Retry delete - this should succeed and clean up the state using (var c = new Controller("file://" + TARGETFOLDER, deleteOpts, null)) - c.Delete(); + await c.DeleteAsync(); // Check database state - file should now be in Deleted state using (var connection = new SqliteConnection($"Data Source={DBFILE};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); using (var cmd = connection.CreateCommand()) { cmd.CommandText = @" @@ -150,9 +151,9 @@ namespace Duplicati.UnitTest WHERE Name = @name"; cmd.Parameters.AddWithValue("@name", fileToDelete); - using (var reader = cmd.ExecuteReader()) + using (var reader = await cmd.ExecuteReaderAsync()) { - if (reader.Read()) + if (await reader.ReadAsync()) { var state = reader.GetString(1); Assert.That(state, Is.EqualTo("Deleted"), @@ -165,7 +166,7 @@ namespace Duplicati.UnitTest // Now run a test/verify operation - it should NOT report "not recorded in local storage" using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var result = c.Test(1); + var result = await c.TestAsync(1); // Should not have errors about files not recorded in local storage Assert.That(result.Errors, Is.Empty, "Test should not report errors about files not recorded in local storage"); @@ -174,7 +175,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void DeleteSuccessShouldMarkAsDeleted() + public async Task DeleteSuccessShouldMarkAsDeletedAsync() { var testopts = TestOptions.Expand(new { @@ -190,12 +191,12 @@ namespace Duplicati.UnitTest // Run initial backup using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Run second backup File.WriteAllText(testFile, "Modified test data for issue 6539"); using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); // Get the dlist file that will be deleted (Version 0 is the newest) var dlistFiles = Directory.GetFiles(TARGETFOLDER, "*.dlist.*"); @@ -210,12 +211,12 @@ namespace Duplicati.UnitTest }); using (var c = new Controller("file://" + TARGETFOLDER, deleteOpts, null)) - c.Delete(); + await c.DeleteAsync(); // Check database state - file should be marked as Deleted using (var connection = new SqliteConnection($"Data Source={DBFILE};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); using (var cmd = connection.CreateCommand()) { cmd.CommandText = @" @@ -224,11 +225,11 @@ namespace Duplicati.UnitTest WHERE Name = @name"; cmd.Parameters.AddWithValue("@name", fileToDelete); - using (var reader = cmd.ExecuteReader()) + using (var reader = await cmd.ExecuteReaderAsync()) { // File might be completely removed from DB after successful delete // or marked as Deleted - both are acceptable - if (reader.Read()) + if (await reader.ReadAsync()) { var state = reader.GetString(1); Assert.That(state, Is.EqualTo("Deleted"), diff --git a/Duplicati/UnitTest/Issue6552.cs b/Duplicati/UnitTest/Issue6552.cs index cc6024688..4145d2462 100644 --- a/Duplicati/UnitTest/Issue6552.cs +++ b/Duplicati/UnitTest/Issue6552.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Duplicati.Library.Main; using Microsoft.Data.Sqlite; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -12,7 +13,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Issue6552")] - public void RecreateDbShouldNotCreateInvalidDeletedBlockReferences() + public async Task RecreateDbShouldNotCreateInvalidDeletedBlockReferencesAsync() { // Arrange var testopts = TestOptions; @@ -25,17 +26,16 @@ namespace Duplicati.UnitTest // Step 1: Create file A.txt and backup File.WriteAllText(Path.Combine(sourceFolder, "A.txt"), "Content A"); - string savedDindexPath = null; using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count()); } // Save the dindex file from first backup var dindexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex.*"); Assert.IsTrue(dindexFiles.Length > 0, "Should have at least one dindex file"); - savedDindexPath = Path.Combine(DATAFOLDER, Path.GetFileName(dindexFiles[0])); + var savedDindexPath = Path.Combine(DATAFOLDER, Path.GetFileName(dindexFiles[0])); File.Copy(dindexFiles[0], savedDindexPath); // Identify the dblock and dlist from the first backup @@ -48,7 +48,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count()); } @@ -79,19 +79,19 @@ namespace Duplicati.UnitTest // This should complete but may create invalid references try { - var res = c.Repair(); + var res = await c.RepairAsync(); } catch (Exception ex) { // Recreate might fail, but we want to check the DB state - TestContext.Progress.WriteLine($"Repair failed: {ex.Message}"); + await TestContext.Progress.WriteLineAsync($"Repair failed: {ex.Message}"); } } // Step 5: Check for invalid DeletedBlock references using (var connection = new SqliteConnection($"Data Source={testopts["dbpath"]};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); var command = connection.CreateCommand(); // Check for DeletedBlock entries with invalid VolumeID @@ -101,7 +101,7 @@ namespace Duplicati.UnitTest WHERE VolumeID NOT IN (SELECT ID FROM RemoteVolume) "; - var invalidCount = (long)command.ExecuteScalar(); + var invalidCount = (long)await command.ExecuteScalarAsync(); // This assertion will fail with the current bug Assert.AreEqual(0, invalidCount, diff --git a/Duplicati/UnitTest/Issue6553.cs b/Duplicati/UnitTest/Issue6553.cs index e0966b36a..511b39749 100644 --- a/Duplicati/UnitTest/Issue6553.cs +++ b/Duplicati/UnitTest/Issue6553.cs @@ -5,6 +5,7 @@ using NUnit.Framework; using Duplicati.Library.Main; using Microsoft.Data.Sqlite; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -12,7 +13,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Issue6553")] - public void RecreateDbShouldNotLoseFilesetWhenDindexLacksDblock() + public async Task RecreateDbShouldNotLoseFilesetWhenDindexLacksDblockAsync() { // Arrange var testopts = TestOptions; @@ -28,7 +29,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count()); } @@ -47,7 +48,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count()); } @@ -63,7 +64,7 @@ namespace Duplicati.UnitTest { try { - var res = c.Repair(); + var res = await c.RepairAsync(); } catch (Exception) { @@ -74,7 +75,7 @@ namespace Duplicati.UnitTest // Step 5: Verify that we have at least one version (Backup 2) using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var listRes = c.List(); + var listRes = await c.ListAsync(); Assert.IsTrue(listRes.Filesets.Count() > 0, "Expected at least one backup version after recreate, but found none"); @@ -83,7 +84,7 @@ namespace Duplicati.UnitTest // Step 6: Check for DeletedBlock entries pointing to Temporary volumes using (var connection = new SqliteConnection($"Data Source={testopts["dbpath"]};Pooling=false")) { - connection.Open(); + await connection.OpenAsync(); var command = connection.CreateCommand(); command.CommandText = @" @@ -91,7 +92,7 @@ namespace Duplicati.UnitTest FROM DeletedBlock WHERE VolumeID IN (SELECT ID FROM RemoteVolume WHERE State = 'Temporary') "; - var tempCount = (long)command.ExecuteScalar(); + var tempCount = (long)await command.ExecuteScalarAsync(); // With the fix (and apparently even without it in this environment), this should be 0 Assert.AreEqual(0, tempCount, "Should not have DeletedBlock entries pointing to Temporary volumes"); @@ -101,7 +102,7 @@ namespace Duplicati.UnitTest FROM DeletedBlock WHERE VolumeID NOT IN (SELECT ID FROM RemoteVolume) "; - var invalidCount = (long)command.ExecuteScalar(); + var invalidCount = (long)await command.ExecuteScalarAsync(); Assert.AreEqual(0, invalidCount, "Should not have DeletedBlock entries with invalid VolumeID references"); } } diff --git a/Duplicati/UnitTest/Issue6688.cs b/Duplicati/UnitTest/Issue6688.cs index e2fa67b90..d0e35a223 100644 --- a/Duplicati/UnitTest/Issue6688.cs +++ b/Duplicati/UnitTest/Issue6688.cs @@ -26,6 +26,7 @@ using NUnit.Framework; using Duplicati.Library.Main; using Microsoft.Data.Sqlite; using Assert = NUnit.Framework.Legacy.ClassicAssert; +using System.Threading.Tasks; namespace Duplicati.UnitTest { @@ -41,7 +42,7 @@ namespace Duplicati.UnitTest [Test] [Category("Issue6688")] [Category("Recreate")] - public void RecreateDbShouldNotReferenceTemporaryVolumesInDeletedBlock() + public async Task RecreateDbShouldNotReferenceTemporaryVolumesInDeletedBlockAsync() { // Arrange var testopts = TestOptions; @@ -56,10 +57,10 @@ namespace Duplicati.UnitTest // Step 1: Create a file with enough content to create blocks and perform initial backup var testContent = string.Join("\n", Enumerable.Range(0, 100).Select(i => $"Line {i}: Test content for issue 6688 - this is test data to ensure we have enough content to create blocks.")); File.WriteAllText(Path.Combine(sourceFolder, "test.txt"), testContent); - + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count(), "Initial backup should succeed"); } @@ -67,7 +68,7 @@ namespace Duplicati.UnitTest var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*"); Assert.IsTrue(dblockFiles.Length > 0, "Should have at least one dblock file"); var dblockPath = dblockFiles[0]; - + // Get the dindex files var dindexFiles = Directory.GetFiles(TARGETFOLDER, "*.dindex.*"); Assert.IsTrue(dindexFiles.Length > 0, "Should have at least one dindex file"); @@ -78,35 +79,35 @@ namespace Duplicati.UnitTest // Step 3: Delete the dblock file (simulate corruption/missing file) // Keep the dindex file which now references a missing dblock File.Delete(dblockPath); - - TestContext.Progress.WriteLine($"Deleted dblock: {Path.GetFileName(dblockPath)}"); - TestContext.Progress.WriteLine($"Remaining dindex files: {string.Join(", ", dindexFiles.Select(Path.GetFileName))}"); + + await TestContext.Progress.WriteLineAsync($"Deleted dblock: {Path.GetFileName(dblockPath)}"); + await TestContext.Progress.WriteLineAsync($"Remaining dindex files: {string.Join(", ", dindexFiles.Select(Path.GetFileName))}"); // Step 4: Recreate the database using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { try { - var res = c.Repair(); - TestContext.Progress.WriteLine($"Repair result: {res.ParsedResult}"); - + var res = await c.RepairAsync(); + await TestContext.Progress.WriteLineAsync($"Repair result: {res.ParsedResult}"); + // The operation may succeed with warnings, but we need to verify DB state } catch (Exception ex) { // It's acceptable for repair to fail with a clear error - TestContext.Progress.WriteLine($"Repair failed: {ex.Message}"); + await TestContext.Progress.WriteLineAsync($"Repair failed: {ex.Message}"); } } // Step 5: Verify the database does not contain invalid references VerifyNoInvalidDeletedBlockReferences(testopts["dbpath"]); } - + [Test] [Category("Issue6688")] [Category("Recreate")] - public void RecreateDbShouldNotReferenceTemporaryVolumes_WhenDindexReferencesMissingDblock() + public async Task RecreateDbShouldNotReferenceTemporaryVolumes_WhenDindexReferencesMissingDblockAsync() { // This test more closely reproduces the issue scenario: // 1. Create two backups (versions) @@ -114,7 +115,7 @@ namespace Duplicati.UnitTest // 3. Keep the first version's dindex file (which references the deleted dblock) // 4. Delete the database // 5. Recreate - this should handle the missing dblock gracefully - + var testopts = TestOptions; testopts["backup-test-samples"] = "0"; testopts["no-encryption"] = "true"; @@ -125,10 +126,10 @@ namespace Duplicati.UnitTest // Step 1: Create file A and backup (Version 1) File.WriteAllText(Path.Combine(sourceFolder, "A.txt"), "Content A - version 1"); - + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count(), "First backup should succeed"); } @@ -138,7 +139,7 @@ namespace Duplicati.UnitTest var dindex1Path = dindexFiles[0]; var savedDindexPath = Path.Combine(DATAFOLDER, "dindex1_backup.zip"); File.Copy(dindex1Path, savedDindexPath, true); - + // Get the first backup's dblock var dblockFiles1 = Directory.GetFiles(TARGETFOLDER, "*.dblock.*"); Assert.IsTrue(dblockFiles1.Length > 0, "Should have dblock after first backup"); @@ -146,45 +147,45 @@ namespace Duplicati.UnitTest // Step 2: Modify file and backup again (Version 2) File.WriteAllText(Path.Combine(sourceFolder, "A.txt"), "Content A - version 2 - modified"); - + using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new[] { sourceFolder }); + var res = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, res.Errors.Count(), "Second backup should succeed"); } // Step 3: Delete the database and prepare for recreate File.Delete(testopts["dbpath"]); - + // Delete the first dblock file (simulating missing dblock) File.Delete(firstDblock); - + // Copy the old dindex back (it references the now-deleted dblock) // Rename it to ensure it's processed early var earlyDindexPath = Path.Combine(TARGETFOLDER, "duplicati-00000000000000000000000000000000.dindex.zip"); File.Copy(savedDindexPath, earlyDindexPath, true); - - TestContext.Progress.WriteLine($"Deleted first dblock: {Path.GetFileName(firstDblock)}"); - TestContext.Progress.WriteLine($"Restored old dindex: {Path.GetFileName(savedDindexPath)}"); + + await TestContext.Progress.WriteLineAsync($"Deleted first dblock: {Path.GetFileName(firstDblock)}"); + await TestContext.Progress.WriteLineAsync($"Restored old dindex: {Path.GetFileName(savedDindexPath)}"); // Step 4: Recreate the database using (var c = new Controller("file://" + TARGETFOLDER, testopts, null)) { try { - var res = c.Repair(); - TestContext.Progress.WriteLine($"Repair result: {res.ParsedResult}"); + var res = await c.RepairAsync(); + await TestContext.Progress.WriteLineAsync($"Repair result: {res.ParsedResult}"); } catch (Exception ex) { - TestContext.Progress.WriteLine($"Repair failed: {ex.Message}"); + await TestContext.Progress.WriteLineAsync($"Repair failed: {ex.Message}"); } } // Step 5: Verify the database does not contain invalid references VerifyNoInvalidDeletedBlockReferences(testopts["dbpath"]); } - + private void VerifyNoInvalidDeletedBlockReferences(string dbPath) { using var connection = new SqliteConnection($"Data Source={dbPath};Pooling=false"); @@ -206,11 +207,11 @@ namespace Duplicati.UnitTest WHERE VolumeID NOT IN (SELECT ID FROM RemoteVolume) "; var orphanedCount = (long)command.ExecuteScalar(); - + // Log diagnostic information TestContext.Progress.WriteLine($"DeletedBlock entries referencing Temporary volumes: {tempCount}"); TestContext.Progress.WriteLine($"DeletedBlock entries with orphaned VolumeID: {orphanedCount}"); - + // List all RemoteVolumes and their states command.CommandText = @" SELECT ID, Name, State, Type FROM RemoteVolume ORDER BY Type, Name @@ -227,7 +228,7 @@ namespace Duplicati.UnitTest TestContext.Progress.WriteLine($" ID={id}, Name={name}, State={state}, Type={type}"); } } - + // List all DeletedBlock entries command.CommandText = @" SELECT db.ID, db.Hash, db.Size, db.VolumeID, rv.State @@ -249,7 +250,7 @@ namespace Duplicati.UnitTest } // These assertions will fail if the bug exists - Assert.AreEqual(0, tempCount, + Assert.AreEqual(0, tempCount, "DeletedBlock table should not contain entries referencing Temporary volumes. " + $"Found {tempCount} invalid entries referencing Temporary volumes."); diff --git a/Duplicati/UnitTest/Issue6705.cs b/Duplicati/UnitTest/Issue6705.cs index 7ec306a1b..291e400d6 100644 --- a/Duplicati/UnitTest/Issue6705.cs +++ b/Duplicati/UnitTest/Issue6705.cs @@ -100,7 +100,7 @@ public class Issue6705 : BasicSetupHelper await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); try { - System.Net.Dns.GetHostEntry(new System.Uri(url).Host); + await System.Net.Dns.GetHostEntryAsync(new System.Uri(url).Host); } catch (Exception) { @@ -140,7 +140,7 @@ public class Issue6705 : BasicSetupHelper */ [OneTimeSetUp] - public void DownloadAndExtractBackups() + public async Task DownloadAndExtractBackupsAsync() { bool remote = true; // Set to false to use local files instead of downloading from S3 var localZipPath = "D:\\git\\duplicati-carl"; @@ -152,7 +152,7 @@ public class Issue6705 : BasicSetupHelper { zipFilepath = ZipPath(os); var url = $"https://testfiles.duplicati.com/cross-os-backups/{os}{zipPostfix}.zip"; - DownloadS3FileIfNewerAsync(zipFilepath, url).GetAwaiter().GetResult(); + await DownloadS3FileIfNewerAsync(zipFilepath, url); } var extractedPath = ExtractedBasePath(os); Directory.CreateDirectory(extractedPath); @@ -162,7 +162,7 @@ public class Issue6705 : BasicSetupHelper [Test] [Category("Targeted")] - public async Task RestoreAcrossOperatingSystems( + public async Task RestoreAcrossOperatingSystemsAsync( [Values("Windows", "Linux", "MacOS")] string original_os, [Values(true, false)] bool use_packed_db, [Values(true, false)] bool use_legacy_restore, @@ -185,7 +185,7 @@ public class Issue6705 : BasicSetupHelper testopts["dbpath"] = Path.Combine(extractedPath, "db.sqlite"); using var c = new Library.Main.Controller($"file://{extractedPath}/backup", testopts, null); - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); // Verify restored files TestUtils.AssertDirectoryTreesAreEquivalent(Path.Combine(extractedPath, "original"), RESTOREFOLDER, false, "Restored files do not match original files"); diff --git a/Duplicati/UnitTest/Issue6820.cs b/Duplicati/UnitTest/Issue6820.cs index 7677a0241..7782c83cc 100644 --- a/Duplicati/UnitTest/Issue6820.cs +++ b/Duplicati/UnitTest/Issue6820.cs @@ -181,7 +181,7 @@ namespace Duplicati.UnitTest [Category("Disruption")] [Category("Issue6820")] [Category("ExcludedFromCLI")] - public async Task StuckCompletingPreviousBackupWithTwoTemporaryDlists() + public async Task StuckCompletingPreviousBackupWithTwoTemporaryDlists_Async() { var options = new Dictionary(this.TestOptions) { @@ -194,19 +194,19 @@ namespace Duplicati.UnitTest this.WriteSourceFiles(seed: 1); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "First backup should succeed"); } this.WriteSourceFiles(seed: 2); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "Second backup should succeed"); } var beforeInject = GetIncompleteDlistVolumes(this.DBFILE); - TestContext.Progress.WriteLine($"Incomplete dlists before injection: {beforeInject.Count}"); + await TestContext.Progress.WriteLineAsync($"Incomplete dlists before injection: {beforeInject.Count}"); Assert.AreEqual(0, beforeInject.Count, "After successful backups, no dlist should be Temporary"); @@ -218,12 +218,12 @@ namespace Duplicati.UnitTest // 2. Inject the "stuck" state: two Temporary dlists + matching Fileset rows. var newName = InjectDoubleTemporaryDlistState(this.DBFILE); - TestContext.Progress.WriteLine($"Injected second Temporary dlist name: {newName}"); + await TestContext.Progress.WriteLineAsync($"Injected second Temporary dlist name: {newName}"); var afterInject = GetIncompleteDlistVolumes(this.DBFILE); - TestContext.Progress.WriteLine($"Incomplete dlists after injection: {afterInject.Count}"); + await TestContext.Progress.WriteLineAsync($"Incomplete dlists after injection: {afterInject.Count}"); foreach (var v in afterInject) - TestContext.Progress.WriteLine($" ID={v.Id} Name={v.Name} State={v.State}"); + await TestContext.Progress.WriteLineAsync($" ID={v.Id} Name={v.Name} State={v.State}"); Assert.AreEqual(2, afterInject.Count, "After injecting, there should be exactly two Temporary dlist rows " + "(reproducing the state reported in issue 6820)"); @@ -236,7 +236,7 @@ namespace Duplicati.UnitTest var path = Path.Combine(this.TARGETFOLDER, v.Name); if (File.Exists(path)) { - TestContext.Progress.WriteLine($"Removing backend dlist: {v.Name}"); + await TestContext.Progress.WriteLineAsync($"Removing backend dlist: {v.Name}"); File.Delete(path); } } @@ -265,12 +265,12 @@ namespace Duplicati.UnitTest Exception backupException = null; var stopwatch = System.Diagnostics.Stopwatch.StartNew(); - var backupTask = Task.Run(() => + var backupTask = Task.Run(async () => { try { using var c = new Controller("file://" + this.TARGETFOLDER, recoveryOptions, null); - backupResults = c.Backup(new[] { this.DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); } catch (Exception ex) { @@ -281,7 +281,7 @@ namespace Duplicati.UnitTest var completed = await Task.WhenAny(backupTask, Task.Delay(timeout)).ConfigureAwait(false); stopwatch.Stop(); - TestContext.Progress.WriteLine( + await TestContext.Progress.WriteLineAsync( $"Recovery backup finished after {stopwatch.Elapsed.TotalSeconds:F2}s"); // Report whether we actually took the synthetic-filelist path, @@ -295,7 +295,7 @@ namespace Duplicati.UnitTest { var lines = File.ReadAllLines(recoveryLogPath); syntheticTaken = lines.Any(l => l.Contains("PreviousBackupFilelistUpload")); - TestContext.Progress.WriteLine( + await TestContext.Progress.WriteLineAsync( syntheticTaken ? "Synthetic filelist path was taken" : "WARNING: Synthetic filelist path was NOT taken - the test did not " + @@ -337,23 +337,23 @@ namespace Duplicati.UnitTest lastLine = line; } - TestContext.Progress.WriteLine( + await TestContext.Progress.WriteLineAsync( $"Longest silent gap after synthetic-filelist begin: {longestGap.TotalSeconds:F1}s"); if (longestGap.TotalMilliseconds > 0) - TestContext.Progress.WriteLine($" preceded by: {longestGapContext}"); + await TestContext.Progress.WriteLineAsync($" preceded by: {longestGapContext}"); // Copy the log out of the cleaned-up test folder for post-mortem. try { var keptLogPath = Path.Combine(Path.GetTempPath(), $"Issue6820.recovery.{DateTime.Now:yyyyMMddHHmmss}.log"); File.Copy(recoveryLogPath, keptLogPath, true); - TestContext.Progress.WriteLine($"Recovery log preserved at: {keptLogPath}"); + await TestContext.Progress.WriteLineAsync($"Recovery log preserved at: {keptLogPath}"); } catch { } } else { - TestContext.Progress.WriteLine( + await TestContext.Progress.WriteLineAsync( "WARNING: Recovery log file was not created - cannot verify code path"); } @@ -365,11 +365,11 @@ namespace Duplicati.UnitTest { // Report the state we're stuck in, for easier diagnosis. var stuckState = GetIncompleteDlistVolumes(this.DBFILE); - TestContext.Progress.WriteLine( + await TestContext.Progress.WriteLineAsync( "Backup did not complete within the timeout. " + "Incomplete dlist rows at timeout:"); foreach (var v in stuckState) - TestContext.Progress.WriteLine($" ID={v.Id} Name={v.Name} State={v.State}"); + await TestContext.Progress.WriteLineAsync($" ID={v.Id} Name={v.Name} State={v.State}"); Assert.Fail( $"Backup did not complete within {timeout.TotalSeconds:F0}s - " + "reproduces 'stuck in completing previous backup' (issue 6820). " + @@ -387,9 +387,9 @@ namespace Duplicati.UnitTest // Final state: no dlist should remain Temporary. The recovery code should // have either uploaded or marked for deletion the previously-Temporary rows. var finalState = GetIncompleteDlistVolumes(this.DBFILE); - TestContext.Progress.WriteLine($"Final incomplete dlist rows: {finalState.Count}"); + await TestContext.Progress.WriteLineAsync($"Final incomplete dlist rows: {finalState.Count}"); foreach (var v in finalState) - TestContext.Progress.WriteLine($" ID={v.Id} Name={v.Name} State={v.State}"); + await TestContext.Progress.WriteLineAsync($" ID={v.Id} Name={v.Name} State={v.State}"); Assert.AreEqual(0, finalState.Count, "After a successful backup, no dlist should remain in Temporary/Uploading state"); } diff --git a/Duplicati/UnitTest/IssueTests.cs b/Duplicati/UnitTest/IssueTests.cs index ad9311a27..078c75ea2 100644 --- a/Duplicati/UnitTest/IssueTests.cs +++ b/Duplicati/UnitTest/IssueTests.cs @@ -32,6 +32,7 @@ using System.Security.AccessControl; using System.Security.Cryptography; using System.Security.Principal; using System.Threading; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -41,7 +42,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void Issue5023ReferencedFileMissing([Values] bool compactBeforeRecreate) + public async Task Issue5023ReferencedFileMissingAsync([Values] bool compactBeforeRecreate) { // Reproduction for part of issue #5023 // Error during repair: "Remote file referenced as x by y, but not found in list, registering a missing remote file" @@ -64,10 +65,8 @@ namespace Duplicati.UnitTest string file1 = Path.Combine(DATAFOLDER, "f1"); TestUtils.WriteTestFile(file1, filesize); using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); + // Sleep to ensure timestamps are different Thread.Sleep(1000); // Fail after index file put @@ -86,12 +85,10 @@ namespace Duplicati.UnitTest BackendLoader.AddBackend(new DeterministicErrorBackend()); string file2 = Path.Combine(DATAFOLDER, "f2"); TestUtils.WriteTestFile(file2, filesize); - var uploadEx = Assert.Catch(() => + var uploadEx = Assert.CatchAsync(async () => { using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - } + await c.BackupAsync(new[] { DATAFOLDER }); }); while (uploadEx is AggregateException && uploadEx.InnerException is not null) @@ -105,10 +102,8 @@ namespace Duplicati.UnitTest // Complete upload target = "file://" + TARGETFOLDER; using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); + // At this point there are two index files for the last dblock Console.WriteLine("Target folder contents (expect extra index file):"); Console.WriteLine(string.Join("\n", from v in Directory.EnumerateFiles(TARGETFOLDER) select Path.GetFileName(v))); @@ -125,9 +120,9 @@ namespace Duplicati.UnitTest File.Delete(file2); using (var c = new Controller(target, testopts, null)) { - var backupResults = c.Backup(new[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backupResults); - ICompactResults compactResults = c.Compact(); + ICompactResults compactResults = await c.CompactAsync(); TestUtils.AssertResults(compactResults); Assert.Greater(compactResults.DeletedFileCount, 0); } @@ -139,16 +134,13 @@ namespace Duplicati.UnitTest // Database recreate should work (fails after compact) File.Delete(DBFILE); using (var c = new Library.Main.Controller(target, testopts, null)) - { - IRepairResults repairResults = c.Repair(); - TestUtils.AssertResults(repairResults); - } + TestUtils.AssertResults(await c.RepairAsync()); } [Test] [Category("Restore"), Category("Bug")] - public void Issue5825RestoreNoOverwrite([Values] bool legacy, [Values] bool local_blocks) + public async Task Issue5825RestoreNoOverwriteAsync([Values] bool legacy, [Values] bool local_blocks) { // Reproduction of Issue #5825 // The logic in the previous version was to create a timestamped version of the file being restored to, if it already exists. @@ -171,16 +163,13 @@ namespace Duplicati.UnitTest // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Attempt to restore the file testopts["restore-path"] = RESTOREFOLDER; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f0]); + var restoreResults = await c.RestoreAsync([f0]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); } @@ -195,7 +184,7 @@ namespace Duplicati.UnitTest testopts["overwrite"] = "true"; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f0]); + var restoreResults = await c.RestoreAsync([f0]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); } @@ -215,7 +204,7 @@ namespace Duplicati.UnitTest // Restore the file again, with overwrite, should restore the timestamp of the file using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f0]); + var restoreResults = await c.RestoreAsync([f0]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(0), "File should not have been restored, only the metadata."); } @@ -231,7 +220,7 @@ namespace Duplicati.UnitTest testopts["overwrite"] = "false"; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f0]); + var restoreResults = await c.RestoreAsync([f0]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); } @@ -250,7 +239,7 @@ namespace Duplicati.UnitTest // Restore the file again, without overwrite. using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f0]); + var restoreResults = await c.RestoreAsync([f0]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored"); } @@ -272,7 +261,7 @@ namespace Duplicati.UnitTest // Restore the file again, without overwrite. using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f0]); + var restoreResults = await c.RestoreAsync([f0]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(0), "File should not have been restored"); } @@ -283,7 +272,7 @@ namespace Duplicati.UnitTest [Test] [Category("Restore"), Category("Bug")] - public void Issue5826RestoreMissingFolder([Values] bool compressRestorePaths, [Values] bool restoreLegacy) + public async Task Issue5826RestoreMissingFolderAsync([Values] bool compressRestorePaths, [Values] bool restoreLegacy) { // Reproduction of Issue #5826 // With the new restore engine, it looks like a file will not be restored if the folder is not selected for restore. @@ -302,16 +291,13 @@ namespace Duplicati.UnitTest // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Attempt to restore a file without selecting its parent folder testopts["restore-path"] = RESTOREFOLDER; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f]); + var restoreResults = await c.RestoreAsync([f]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored."); Assert.That(restoreResults.Warnings.Count(), Is.EqualTo(compressRestorePaths ? 0 : 1), "Warning should be generated for missing folder"); } @@ -320,7 +306,7 @@ namespace Duplicati.UnitTest [Test] [Category("Restore"), Category("Bug")] - public void Issue5886RestoreModifiedMiddleBlock() + public async Task Issue5886RestoreModifiedMiddleBlockAsync() { var blocksize = 1024; var n_blocks = 5; @@ -337,10 +323,7 @@ namespace Duplicati.UnitTest // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); var original_contents = File.ReadAllBytes(f); var new_block = new byte[blocksize]; @@ -372,7 +355,7 @@ namespace Duplicati.UnitTest // Attempt to restore the file using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f]); + var restoreResults = await c.RestoreAsync([f]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored."); } @@ -385,7 +368,7 @@ namespace Duplicati.UnitTest [Test] [Category("Restore"), Category("Bug")] - public void Issue5957RestoreModifiedBlockUseLocalBlocks([Values] bool use_local, [Values] bool overwrite) + public async Task Issue5957RestoreModifiedBlockUseLocalBlocksAsync([Values] bool use_local, [Values] bool overwrite) { var blocksize = 1024; var testopts = new Dictionary(TestOptions) @@ -402,22 +385,19 @@ namespace Duplicati.UnitTest // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); var original_contents = File.ReadAllBytes(f); using (var stream = File.Open(f, FileMode.Open, FileAccess.ReadWrite)) { stream.Seek(0, SeekOrigin.Begin); - byte[] buffer = new byte[1]; - if (stream.Read(buffer, 0, 1) < 1) + var buffer = new byte[1]; + if (await stream.ReadAsync(buffer, 0, 1) < 1) throw new InvalidOperationException("Failed to read from test file"); buffer[0] = (byte)~buffer[0]; stream.Seek(0, SeekOrigin.Begin); - stream.Write(buffer, 0, 1); + await stream.WriteAsync(buffer, 0, 1); } var restored_contents = File.ReadAllBytes(f); @@ -426,7 +406,7 @@ namespace Duplicati.UnitTest // Attempt to restore the file using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([f]); + var restoreResults = await c.RestoreAsync([f]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(1), "File should have been restored."); } @@ -449,7 +429,7 @@ namespace Duplicati.UnitTest [Test] [Category("Restore"), Category("Bug")] - public void Issue6068FolderMetadata([Values] bool restoreLegacy) + public async Task Issue6068FolderMetadataAsync([Values] bool restoreLegacy) { // Reproduction of Issue #6068 // The folder metadata is not restored @@ -463,20 +443,17 @@ namespace Duplicati.UnitTest // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Sleep to ensure timestamps are different - System.Threading.Tasks.Task.Delay(1000).Wait(); + await Task.Delay(1000); // Attempt to restore to another folder testopts["restore-path"] = RESTOREFOLDER; testopts["restore-legacy"] = restoreLegacy.ToString().ToLower(); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var restoreResults = c.Restore([]); + var restoreResults = await c.RestoreAsync([]); Assert.That(restoreResults.RestoredFiles, Is.EqualTo(0), "No files should have been restored."); } @@ -490,7 +467,7 @@ namespace Duplicati.UnitTest [Test] [Category("Test"), Category("Bug")] - public void Issue6459HashNullInTest() + public async Task Issue6459HashNullInTestAsync() { // Reproduction of the issue outlined in https://forum.duplicati.com/t/value-cannot-be-null-parameter-hash-cannot-be-null/20958 // This test is to ensure that a null hash in the database does not cause issues during backup and repair operations. @@ -507,47 +484,38 @@ namespace Duplicati.UnitTest // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Set one of the hashes in the database to null { using var con = new SqliteConnection($"Data source={DBFILE};Pooling=false"); - con.Open(); + await con.OpenAsync(); using var cmd = con.CreateCommand(); using var transaction = con.BeginTransaction(); cmd.Transaction = transaction; cmd.CommandText = @"UPDATE Remotevolume SET Hash = NULL WHERE Name LIKE '%.dblock.%'"; - cmd.ExecuteNonQuery(); - transaction.Commit(); + await cmd.ExecuteNonQueryAsync(); + await transaction.CommitAsync(); } // Add another file and perform the backup again. // This would fail prior to the fix. TestUtils.WriteTestFile($"{f}_1.txt", 1024); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Delete and recreate the database to ensure the null hash is not present anymore File.Delete(DBFILE); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IRepairResults repairResults = c.Repair(); - TestUtils.AssertResults(repairResults); - } + TestUtils.AssertResults(await c.RepairAsync()); // Double check that the null hash is not present anymore { using var con = new SqliteConnection($"Data source={DBFILE};Pooling=false"); - con.Open(); + await con.OpenAsync(); using var cmd = con.CreateCommand(); cmd.CommandText = @"SELECT COUNT(*) FROM Remotevolume WHERE Hash IS NULL"; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.That(count, Is.EqualTo(0), "There should be no null hashes in the database."); } @@ -555,7 +523,7 @@ namespace Duplicati.UnitTest TestUtils.WriteTestFile($"{f}_2.txt", 1024); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup([DATAFOLDER]); + IBackupResults backupResults = await c.BackupAsync([DATAFOLDER]); TestUtils.AssertResults(backupResults); } } @@ -563,7 +531,7 @@ namespace Duplicati.UnitTest [Test] [Category("Restore"), Category("Bug")] - public void Issue6068FileAndFolderAttributesAndPermissions([Values] bool restorePermissions, [Values] bool restoreLegacy, [Values] bool skip_metadata) + public async Task Issue6068FileAndFolderAttributesAndPermissionsAsync([Values] bool restorePermissions, [Values] bool restoreLegacy, [Values] bool skip_metadata) { // This test is to verify that permissions are restored correctly // if the restore-permissions option is set, and that the @@ -653,17 +621,12 @@ namespace Duplicati.UnitTest { // Backup the files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - IBackupResults backupResults = c.Backup([DATAFOLDER]); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Restore the files testopts["restore-path"] = RESTOREFOLDER; using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - { - var restoreResults = c.Restore([]); - } + await c.RestoreAsync([]); // Check the folder attributes foreach (var dir in dirs.Skip(1)) @@ -806,7 +769,7 @@ namespace Duplicati.UnitTest [Test] [Category("Disruption"), Category("Bug")] - public void TestSystematicErrors5023() + public async Task TestSystematicErrors5023Async() { // Attempt to recreate other bugs from #5023, but not successful var testopts = new Dictionary(TestOptions) @@ -843,10 +806,8 @@ namespace Duplicati.UnitTest } // Initial backup using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); + while (errorIdx < (maxFiles + 2)) { if (errorIdx % 10 == 0) @@ -857,10 +818,7 @@ namespace Duplicati.UnitTest try { using (var c = new Library.Main.Controller(targetError, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); } catch (AssertionException) { throw; } catch { } @@ -868,10 +826,7 @@ namespace Duplicati.UnitTest try { using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); } catch (UserInformationException e) { @@ -883,10 +838,7 @@ namespace Duplicati.UnitTest File.Delete(DBFILE); using (var c = new Library.Main.Controller(target, testopts, null)) - { - IRepairResults repairResults = c.Repair(); - TestUtils.AssertResults(repairResults); - } + TestUtils.AssertResults(await c.RepairAsync()); } else if (e.HelpID == "DatabaseTimestampError") { @@ -914,7 +866,7 @@ namespace Duplicati.UnitTest [Test, Sequential] [Category("Targeted"), Category("Bug"), Category("Non-critical")] [TestCase(false, true), TestCase(true, true), TestCase(true, false)] - public void Issue5038MissingListBlocklist(bool sameVersion, bool blockFirst) + public async Task Issue5038MissingListBlocklistAsync(bool sameVersion, bool blockFirst) { // Backup containing the blocklist of a file BEFORE the file causes a dindex with missing blocklist entry // This is not critical, because it only requires extra block volume downloads @@ -944,26 +896,20 @@ namespace Duplicati.UnitTest { // Backup blockfile first using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); } byte[] combined = block1.Concat(block2).ToArray(); TestUtils.WriteFile(filename, combined); // Backup file that would produce blockfile using (var c = new Library.Main.Controller(target, testopts, null)) - { - IBackupResults backupResults = c.Backup(new[] { DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + TestUtils.AssertResults(await c.BackupAsync(new[] { DATAFOLDER })); // Recreate database downloads block volume File.Delete(DBFILE); using (var c = new Library.Main.Controller(target, testopts, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); Assert.IsNull(repairResults.Messages.FirstOrDefault(v => v.Contains("ProcessingRequiredBlocklistVolumes")), "Blocklist download pass was required"); @@ -971,21 +917,21 @@ namespace Duplicati.UnitTest } [Test] - public void Issue6817DollarSignNumberInFilenameBreaksRegex() + public async Task Issue6817DollarSignNumberInFilenameBreaksRegexAsync() { var filename = "~$1234567891011121314151617181920.txt"; var content = RandomNumberGenerator.GetBytes(1024); TestUtils.WriteFile(Path.Combine(DATAFOLDER, filename), content); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // Delete the database File.Delete(DBFILE); // Recreate the database, which would fail if the filename is not handled correctly using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, TestOptions, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); } } } diff --git a/Duplicati/UnitTest/LocalDatabaseRemoveRemoteVolumesTests.cs b/Duplicati/UnitTest/LocalDatabaseRemoveRemoteVolumesTests.cs index 358bac42f..2e1857b85 100644 --- a/Duplicati/UnitTest/LocalDatabaseRemoveRemoteVolumesTests.cs +++ b/Duplicati/UnitTest/LocalDatabaseRemoveRemoteVolumesTests.cs @@ -52,10 +52,10 @@ namespace Duplicati.UnitTest /// [Test] [Category("Database")] - public async Task RemoveRemoteVolumes_WithOrphanedFilesetEntry_ThrowsConstraintException() + public async Task RemoveRemoteVolumes_WithOrphanedFilesetEntry_ThrowsConstraintExceptionAsync() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); // Use DatabaseUpgrader to create the schema from embedded resources DatabaseUpgrader.UpgradeDatabase(db, dbfile, typeof(DatabaseSchemaMarker)); @@ -64,35 +64,35 @@ namespace Duplicati.UnitTest // Insert an operation record (required for LocalDatabase initialization) cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES ('Test', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a block volume (ID=1) - this is the volume we will delete cmd.CommandText = @" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES (1, 1, 'block-volume.zip', 'Blocks', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a fileset volume (ID=2) - this volume is NOT being deleted cmd.CommandText = @" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES (2, 1, 'fileset-volume.zip', 'Files', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a fileset that references the fileset volume (NOT being deleted) cmd.CommandText = @" INSERT INTO ""Fileset"" (""ID"", ""OperationID"", ""VolumeID"", ""IsFullBackup"", ""Timestamp"") VALUES (1, 1, 2, 1, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert an orphaned FilesetEntry - this references a FileID (99999) that does NOT exist in FileLookup // This simulates a database corruption scenario cmd.CommandText = @" INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (1, 99999, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Close the connection so LocalDatabase can open it - db.Close(); + await db.CloseAsync(); // Create LocalDatabase instance and attempt to remove the block volume await using var localDb = await LocalDatabase.CreateLocalDatabaseAsync( @@ -110,7 +110,7 @@ namespace Duplicati.UnitTest ConstraintException? caughtException = null; try { - await localDb.RemoveRemoteVolumes(new[] { "block-volume.zip" }, CancellationToken.None); + await localDb.RemoveRemoteVolumesAsync(new[] { "block-volume.zip" }, CancellationToken.None); } catch (ConstraintException ex) { @@ -128,10 +128,10 @@ namespace Duplicati.UnitTest /// [Test] [Category("Database")] - public async Task RemoveRemoteVolumes_WithValidFilesetEntry_Succeeds() + public async Task RemoveRemoteVolumes_WithValidFilesetEntry_Succeeds_Async() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); // Use DatabaseUpgrader to create the schema from embedded resources DatabaseUpgrader.UpgradeDatabase(db, dbfile, typeof(DatabaseSchemaMarker)); @@ -140,16 +140,16 @@ namespace Duplicati.UnitTest // Insert an operation record cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES ('Test', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a remote volume that we will try to remove cmd.CommandText = @" INSERT INTO ""Remotevolume"" (""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES (1, 'test-volume.zip', 'Blocks', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Close the connection so LocalDatabase can open it - db.Close(); + await db.CloseAsync(); // Create LocalDatabase instance and attempt to remove the volume await using var localDb = await LocalDatabase.CreateLocalDatabaseAsync( @@ -164,7 +164,7 @@ namespace Duplicati.UnitTest // (no FilesetEntry records exist, so no orphans can be created) Assert.DoesNotThrowAsync(async () => { - await localDb.RemoveRemoteVolumes(new[] { "test-volume.zip" }, CancellationToken.None); + await localDb.RemoveRemoteVolumesAsync(new[] { "test-volume.zip" }, CancellationToken.None); }); } @@ -177,10 +177,10 @@ namespace Duplicati.UnitTest /// [Test] [Category("Database")] - public async Task RemoveRemoteVolumes_WithMultipleOrphanedFilesetEntries_ThrowsConstraintExceptionWithCount() + public async Task RemoveRemoteVolumes_WithMultipleOrphanedFilesetEntries_ThrowsConstraintExceptionWithCount_Async() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); // Use DatabaseUpgrader to create the schema from embedded resources DatabaseUpgrader.UpgradeDatabase(db, dbfile, typeof(DatabaseSchemaMarker)); @@ -189,25 +189,25 @@ namespace Duplicati.UnitTest // Insert an operation record cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES ('Test', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a block volume (ID=1) - this is the volume we will delete cmd.CommandText = @" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES (1, 1, 'block-volume.zip', 'Blocks', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a fileset volume (ID=2) - this volume is NOT being deleted cmd.CommandText = @" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES (2, 1, 'fileset-volume.zip', 'Files', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a fileset that references the fileset volume (NOT being deleted) cmd.CommandText = @" INSERT INTO ""Fileset"" (""ID"", ""OperationID"", ""VolumeID"", ""IsFullBackup"", ""Timestamp"") VALUES (1, 1, 2, 1, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert 3 orphaned FilesetEntry records - these reference FileIDs that do NOT exist in FileLookup // This simulates a database corruption scenario with multiple orphaned records @@ -215,10 +215,10 @@ namespace Duplicati.UnitTest INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (1, 99999, 0); INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (1, 99998, 0); INSERT INTO ""FilesetEntry"" (""FilesetID"", ""FileID"", ""Lastmodified"") VALUES (1, 99997, 0);"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Close the connection so LocalDatabase can open it - db.Close(); + await db.CloseAsync(); // Create LocalDatabase instance await using var localDb = await LocalDatabase.CreateLocalDatabaseAsync( @@ -233,7 +233,7 @@ namespace Duplicati.UnitTest ConstraintException? caughtException = null; try { - await localDb.RemoveRemoteVolumes(new[] { "block-volume.zip" }, CancellationToken.None); + await localDb.RemoveRemoteVolumesAsync(new[] { "block-volume.zip" }, CancellationToken.None); } catch (ConstraintException ex) { @@ -252,10 +252,10 @@ namespace Duplicati.UnitTest /// [Test] [Category("Database")] - public async Task RemoveRemoteVolumes_WithLargeInput_UsesTemporaryTable() + public async Task RemoveRemoteVolumes_WithLargeInput_UsesTemporaryTable_Async() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); // Use DatabaseUpgrader to create the schema from embedded resources DatabaseUpgrader.UpgradeDatabase(db, dbfile, typeof(DatabaseSchemaMarker)); @@ -264,7 +264,7 @@ namespace Duplicati.UnitTest // Insert an operation record (required for LocalDatabase initialization) cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES ('Test', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Create 150 remote volumes (exceeds CHUNK_SIZE of 128) to trigger temporary table path // Set DeleteGraceTime to 0 so they can be deleted immediately (grace time has passed) @@ -278,11 +278,11 @@ namespace Duplicati.UnitTest cmd.CommandText = $@" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES ({i + 1}, 1, '{volumeName}', 'Blocks', 'Deleted', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } // Close the connection so LocalDatabase can open it - db.Close(); + await db.CloseAsync(); // Create LocalDatabase instance await using var localDb = await LocalDatabase.CreateLocalDatabaseAsync( @@ -297,7 +297,7 @@ namespace Duplicati.UnitTest // This should trigger the temporary table code path Assert.DoesNotThrowAsync(async () => { - await localDb.RemoveRemoteVolumes(volumeNames, CancellationToken.None); + await localDb.RemoveRemoteVolumesAsync(volumeNames, CancellationToken.None); }); // Assert: Verify all volumes were deleted @@ -305,7 +305,7 @@ namespace Duplicati.UnitTest // Since State is 'Deleted' and @State is 'Deleted', and DeleteGraceTime is 0, they should be deleted using var verifyCmd = localDb.Connection.CreateCommand(); verifyCmd.CommandText = @"SELECT COUNT(*) FROM ""Remotevolume"" WHERE ""Name"" LIKE 'test-volume-%'"; - var count = (long)verifyCmd.ExecuteScalar()!; + var count = (long)(await verifyCmd.ExecuteScalarAsync())!; Assert.That(count, Is.EqualTo(0), "All 150 volumes should have been deleted"); } } diff --git a/Duplicati/UnitTest/LocalListAffectedDatabaseTests.cs b/Duplicati/UnitTest/LocalListAffectedDatabaseTests.cs index 6efd6019b..48360ea93 100644 --- a/Duplicati/UnitTest/LocalListAffectedDatabaseTests.cs +++ b/Duplicati/UnitTest/LocalListAffectedDatabaseTests.cs @@ -40,13 +40,13 @@ namespace Duplicati.UnitTest public class LocalListAffectedDatabaseTests { /// - /// Tests that works correctly + /// Tests that works correctly /// with a large number of items that triggers the temporary table code path /// (when count > CHUNK_SIZE = 128). /// [Test] [Category("Database")] - public async Task GetLogLines_WithLargeItemCount_UsesTemporaryTable() + public async Task GetLogLines_WithLargeItemCount_UsesTemporaryTable_Async() { // Arrange using var tempFile = new TempFile(); @@ -59,10 +59,10 @@ namespace Duplicati.UnitTest using (var cmd = db.Connection.CreateCommand()) { // Insert operation entry first - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT OR IGNORE INTO Operation (ID, Description, Timestamp) VALUES (1, 'TestOperation', 0);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert 150 remote operation entries for (int i = 0; i < 150; i++) @@ -70,7 +70,7 @@ namespace Duplicati.UnitTest var path = $"/remote/path/file{i:D4}.zip"; remotePaths.Add(path); - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO RemoteOperation (OperationID, Timestamp, Operation, Path, Data) VALUES (@operationId, @timestamp, @operation, @path, @data);") .SetParameterValue("@operationId", 1L) @@ -78,7 +78,7 @@ namespace Duplicati.UnitTest .SetParameterValue("@operation", "put") .SetParameterValue("@path", path) .SetParameterValue("@data", $"Operation data for {path}") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert some log data that references these paths @@ -86,7 +86,7 @@ namespace Duplicati.UnitTest // log entries to match the first chunk for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO LogData (OperationID, Timestamp, Type, Message, Exception) VALUES (@operationId, @timestamp, @type, @message, @exception);") .SetParameterValue("@operationId", 1L) @@ -94,13 +94,13 @@ namespace Duplicati.UnitTest .SetParameterValue("@type", "Information") .SetParameterValue("@message", $"Log message referencing {remotePaths[i]}") .SetParameterValue("@exception", (string?)null) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } } // Act: Call GetLogLines with 150 items // This should trigger the temporary table code path - var results = await db.GetLogLines(remotePaths, CancellationToken.None) + var results = await db.GetLogLinesAsync(remotePaths, CancellationToken.None) .ToListAsync() .ConfigureAwait(false); @@ -109,13 +109,13 @@ namespace Duplicati.UnitTest } /// - /// Tests that works correctly + /// Tests that works correctly /// with a large number of items that triggers the temporary table code path /// (when count > CHUNK_SIZE = 128). /// [Test] [Category("Database")] - public async Task GetVolumes_WithLargeItemCount_UsesTemporaryTable() + public async Task GetVolumes_WithLargeItemCount_UsesTemporaryTable_Async() { // Arrange using var tempFile = new TempFile(); @@ -132,15 +132,15 @@ namespace Duplicati.UnitTest using (var cmd = db.Connection.CreateCommand()) { // Insert operation entry - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT OR IGNORE INTO Operation (ID, Description, Timestamp) VALUES (1, 'TestOperation', 0);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert 150 remote volumes for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO RemoteVolume (ID, OperationID, Name, Type, State, Size, VerificationCount, DeleteGraceTime, ArchiveTime, LockExpirationTime) VALUES (@id, @operationId, @name, @type, @state, @size, @verificationCount, @deleteGraceTime, @archiveTime, @lockExpirationTime);") .SetParameterValue("@id", i + 1) @@ -153,13 +153,13 @@ namespace Duplicati.UnitTest .SetParameterValue("@deleteGraceTime", 0) .SetParameterValue("@archiveTime", 0) .SetParameterValue("@lockExpirationTime", 0) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert fileset entries that reference these volumes for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Fileset (ID, OperationID, VolumeID, IsFullBackup, Timestamp) VALUES (@id, @operationId, @volumeId, @isFullBackup, @timestamp);") .SetParameterValue("@id", i + 1) @@ -167,56 +167,56 @@ namespace Duplicati.UnitTest .SetParameterValue("@volumeId", i + 1) .SetParameterValue("@isFullBackup", 1) .SetParameterValue("@timestamp", i) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert Block entries that reference these volumes for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Block (ID, VolumeID, Hash, Size) VALUES (@id, @volumeId, @hash, @size);") .SetParameterValue("@id", i + 1) .SetParameterValue("@volumeId", i + 1) .SetParameterValue("@hash", $"hash{i}") .SetParameterValue("@size", 1024) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert Blockset entries - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Blockset (ID, Length, FullHash) VALUES (1, 1024, 'fullhash');") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert BlocksetEntry entries that link blocks to blocksets for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO BlocksetEntry (BlocksetID, BlockID, ""Index"") VALUES (@blocksetId, @blockId, @idx);") .SetParameterValue("@blocksetId", 1) .SetParameterValue("@blockId", i + 1) .SetParameterValue("@idx", i) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert Metadataset entries - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO Metadataset (ID, BlocksetID) VALUES (1, 1);") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert PathPrefix entries - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO PathPrefix (ID, Prefix) VALUES (1, '/test/');") - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); // Insert FileLookup entries for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FileLookup (ID, PrefixID, Path, BlocksetID, MetadataID) VALUES (@id, @prefixId, @path, @blocksetId, @metadataId);") .SetParameterValue("@id", i + 1) @@ -224,25 +224,25 @@ namespace Duplicati.UnitTest .SetParameterValue("@path", $"file{i}.txt") .SetParameterValue("@blocksetId", 1) .SetParameterValue("@metadataId", 1) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } // Insert FilesetEntry entries for (int i = 0; i < 150; i++) { - cmd.SetCommandAndParameters(@" + await cmd.SetCommandAndParameters(@" INSERT INTO FilesetEntry (FilesetID, FileID, Lastmodified) VALUES (@filesetId, @fileId, @lastModified);") .SetParameterValue("@filesetId", i + 1) .SetParameterValue("@fileId", i + 1) .SetParameterValue("@lastModified", 0) - .ExecuteNonQuery(); + .ExecuteNonQueryAsync(); } } // Act: Call GetVolumes with 150 items // This should trigger the temporary table code path - var results = await db.GetVolumes(volumeNames, CancellationToken.None) + var results = await db.GetVolumesAsync(volumeNames, CancellationToken.None) .ToListAsync() .ConfigureAwait(false); @@ -251,12 +251,12 @@ namespace Duplicati.UnitTest } /// - /// Tests that returns empty + /// Tests that returns empty /// results when given an empty list of items. /// [Test] [Category("Database")] - public async Task GetLogLines_WithEmptyItems_ReturnsEmpty() + public async Task GetLogLines_WithEmptyItems_ReturnsEmpty_Async() { // Arrange using var tempFile = new TempFile(); @@ -264,7 +264,7 @@ namespace Duplicati.UnitTest .ConfigureAwait(false); // Act: Call GetLogLines with empty list - var results = await db.GetLogLines(Array.Empty(), CancellationToken.None) + var results = await db.GetLogLinesAsync(Array.Empty(), CancellationToken.None) .ToListAsync() .ConfigureAwait(false); @@ -273,12 +273,12 @@ namespace Duplicati.UnitTest } /// - /// Tests that returns empty + /// Tests that returns empty /// results when given an empty list of items. /// [Test] [Category("Database")] - public async Task GetVolumes_WithEmptyItems_ReturnsEmpty() + public async Task GetVolumes_WithEmptyItems_ReturnsEmpty_Async() { // Arrange using var tempFile = new TempFile(); @@ -286,7 +286,7 @@ namespace Duplicati.UnitTest .ConfigureAwait(false); // Act: Call GetVolumes with empty list - var results = await db.GetVolumes(Array.Empty(), CancellationToken.None) + var results = await db.GetVolumesAsync(Array.Empty(), CancellationToken.None) .ToListAsync() .ConfigureAwait(false); diff --git a/Duplicati/UnitTest/LockingDeleteAndCompactTests.cs b/Duplicati/UnitTest/LockingDeleteAndCompactTests.cs index c115cfa50..079bd6805 100644 --- a/Duplicati/UnitTest/LockingDeleteAndCompactTests.cs +++ b/Duplicati/UnitTest/LockingDeleteAndCompactTests.cs @@ -82,7 +82,7 @@ namespace Duplicati.UnitTest [Test] [Category("DeleteHandler")] - public async Task DeleteSkipsLockedRemoteFilesetVolume() + public async Task DeleteSkipsLockedRemoteFilesetVolumeAsync() { var backupOpts = TestOptions; var target = "file://" + TARGETFOLDER; @@ -94,13 +94,13 @@ namespace Duplicati.UnitTest string oldestFilesetVolumeName; using (var c = new Controller(target, backupOpts, null)) { - var b1 = c.Backup(new[] { DATAFOLDER }); + var b1 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b1.Errors, Is.Empty, "Backup 1 had errors"); SleepUntilNextSecond(b1.BeginTime); TestUtils.WriteTestFile(file2, 1024); - var b2 = c.Backup(new[] { DATAFOLDER }); + var b2 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b2.Errors, Is.Empty, "Backup 2 had errors"); // Find the oldest fileset volume, and lock it. @@ -108,13 +108,13 @@ namespace Duplicati.UnitTest await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, null, true, null, token).ConfigureAwait(false)) { - var filesets = await db.FilesetTimes(token) + var filesets = await db.FilesetTimesAsync(token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); Assert.That(filesets.Length, Is.GreaterThanOrEqualTo(2), "Expected at least 2 filesets"); var oldestFilesetId = filesets.Last().Key; - var oldestFilesetVolume = await db.GetRemoteVolumeFromFilesetID(oldestFilesetId, token).ConfigureAwait(false); + var oldestFilesetVolume = await db.GetRemoteVolumeFromFilesetIDAsync(oldestFilesetId, token).ConfigureAwait(false); oldestFilesetVolumeName = oldestFilesetVolume.Name; } @@ -122,7 +122,7 @@ namespace Duplicati.UnitTest await using (var lockDb = await LocalLockDatabase.CreateAsync(DBFILE, null, token).ConfigureAwait(false)) { - await lockDb.UpdateRemoteVolumeLockExpiration(oldestFilesetVolumeName, DateTime.UtcNow.AddHours(1), token).ConfigureAwait(false); + await lockDb.UpdateRemoteVolumeLockExpirationAsync(oldestFilesetVolumeName, DateTime.UtcNow.AddHours(1), token).ConfigureAwait(false); await lockDb.Transaction.CommitAsync(token: token).ConfigureAwait(false); } } @@ -136,7 +136,7 @@ namespace Duplicati.UnitTest using (var cdelete = new Controller(target, deleteOpts, null)) { - var deleteResults = cdelete.Delete(); + var deleteResults = await cdelete.DeleteAsync(); Assert.That(deleteResults.Errors, Is.Empty, "Delete had errors"); Assert.That(deleteResults.Warnings.Any(x => x.Contains("fileset volume has an active lock", StringComparison.Ordinal)), Is.True, @@ -149,7 +149,7 @@ namespace Duplicati.UnitTest [Test] [Category("DeleteHandler")] - public async Task DeleteHonorsFilesetLocksFromBackupUntilExpiration() + public async Task DeleteHonorsFilesetLocksFromBackupUntilExpirationAsync() { // These values are chosen to give the backup and delete operations enough time to complete // while still leaving a window where the lock is active. They can be adjusted in CI if @@ -199,7 +199,7 @@ namespace Duplicati.UnitTest var b1BeginTime = DateTime.MinValue; using (var c1 = new Controller(target, backupOpts, null)) { - var b1 = c1.Backup(new[] { DATAFOLDER }); + var b1 = await c1.BackupAsync(new[] { DATAFOLDER }); Assert.That(b1.Errors, Is.Empty, "Backup 1 had errors"); b1BeginTime = b1.BeginTime; } @@ -207,7 +207,7 @@ namespace Duplicati.UnitTest // Capture the latest fileset (the one created by backup 1) and apply locks. await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, null, true, null, token).ConfigureAwait(false)) { - var filesets = await db.FilesetTimes(token) + var filesets = await db.FilesetTimesAsync(token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); @@ -228,14 +228,14 @@ namespace Duplicati.UnitTest TestUtils.WriteTestFile(file2, 1024); using (var c2 = new Controller(target, backupOpts, null)) { - var b2 = c2.Backup(new[] { DATAFOLDER }); + var b2 = await c2.BackupAsync(new[] { DATAFOLDER }); Assert.That(b2.Errors, Is.Empty, "Backup 2 had errors"); } // Apply locks for the second backup as well. await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, null, true, null, token).ConfigureAwait(false)) { - var filesets = await db.FilesetTimes(token) + var filesets = await db.FilesetTimesAsync(token) .ToArrayAsync(cancellationToken: token) .ConfigureAwait(false); @@ -247,7 +247,7 @@ namespace Duplicati.UnitTest async Task IsFilesetLockedAsync(long filesetId, CancellationToken ct) { await using var deleteDb = await LocalDeleteDatabase.CreateAsync(DBFILE, "LockCheck", null, ct).ConfigureAwait(false); - return await deleteDb.HasAnyLockedFiles(filesetId, DateTime.UtcNow, ct).ConfigureAwait(false); + return await deleteDb.HasAnyLockedFilesAsync(filesetId, DateTime.UtcNow, ct).ConfigureAwait(false); } // Verify that the oldest fileset has at least one active lock immediately after the backups. @@ -264,7 +264,7 @@ namespace Duplicati.UnitTest using (var cdelete = new Controller(target, deleteOpts, null)) { - var deleteResults = cdelete.Delete(); + var deleteResults = await cdelete.DeleteAsync(); Assert.That(deleteResults.Errors, Is.Empty, "Delete (with active lock) had errors"); Assert.That(deleteResults.DeletedSets, Is.Empty, "Expected no filesets to be deleted while lock is active"); Assert.That(deleteResults.Warnings.Any(x => x.Contains("Skipping deletion of fileset version", StringComparison.Ordinal)), @@ -291,7 +291,7 @@ namespace Duplicati.UnitTest using (var cdelete = new Controller(target, finalDeleteOpts, null)) { - var deleteResults = cdelete.Delete(); + var deleteResults = await cdelete.DeleteAsync(); Assert.That(deleteResults.Errors, Is.Empty, "Delete (after lock expiration) had errors"); var deletedSets = deleteResults.DeletedSets?.ToArray() ?? Array.Empty>(); @@ -303,7 +303,7 @@ namespace Duplicati.UnitTest [Test] [Category("Compact")] - public async Task CompactDetectsAndAvoidsLockedCompactableVolume() + public async Task CompactDetectsAndAvoidsLockedCompactableVolumeAsync() { var testopts = TestOptions; testopts["backup-test-samples"] = "0"; @@ -333,21 +333,21 @@ namespace Duplicati.UnitTest // Create multiple versions and remove some files to generate compactable volumes. TestUtils.WriteTestFile(file1, fileSize); TestUtils.WriteTestFile(file2, fileSize); - var b1 = c.Backup(new[] { DATAFOLDER }); + var b1 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b1.Errors, Is.Empty, "Backup 1 had errors"); SleepUntilNextSecond(b1.BeginTime); TestUtils.WriteTestFile(file3, fileSize); TestUtils.WriteTestFile(file4, fileSize); - var b2 = c.Backup(new[] { DATAFOLDER }); + var b2 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b2.Errors, Is.Empty, "Backup 2 had errors"); SleepUntilNextSecond(b2.BeginTime); TestUtils.WriteTestFile(file5, fileSize); TestUtils.WriteTestFile(file6, fileSize); - var b3 = c.Backup(new[] { DATAFOLDER }); + var b3 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b3.Errors, Is.Empty, "Backup 3 had errors"); SleepUntilNextSecond(b3.BeginTime); @@ -355,7 +355,7 @@ namespace Duplicati.UnitTest File.Delete(file3); File.Delete(file5); - var b4 = c.Backup(new[] { DATAFOLDER }); + var b4 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b4.Errors, Is.Empty, "Backup 4 had errors"); var token = CancellationToken.None; @@ -364,7 +364,7 @@ namespace Duplicati.UnitTest await using (var db = await LocalDeleteDatabase.CreateAsync(DBFILE, "CompactLockTest", null, token).ConfigureAwait(false)) { - var report = await db.GetCompactReport(optionsObj.VolumeSize, optionsObj.Threshold, optionsObj.SmallFileSize, optionsObj.SmallFileMaxCount, token).ConfigureAwait(false); + var report = await db.GetCompactReportAsync(optionsObj.VolumeSize, optionsObj.Threshold, optionsObj.SmallFileSize, optionsObj.SmallFileMaxCount, token).ConfigureAwait(false); lockedCandidateName = report.CompactableVolumes.FirstOrDefault(); } @@ -373,11 +373,11 @@ namespace Duplicati.UnitTest await using (var lockDb = await LocalLockDatabase.CreateAsync(DBFILE, null, token).ConfigureAwait(false)) { - await lockDb.UpdateRemoteVolumeLockExpiration(lockedCandidateName, DateTime.UtcNow.AddHours(1), token).ConfigureAwait(false); + await lockDb.UpdateRemoteVolumeLockExpirationAsync(lockedCandidateName, DateTime.UtcNow.AddHours(1), token).ConfigureAwait(false); await lockDb.Transaction.CommitAsync(token: token).ConfigureAwait(false); } - var compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.That(compactResults.Errors, Is.Empty, "Compact had errors"); Assert.That(compactResults.Messages.Any(x => x.Contains("selected for compaction but has an active lock", StringComparison.Ordinal) && x.Contains(lockedCandidateName, StringComparison.Ordinal)), Is.True, @@ -387,7 +387,7 @@ namespace Duplicati.UnitTest [Test] [Category("Compact")] - public async Task CompactRestoresStoredWritePathOptionsWhenCliOmitsThem() + public async Task CompactRestoresStoredWritePathOptionsWhenCliOmitsThemAsync() { var backupOpts = TestOptions; backupOpts["prefix"] = "compactprefix"; @@ -415,21 +415,21 @@ namespace Duplicati.UnitTest { TestUtils.WriteTestFile(file1, fileSize); TestUtils.WriteTestFile(file2, fileSize); - var b1 = c.Backup(new[] { DATAFOLDER }); + var b1 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b1.Errors, Is.Empty, "Backup 1 had errors"); SleepUntilNextSecond(b1.BeginTime); TestUtils.WriteTestFile(file3, fileSize); TestUtils.WriteTestFile(file4, fileSize); - var b2 = c.Backup(new[] { DATAFOLDER }); + var b2 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b2.Errors, Is.Empty, "Backup 2 had errors"); SleepUntilNextSecond(b2.BeginTime); TestUtils.WriteTestFile(file5, fileSize); TestUtils.WriteTestFile(file6, fileSize); - var b3 = c.Backup(new[] { DATAFOLDER }); + var b3 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b3.Errors, Is.Empty, "Backup 3 had errors"); SleepUntilNextSecond(b3.BeginTime); @@ -437,14 +437,14 @@ namespace Duplicati.UnitTest File.Delete(file3); File.Delete(file5); - var b4 = c.Backup(new[] { DATAFOLDER }); + var b4 = await c.BackupAsync(new[] { DATAFOLDER }); Assert.That(b4.Errors, Is.Empty, "Backup 4 had errors"); } var token = CancellationToken.None; await using (var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, null, true, null, token).ConfigureAwait(false)) { - var storedOptions = await db.GetDbOptions(token).ConfigureAwait(false); + var storedOptions = await db.GetDbOptionsAsync(token).ConfigureAwait(false); Assert.That(storedOptions["prefix"], Is.EqualTo("compactprefix")); Assert.That(storedOptions["dblock-size"], Is.EqualTo((20 * 1024).ToString())); Assert.That(storedOptions["compression-module"], Is.EqualTo("zip")); @@ -468,7 +468,7 @@ namespace Duplicati.UnitTest using (var c = new Controller(target, compactOpts, null)) { - var compactResults = c.Compact(); + var compactResults = await c.CompactAsync(); Assert.That(compactResults.Errors, Is.Empty, "Compact had errors"); Assert.That(compactResults.DownloadedFileCount, Is.GreaterThan(0), "No compact operation was performed"); } diff --git a/Duplicati/UnitTest/MetadataContentInDatabaseTests.cs b/Duplicati/UnitTest/MetadataContentInDatabaseTests.cs index d9b2e173d..b2a48d1b8 100644 --- a/Duplicati/UnitTest/MetadataContentInDatabaseTests.cs +++ b/Duplicati/UnitTest/MetadataContentInDatabaseTests.cs @@ -24,6 +24,7 @@ using System.IO; using Duplicati.Library.Main; using Duplicati.Library.Main.Database; using Duplicati.Library.SQLiteHelper; +using System.Threading.Tasks; using NUnit.Framework; namespace Duplicati.UnitTest; @@ -44,7 +45,7 @@ public class MetadataContentInDatabaseTests : BasicSetupHelper } [Test] - public void Backup_StoresMetadataContent_WhenOptionEnabled() + public async Task Backup_StoresMetadataContent_WhenOptionEnabledAsync() { var options = new Dictionary(this.TestOptions) { @@ -56,13 +57,13 @@ public class MetadataContentInDatabaseTests : BasicSetupHelper File.WriteAllText(Path.Combine(this.DATAFOLDER, "folder", "file.txt"), "data"); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup([this.DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([this.DATAFOLDER])); Assert.That(CountMetadatasetsWithContent(options["dbpath"]), Is.GreaterThan(0)); } [Test] - public void Recreate_StoresMetadataContent_WhenOptionEnabled() + public async Task Recreate_StoresMetadataContent_WhenOptionEnabledAsync() { var options = new Dictionary(this.TestOptions) { @@ -74,13 +75,13 @@ public class MetadataContentInDatabaseTests : BasicSetupHelper File.WriteAllText(Path.Combine(this.DATAFOLDER, "folder", "file.txt"), "data"); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup([this.DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([this.DATAFOLDER])); // Force a recreate by deleting the local database. File.Delete(options["dbpath"]); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); Assert.That(CountMetadatasetsWithContent(options["dbpath"]), Is.GreaterThan(0)); } diff --git a/Duplicati/UnitTest/PreventEmptySourceTest.cs b/Duplicati/UnitTest/PreventEmptySourceTest.cs index 4fe4b6cd0..fdc306a55 100644 --- a/Duplicati/UnitTest/PreventEmptySourceTest.cs +++ b/Duplicati/UnitTest/PreventEmptySourceTest.cs @@ -19,6 +19,7 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Threading.Tasks; using Duplicati.Library.Interface; using NUnit.Framework; @@ -28,23 +29,23 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void BackupFailsWhenSourceFolderIsEmpty() + public async Task BackupFailsWhenSourceFolderIsEmptyAsync() { var testopts = TestOptions.Expand(new { prevent_empty_source = true }); using var controller = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null); - var ex = Assert.Throws(() => controller.Backup(new[] { DATAFOLDER })); + var ex = Assert.ThrowsAsync(() => controller.BackupAsync(new[] { DATAFOLDER })); Assert.That(ex!.HelpID, Is.EqualTo("SourceFolderEmpty")); } [Test] [Category("Targeted")] - public void BackupWorksWhenSourceFolderIsEmpty() + public async Task BackupWorksWhenSourceFolderIsEmptyAsync() { var testopts = TestOptions.Expand(new { }); using var controller = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null); - var result = controller.Backup(new[] { DATAFOLDER }); + var result = await controller.BackupAsync(new[] { DATAFOLDER }); Assert.That(result, Is.Not.Null); } } diff --git a/Duplicati/UnitTest/ProblematicPathTests.cs b/Duplicati/UnitTest/ProblematicPathTests.cs index af247d2c8..13a0c6e02 100644 --- a/Duplicati/UnitTest/ProblematicPathTests.cs +++ b/Duplicati/UnitTest/ProblematicPathTests.cs @@ -23,8 +23,8 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Text.RegularExpressions; +using System.Threading.Tasks; using Duplicati.Library.Common.IO; -using Duplicati.Library.Interface; using Duplicati.Library.Main; using Duplicati.Library.Utility; using NUnit.Framework; @@ -38,10 +38,10 @@ namespace Duplicati.UnitTest { [Test] [Category("ProblematicPath")] - public void DirectoriesWithWildcards() + public async Task DirectoriesWithWildcardsAsync() { const string file = "file"; - List directories = new List(); + var directories = new List(); // Keep expected match counts since they'll differ between // Linux and Windows. @@ -49,7 +49,7 @@ namespace Duplicati.UnitTest var verbatimAsteriskShouldMatchCount = 0; const string asterisk = "*"; - string dirWithAsterisk = Path.Combine(this.DATAFOLDER, asterisk); + var dirWithAsterisk = Path.Combine(this.DATAFOLDER, asterisk); // Windows does not support literal asterisks in paths. if (!OperatingSystem.IsWindows()) { @@ -61,7 +61,7 @@ namespace Duplicati.UnitTest } const string questionMark = "?"; - string dirWithQuestionMark = Path.Combine(this.DATAFOLDER, questionMark); + var dirWithQuestionMark = Path.Combine(this.DATAFOLDER, questionMark); // Windows does not support literal question marks in paths. if (!OperatingSystem.IsWindows()) { @@ -74,44 +74,44 @@ namespace Duplicati.UnitTest // Include at least one single character directory in Windows // for a '?' wildcard can match on const string singleCharacterDir = "X"; - string dirWithSingleCharacter = Path.Combine(this.DATAFOLDER, singleCharacterDir); + var dirWithSingleCharacter = Path.Combine(this.DATAFOLDER, singleCharacterDir); SystemIO.IO_OS.DirectoryCreate(dirWithSingleCharacter); TestUtils.WriteFile(SystemIO.IO_OS.PathCombine(dirWithSingleCharacter, file), new byte[] { 2 }); directories.Add(dirWithSingleCharacter); questionMarkWildcardShouldMatchCount++; const string dir = "dir"; - string normalDir = Path.Combine(this.DATAFOLDER, dir); + var normalDir = Path.Combine(this.DATAFOLDER, dir); SystemIO.IO_OS.DirectoryCreate(normalDir); TestUtils.WriteFile(SystemIO.IO_OS.PathCombine(normalDir, file), new byte[] { 3 }); directories.Add(normalDir); // Backup all files. - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } // Restore all files. - Dictionary restoreOptions = new Dictionary(options) { ["restore-path"] = this.RESTOREFOLDER }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + var restoreOptions = new Dictionary(options) { ["restore-path"] = this.RESTOREFOLDER }; + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(null); + var restoreResults = await c.RestoreAsync(null); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, "Restore"); // List results using * should return a match for each directory. - IListResults listResults = c.List(SystemIO.IO_OS.PathCombine(dirWithAsterisk, file)); + var listResults = await c.ListAsync(SystemIO.IO_OS.PathCombine(dirWithAsterisk, file)); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); Assert.AreEqual(directories.Count, listResults.Files.Count()); - listResults = c.List(SystemIO.IO_OS.PathCombine(dirWithQuestionMark, file)); + listResults = await c.ListAsync(SystemIO.IO_OS.PathCombine(dirWithQuestionMark, file)); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); // List results using ? should return 3 matches in Linux, @@ -124,27 +124,27 @@ namespace Duplicati.UnitTest SystemIO.IO_OS.DirectoryDelete(this.RESTOREFOLDER, true); // Restore one file at a time using the verbatim identifier. - foreach (string directory in directories) + foreach (var directory in directories) { - foreach (string expectedFilePath in SystemIO.IO_OS.EnumerateFiles(directory)) + foreach (var expectedFilePath in SystemIO.IO_OS.EnumerateFiles(directory)) { - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - string verbatimFilePath = "@" + expectedFilePath; + var verbatimFilePath = "@" + expectedFilePath; // Verify that list result using verbatim identifier contains only one file. - IListResults listResults = c.List(verbatimFilePath); + var listResults = await c.ListAsync(verbatimFilePath); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); Assert.AreEqual(1, listResults.Files.Count()); Assert.AreEqual(expectedFilePath, listResults.Files.Single().Path); - IRestoreResults restoreResults = c.Restore(new[] { verbatimFilePath }); + var restoreResults = await c.RestoreAsync(new[] { verbatimFilePath }); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); - string fileName = SystemIO.IO_OS.PathGetFileName(expectedFilePath); - string restoredFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, fileName); + var fileName = SystemIO.IO_OS.PathGetFileName(expectedFilePath); + var restoredFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, fileName); TestUtils.AssertFilesAreEqual(expectedFilePath, restoredFilePath, false, expectedFilePath); SystemIO.IO_OS.FileDelete(restoredFilePath); @@ -153,10 +153,10 @@ namespace Duplicati.UnitTest } // Backup with asterisk in include filter should include all directories. - FilterExpression filter = new FilterExpression(dirWithAsterisk); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var filter = new FilterExpression(dirWithAsterisk); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }, filter); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }, filter); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); Assert.AreEqual(directories.Count, backupResults.ExaminedFiles); @@ -168,9 +168,9 @@ namespace Duplicati.UnitTest // Backup with verbatim asterisk in include filter should include // one directory in Linux and zero directories in Windows. filter = new FilterExpression("@" + SystemIO.IO_OS.PathCombine(dirWithAsterisk, file)); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }, filter); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }, filter); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); Assert.AreEqual(verbatimAsteriskShouldMatchCount, backupResults.ExaminedFiles); @@ -179,53 +179,53 @@ namespace Duplicati.UnitTest [Test] [Category("ProblematicPath")] - public void ExcludeProblematicPaths() + public async Task ExcludeProblematicPathsAsync() { // A normal path that will be backed up. - string normalFilePath = Path.Combine(this.DATAFOLDER, "normal"); + var normalFilePath = Path.Combine(this.DATAFOLDER, "normal"); File.WriteAllBytes(normalFilePath, new byte[] { 0, 1, 2 }); // A long path to exclude. - string longFile = SystemIO.IO_OS.PathCombine(this.DATAFOLDER, new string('y', 255)); + var longFile = SystemIO.IO_OS.PathCombine(this.DATAFOLDER, new string('y', 255)); TestUtils.WriteFile(longFile, new byte[] { 0, 1 }); // A folder that ends with a dot to exclude. - string folderWithDot = Path.Combine(this.DATAFOLDER, "folder_with_dot."); + var folderWithDot = Path.Combine(this.DATAFOLDER, "folder_with_dot."); SystemIO.IO_OS.DirectoryCreate(folderWithDot); // A folder that ends with a space to exclude. - string folderWithSpace = Path.Combine(this.DATAFOLDER, "folder_with_space "); + var folderWithSpace = Path.Combine(this.DATAFOLDER, "folder_with_space "); SystemIO.IO_OS.DirectoryCreate(folderWithSpace); // A file that ends with a dot to exclude. - string fileWithDot = Path.Combine(this.DATAFOLDER, "file_with_dot."); + var fileWithDot = Path.Combine(this.DATAFOLDER, "file_with_dot."); TestUtils.WriteFile(fileWithDot, new byte[] { 0, 1 }); // A file that ends with a space to exclude. - string fileWithSpace = Path.Combine(this.DATAFOLDER, "file_with_space "); + var fileWithSpace = Path.Combine(this.DATAFOLDER, "file_with_space "); TestUtils.WriteFile(fileWithSpace, new byte[] { 0, 1 }); - FilterExpression filter = new FilterExpression(longFile, false); + var filter = new FilterExpression(longFile, false); filter = FilterExpression.Combine(filter, new FilterExpression(Util.AppendDirSeparator(folderWithDot), false)); filter = FilterExpression.Combine(filter, new FilterExpression(Util.AppendDirSeparator(folderWithSpace), false)); filter = FilterExpression.Combine(filter, new FilterExpression(fileWithDot, false)); filter = FilterExpression.Combine(filter, new FilterExpression(fileWithSpace, false)); - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }, filter); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }, filter); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IListResults listResults = c.List("*"); + var listResults = await c.ListAsync("*"); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); - string[] backedUpPaths = listResults.Files.Select(x => x.Path).ToArray(); + var backedUpPaths = listResults.Files.Select(x => x.Path).ToArray(); Assert.AreEqual(2, backedUpPaths.Length); Assert.Contains(Util.AppendDirSeparator(this.DATAFOLDER), backedUpPaths); Assert.Contains(normalFilePath, backedUpPaths); @@ -234,36 +234,36 @@ namespace Duplicati.UnitTest [Test] [Category("ProblematicPath")] - public void LongPath() + public async Task LongPathAsync() { - string folderPath = Path.Combine(this.DATAFOLDER, new string('x', 10)); + var folderPath = Path.Combine(this.DATAFOLDER, new string('x', 10)); SystemIO.IO_OS.DirectoryCreate(folderPath); - string fileName = new string('y', 255); - string filePath = SystemIO.IO_OS.PathCombine(folderPath, fileName); + var fileName = new string('y', 255); + var filePath = SystemIO.IO_OS.PathCombine(folderPath, fileName); byte[] fileBytes = { 0, 1, 2 }; TestUtils.WriteFile(filePath, fileBytes); - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } - Dictionary restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] { filePath }); + var restoreResults = await c.RestoreAsync(new[] { filePath }); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); } - string restoreFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, fileName); + var restoreFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, fileName); Assert.IsTrue(SystemIO.IO_OS.FileExists(restoreFilePath)); - MemoryStream restoredStream = new MemoryStream(); + var restoredStream = new MemoryStream(); using (FileStream fileStream = SystemIO.IO_OS.FileOpenRead(restoreFilePath)) { Utility.CopyStream(fileStream, restoredStream); @@ -279,47 +279,47 @@ namespace Duplicati.UnitTest [TestCase("ends_with_space ", false)] [TestCase("ends_with_spaces ", false)] [TestCase("ends_with_newline\n", true)] - public void ProblematicSuffixes(string pathComponent, bool skipOnWindows) + public async Task ProblematicSuffixesAsync(string pathComponent, bool skipOnWindows) { if (OperatingSystem.IsWindows() && skipOnWindows) { return; } - string folderPath = SystemIO.IO_OS.PathCombine(this.DATAFOLDER, pathComponent); + var folderPath = SystemIO.IO_OS.PathCombine(this.DATAFOLDER, pathComponent); SystemIO.IO_OS.DirectoryCreate(folderPath); - string filePath = SystemIO.IO_OS.PathCombine(folderPath, pathComponent); - byte[] fileBytes = { 0, 1, 2 }; + var filePath = SystemIO.IO_OS.PathCombine(folderPath, pathComponent); + byte[] fileBytes = [0, 1, 2]; TestUtils.WriteFile(filePath, fileBytes); - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } - Dictionary restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; + var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; // Restore just the file. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] { filePath }); + var restoreResults = await c.RestoreAsync(new[] { filePath }); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); } - string restoreFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, pathComponent); + var restoreFilePath = SystemIO.IO_OS.PathCombine(this.RESTOREFOLDER, pathComponent); TestUtils.AssertFilesAreEqual(filePath, restoreFilePath, true, pathComponent); SystemIO.IO_OS.FileDelete(restoreFilePath); // Restore the entire directory. - string pathSpec = $"[{Regex.Escape(Util.AppendDirSeparator(this.DATAFOLDER))}.*]"; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + var pathSpec = $"[{Regex.Escape(Util.AppendDirSeparator(this.DATAFOLDER))}.*]"; + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] { pathSpec }); + var restoreResults = await c.RestoreAsync(new[] { pathSpec }); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); } diff --git a/Duplicati/UnitTest/PurgeTesting.cs b/Duplicati/UnitTest/PurgeTesting.cs index 98db35387..2d086265c 100644 --- a/Duplicati/UnitTest/PurgeTesting.cs +++ b/Duplicati/UnitTest/PurgeTesting.cs @@ -23,8 +23,8 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Common.IO; -using Duplicati.Library.Interface; using Duplicati.Library.Main; using NUnit.Framework; using Assert = NUnit.Framework.Legacy.ClassicAssert; @@ -35,7 +35,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Purge")] - public void PurgeTest() + public async Task PurgeTestAsync() { var blocksize = 1024 * 10; var basedatasize = 0; @@ -51,7 +51,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round1.Select(x => "*" + Path.DirectorySeparatorChar + x))); + var res = await c.BackupAsync(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round1.Select(x => "*" + Path.DirectorySeparatorChar + x))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(res.AddedFiles, round1.Length); @@ -60,7 +60,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round2.Select(x => "*" + Path.DirectorySeparatorChar + x))); + var res = await c.BackupAsync(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round2.Select(x => "*" + Path.DirectorySeparatorChar + x))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(res.AddedFiles, round2.Length - round1.Length); @@ -69,7 +69,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(res.AddedFiles, filenames.Count - round2.Length); @@ -80,7 +80,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { list_sets_only = true }), null)) { - var inf = c.List(); + var inf = await c.ListAsync(); Assert.AreEqual(0, inf.Errors.Count()); Assert.AreEqual(0, inf.Warnings.Count()); var filesets = inf.Filesets.Count(); @@ -89,7 +89,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IListResults listResults = c.List("*"); + var listResults = await c.ListAsync("*"); Assert.AreEqual(0, listResults.Errors.Count()); Assert.AreEqual(0, listResults.Warnings.Count()); var filecount = listResults.Files.Count(); @@ -101,7 +101,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.PurgeFiles(new Library.Utility.FilterExpression("*" + Path.DirectorySeparatorChar + allversion_candidate)); + var res = await c.PurgeFilesAsync(new Library.Utility.FilterExpression("*" + Path.DirectorySeparatorChar + allversion_candidate)); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(3, res.RewrittenFileLists, "Incorrect number of rewritten filesets after all-versions purge"); @@ -112,7 +112,7 @@ namespace Duplicati.UnitTest { using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = i }), null)) { - var res = c.PurgeFiles(new Library.Utility.FilterExpression(Path.Combine(this.DATAFOLDER, single_version_candidate))); + var res = await c.PurgeFilesAsync(new Library.Utility.FilterExpression(Path.Combine(this.DATAFOLDER, single_version_candidate))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(1, res.RewrittenFileLists, "Incorrect number of rewritten filesets after single-versions purge"); @@ -122,7 +122,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.PurgeFiles(new Library.Utility.FilterExpression(round2.Skip(round1.Length).Take(2).Select(x => "*" + Path.DirectorySeparatorChar + x))); + var res = await c.PurgeFilesAsync(new Library.Utility.FilterExpression(round2.Skip(round1.Length).Take(2).Select(x => "*" + Path.DirectorySeparatorChar + x))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(2, res.RewrittenFileLists, "Incorrect number of rewritten filesets after 2-versions purge"); @@ -131,7 +131,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.PurgeFiles(new Library.Utility.FilterExpression(round3.Skip(round2.Length).Take(2).Select(x => "*" + Path.DirectorySeparatorChar + x))); + var res = await c.PurgeFilesAsync(new Library.Utility.FilterExpression(round3.Skip(round2.Length).Take(2).Select(x => "*" + Path.DirectorySeparatorChar + x))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(1, res.RewrittenFileLists, "Incorrect number of rewritten filesets after 1-versions purge"); @@ -145,11 +145,11 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var listinfo = c.List("*"); + var listinfo = await c.ListAsync("*"); Assert.AreEqual(0, listinfo.Errors.Count()); Assert.AreEqual(0, listinfo.Warnings.Count()); var filecount = listinfo.Files.Count(); - listinfo = c.List(); + listinfo = await c.ListAsync(); Assert.AreEqual(0, listinfo.Errors.Count()); Assert.AreEqual(0, listinfo.Warnings.Count()); var filesets = listinfo.Filesets.Count(); @@ -160,19 +160,19 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new string[] { DATAFOLDER }); + var backupResults = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var listinfo = c.List("*"); + var listinfo = await c.ListAsync("*"); Assert.AreEqual(0, listinfo.Errors.Count()); Assert.AreEqual(0, listinfo.Warnings.Count()); var files = listinfo.Files.ToArray(); var filecount = files.Length; - listinfo = c.List(); + listinfo = await c.ListAsync(); Assert.AreEqual(0, listinfo.Errors.Count()); Assert.AreEqual(0, listinfo.Warnings.Count()); var filesets = listinfo.Filesets.ToArray(); @@ -191,7 +191,7 @@ namespace Duplicati.UnitTest [Test] [Category("Purge")] - public void PurgeBrokenFilesTest() + public async Task PurgeBrokenFilesTestAsync() { var blocksize = 1024 * 10; var basedatasize = 0; @@ -206,7 +206,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round1.Select(x => "*" + Path.DirectorySeparatorChar + x))); + var res = await c.BackupAsync(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round1.Select(x => "*" + Path.DirectorySeparatorChar + x))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(res.AddedFiles, round1.Length); @@ -222,7 +222,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round2.Select(x => "*" + Path.DirectorySeparatorChar + x))); + var res = await c.BackupAsync(new string[] { DATAFOLDER }, new Library.Utility.FilterExpression(round2.Select(x => "*" + Path.DirectorySeparatorChar + x))); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(round2.Length - round1.Length, res.AddedFiles); @@ -231,7 +231,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(TimeSpan.FromSeconds(5)); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Backup(new string[] { DATAFOLDER }); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); Assert.AreEqual(filenames.Count - round2.Length, res.AddedFiles); @@ -244,7 +244,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var brk = c.ListBrokenFiles(null); + var brk = await c.ListBrokenFilesAsync(null); Assert.AreEqual(0, brk.Errors.Count()); Assert.AreEqual(0, brk.Warnings.Count()); var sets = brk.BrokenFiles.Count(); @@ -258,7 +258,7 @@ namespace Duplicati.UnitTest for (var i = 0; i < 3; i++) using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { version = i }), null)) { - var brk = c.ListBrokenFiles(null); + var brk = await c.ListBrokenFilesAsync(null); Assert.AreEqual(0, brk.Errors.Count()); Assert.AreEqual(0, brk.Warnings.Count()); var sets = brk.BrokenFiles.Count(); @@ -269,16 +269,16 @@ namespace Duplicati.UnitTest // A dry-run should run without exceptions (see issue #4379). Dictionary dryRunOptions = new Dictionary(testopts) { ["dry-run"] = "true" }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, dryRunOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, dryRunOptions, null)) { - IPurgeBrokenFilesResults purgeResults = c.PurgeBrokenFiles(null); + var purgeResults = await c.PurgeBrokenFilesAsync(null); Assert.AreEqual(0, purgeResults.Errors.Count()); Assert.AreEqual(0, purgeResults.Warnings.Count()); } using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var brk = c.PurgeBrokenFiles(null); + var brk = await c.PurgeBrokenFilesAsync(null); Assert.AreEqual(0, brk.Errors.Count()); Assert.AreEqual(0, brk.Warnings.Count()); @@ -298,9 +298,9 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(wait_target); // A subsequent backup should be successful. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, testopts, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, testopts, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } diff --git a/Duplicati/UnitTest/RecoveryToolTests.cs b/Duplicati/UnitTest/RecoveryToolTests.cs index 54c60b610..196023dee 100644 --- a/Duplicati/UnitTest/RecoveryToolTests.cs +++ b/Duplicati/UnitTest/RecoveryToolTests.cs @@ -213,43 +213,43 @@ namespace Duplicati.UnitTest [TestCase("false", "true")] [TestCase("true", "false")] [TestCase("false", "false")] - public void Recover(string buildIndexWithFiles, bool reducedMemoryUsage) + public async Task RecoverAsync(string buildIndexWithFiles, bool reducedMemoryUsage) { // Files to create in MB. - int[] fileSizes = { 0, 10, 20, 30 }; + int[] fileSizes = [0, 10, 20, 30]; foreach (int size in fileSizes) { - byte[] data = new byte[size * 1024 * 1024]; - Random rng = new Random(); + var data = new byte[size * 1024 * 1024]; + var rng = new Random(); rng.NextBytes(data); File.WriteAllBytes(Path.Combine(this.DATAFOLDER, size + "MB"), data); } const string subdirectoryName = "subdirectory"; - string subdirectoryPath = Path.Combine(this.DATAFOLDER, subdirectoryName); + var subdirectoryPath = Path.Combine(this.DATAFOLDER, subdirectoryName); Directory.CreateDirectory(subdirectoryPath); foreach (int size in fileSizes) { - byte[] data = new byte[size * 1024 * 1024]; - Random rng = new Random(); + var data = new byte[size * 1024 * 1024]; + var rng = new Random(); rng.NextBytes(data); File.WriteAllBytes(Path.Combine(subdirectoryPath, size + "MB"), data); } // Run a backup. - Dictionary options = new Dictionary(this.TestOptions); - string backendURL = "file://" + this.TARGETFOLDER; - using (Controller c = new Controller(backendURL, options, null)) + var options = new Dictionary(this.TestOptions); + var backendURL = "file://" + this.TARGETFOLDER; + using (var c = new Controller(backendURL, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } // Download the backend files. - string downloadFolder = Path.Combine(this.RESTOREFOLDER, "downloadedFiles"); + var downloadFolder = Path.Combine(this.RESTOREFOLDER, "downloadedFiles"); Directory.CreateDirectory(downloadFolder); - int status = CommandLine.RecoveryTool.Program.Main(new[] { "download", $"{backendURL}", $"{downloadFolder}", $"--passphrase={options["passphrase"]}" }); + var status = CommandLine.RecoveryTool.Program.Main(new[] { "download", $"{backendURL}", $"{downloadFolder}", $"--passphrase={options["passphrase"]}" }); Assert.AreEqual(0, status); // Create the index. @@ -257,7 +257,7 @@ namespace Duplicati.UnitTest Assert.AreEqual(0, status); // Restore to a different folder. - string restoreFolder = Path.Combine(this.RESTOREFOLDER, "restoredFiles"); + var restoreFolder = Path.Combine(this.RESTOREFOLDER, "restoredFiles"); Directory.CreateDirectory(restoreFolder); status = CommandLine.RecoveryTool.Program.Main(new[] { "restore", $"{downloadFolder}", $"--targetpath={restoreFolder}", $"--reduce-memory-use={reducedMemoryUsage}" }); Assert.AreEqual(0, status); @@ -269,33 +269,33 @@ namespace Duplicati.UnitTest [Category("RecoveryTool")] [TestCase(false)] [TestCase(true)] - public void Recompress(bool noEncryption) + public async Task RecompressAsync(bool noEncryption) { // Files to create in MB. - int[] fileSizes = { 10, 20, 30 }; + int[] fileSizes = [10, 20, 30]; foreach (int size in fileSizes) { - byte[] data = new byte[size * 1024 * 1024]; - Random rng = new Random(); + var data = new byte[size * 1024 * 1024]; + var rng = new Random(); rng.NextBytes(data); File.WriteAllBytes(Path.Combine(this.DATAFOLDER, size + "MB"), data); } // Run a backup. - Dictionary options = new Dictionary(this.TestOptions) + var options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption.ToString() }; - string backendURL = "file://" + this.TARGETFOLDER; - using (Controller c = new Controller(backendURL, options, null)) + var backendURL = "file://" + this.TARGETFOLDER; + using (var c = new Controller(backendURL, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } // Recompress. - string downloadFolder = Path.Combine(this.RESTOREFOLDER, "downloadedFiles"); + var downloadFolder = Path.Combine(this.RESTOREFOLDER, "downloadedFiles"); Directory.CreateDirectory(downloadFolder); - int status = CommandLine.RecoveryTool.Program.Main(new[] { "recompress", "zip", $"{backendURL}", this.RESTOREFOLDER, $"--passphrase={options["passphrase"]}" }); + var status = CommandLine.RecoveryTool.Program.Main(new[] { "recompress", "zip", $"{backendURL}", this.RESTOREFOLDER, $"--passphrase={options["passphrase"]}" }); Assert.AreEqual(0, status); } @@ -303,20 +303,20 @@ namespace Duplicati.UnitTest [Category("RecoveryTool")] [TestCase(false)] [TestCase(true)] - public void RecompressAndRecreateDatabase(bool noEncryption) + public async Task RecompressAndRecreateDatabaseAsync(bool noEncryption) { // Create a small dataset. File.WriteAllBytes(Path.Combine(this.DATAFOLDER, "file.txt"), new byte[] { 1, 2, 3 }); // Run a backup with default zip compression. - Dictionary options = new Dictionary(this.TestOptions) + var options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption.ToString() }; - string backendURL = "file://" + this.TARGETFOLDER; - using (Controller c = new Controller(backendURL, options, null)) + var backendURL = "file://" + this.TARGETFOLDER; + using (var c = new Controller(backendURL, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -325,9 +325,9 @@ namespace Duplicati.UnitTest File.Delete(options["dbpath"]); // Recompress remote files from zip to tzstd and reupload. - string recompressFolder = Path.Combine(this.RESTOREFOLDER, "recompressed"); + var recompressFolder = Path.Combine(this.RESTOREFOLDER, "recompressed"); Directory.CreateDirectory(recompressFolder); - int status = CommandLine.RecoveryTool.Program.Main(new[] { "recompress", "tzstd", backendURL, recompressFolder, "--reupload", $"--passphrase={options["passphrase"]}" }); + var status = CommandLine.RecoveryTool.Program.Main(new[] { "recompress", "tzstd", backendURL, recompressFolder, "--reupload", $"--passphrase={options["passphrase"]}" }); Assert.AreEqual(0, status); // Verify that the remote now contains tzstd files and no zip files. @@ -336,18 +336,18 @@ namespace Duplicati.UnitTest Assert.That(remoteFiles.Any(f => f.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)), Is.False, "Remote should not contain zip files after recompress."); // Recreate the local database via Repair. - using (Controller c = new Controller(backendURL, options, null)) + using (var c = new Controller(backendURL, options, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Count()); } // The recreated database should have compression-module set to tzstd, // because that is the actual compression format now used on the remote. - using (var db = Duplicati.Library.Main.Database.LocalDatabase.CreateLocalDatabaseAsync(options["dbpath"], "Test", true, null, System.Threading.CancellationToken.None).Await()) + using (var db = await Duplicati.Library.Main.Database.LocalDatabase.CreateLocalDatabaseAsync(options["dbpath"], "Test", true, null, System.Threading.CancellationToken.None)) { - var dbOptions = db.GetDbOptions(System.Threading.CancellationToken.None).Await(); + var dbOptions = await db.GetDbOptionsAsync(System.Threading.CancellationToken.None); Assert.That(dbOptions.ContainsKey("compression-module"), Is.True, "Database should contain compression-module option."); // This assertion replicates the reported issue: after recreate the database // still stores the old compression module (zip) instead of the actual one (tzstd). diff --git a/Duplicati/UnitTest/RemoteSynchronizationHandlerTests.cs b/Duplicati/UnitTest/RemoteSynchronizationHandlerTests.cs index 4d9c8e6e1..9ffd83863 100644 --- a/Duplicati/UnitTest/RemoteSynchronizationHandlerTests.cs +++ b/Duplicati/UnitTest/RemoteSynchronizationHandlerTests.cs @@ -179,7 +179,7 @@ namespace Duplicati.UnitTest ]}}" }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), new BackupResults()); var stored_source = handler.GetType().GetField("m_source", BINDING_FLAGS).GetValue(handler) as string; @@ -301,7 +301,7 @@ namespace Duplicati.UnitTest [Test] [Category("RemoteSync")] - public void TestRunAsync_SuccessfulBackup_TriggersSync() + public async Task TestRunAsync_SuccessfulBackup_TriggersSyncAsync() { // Create some test files System.IO.File.WriteAllText(System.IO.Path.Combine(DATAFOLDER, "testfile.txt"), "test content"); @@ -313,25 +313,25 @@ namespace Duplicati.UnitTest // Run actual backup using var console = new CommandLine.ConsoleOutput(Console.Out, options); using var controller = new Controller($"file://{TARGETFOLDER}", options, console); - var result = controller.Backup([DATAFOLDER]); + var result = await controller.BackupAsync([DATAFOLDER]); // Now check if sync was triggered by checking if Operation table // has sync entry - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); cmd.CommandText = @" SELECT COUNT(*) FROM ""Operation"" WHERE ""Description"" LIKE 'Rsync %' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(1, count); } [Test] [Category("RemoteSync")] - public async Task TestRunAsync_FailedBackup_SkipsSync() + public async Task TestRunAsync_FailedBackup_SkipsSync_Async() { var options = new Dictionary { @@ -339,12 +339,12 @@ namespace Duplicati.UnitTest ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var results = new BasicBackupResults(ParsedResultType.Error); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); // Create Operation table - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); @@ -356,7 +356,7 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" = 'Rsync 0' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(0, count); } @@ -697,7 +697,7 @@ namespace Duplicati.UnitTest [Test] [Category("RemoteSync")] - public async Task TestRunAsync_NonBackupOperation_SkipsSync() + public async Task TestRunAsync_NonBackupOperation_SkipsSync_Async() { var options = new Dictionary { @@ -707,14 +707,14 @@ namespace Duplicati.UnitTest ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var results = new BasicBackupResults(ParsedResultType.Success); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); await handler.RunAsync(); // Check if sync was not recorded - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); cmd.CommandText = @" @@ -722,14 +722,14 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" = 'Rsync 0' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(0, count); } [Test] [Category("RemoteSync")] - public async Task TestRunAsync_FatalResult_SkipsSync() + public async Task TestRunAsync_FatalResult_SkipsSync_Async() { var options = new Dictionary { @@ -738,14 +738,14 @@ namespace Duplicati.UnitTest ]}}", ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var results = new BasicBackupResults(ParsedResultType.Fatal); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); await handler.RunAsync(); // Check if sync was not recorded - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); cmd.CommandText = @" @@ -753,7 +753,7 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" = 'Rsync 0' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(0, count); } @@ -800,7 +800,7 @@ namespace Duplicati.UnitTest [Test] [Category("RemoteSync")] - public async Task TestRunAsync_WithEmptyDestinations_SkipsSync() + public async Task TestRunAsync_WithEmptyDestinations_SkipsSync_Async() { var options = new Dictionary { @@ -812,12 +812,12 @@ namespace Duplicati.UnitTest ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}/"; + var remoteurl = $"file://{source}/"; var results = new BasicBackupResults(ParsedResultType.Success); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); // Create Operation table - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); @@ -830,7 +830,7 @@ namespace Duplicati.UnitTest WHERE ""Description"" LIKE 'Rsync %' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(2, count); // Two valid destinations, empty skipped } @@ -862,7 +862,7 @@ namespace Duplicati.UnitTest [Test] [Category("RemoteSync")] - public async Task TestRunAsync_WithMultipleDestinations_SelectiveSync() + public async Task TestRunAsync_WithMultipleDestinations_SelectiveSync_Async() { var options = new Dictionary { @@ -872,12 +872,12 @@ namespace Duplicati.UnitTest ]}}", ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var results = new BasicBackupResults(ParsedResultType.Success); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); // Create Operation table and insert recent sync for dest2 - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); cmd.CommandText = @" @@ -889,7 +889,7 @@ namespace Duplicati.UnitTest @ts )"; cmd.AddNamedParameter("@ts", Library.Utility.Utility.NormalizeDateTimeToEpochSeconds(DateTime.UtcNow)); - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); await handler.RunAsync(); @@ -899,14 +899,14 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" LIKE 'Rsync %' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(2, count); // Initial 1 + 1 new = 2 } [Test] [Category("RemoteSync")] - public async Task TestRunAsync_WithWarningResult_TriggersSync() + public async Task TestRunAsync_WithWarningResult_TriggersSync_Async() { var options = new Dictionary { @@ -916,12 +916,12 @@ namespace Duplicati.UnitTest ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var result = new BasicBackupResults(ParsedResultType.Warning); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), result); // Create Operation table - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); @@ -933,14 +933,14 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" = 'Rsync 0' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(1, count); } [Test] [Category("RemoteSync")] - public async Task TestRunAsync_WithWarningResult_SkipsSync_WhenDisabled() + public async Task TestRunAsync_WithWarningResult_SkipsSync_WhenDisabled_Async() { var options = new Dictionary { @@ -949,12 +949,12 @@ namespace Duplicati.UnitTest ]}}", ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var result = new BasicBackupResults(ParsedResultType.Warning); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), result); // Create Operation table - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); @@ -966,7 +966,7 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" = 'Rsync 0' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(0, count); } @@ -1085,7 +1085,7 @@ namespace Duplicati.UnitTest [Test] [Category("RemoteSync")] - public async Task TestRunAsync_WithNoDestinations_DoesNotActivate() + public async Task TestRunAsync_WithNoDestinations_DoesNotActivate_Async() { var options = new Dictionary { @@ -1093,7 +1093,7 @@ namespace Duplicati.UnitTest ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var results = new BasicBackupResults(ParsedResultType.Success); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); @@ -1101,7 +1101,7 @@ namespace Duplicati.UnitTest Assert.IsFalse((bool)enabled); // Create Operation table - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); @@ -1114,7 +1114,7 @@ namespace Duplicati.UnitTest WHERE ""Description"" LIKE 'Rsync %' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(0, count); } @@ -1122,7 +1122,7 @@ namespace Duplicati.UnitTest [Test] [Category("RemoteSync")] - public async Task TestRunAsync_ForNonBackupOperations_SkipsSync() + public async Task TestRunAsync_ForNonBackupOperations_SkipsSync_Async() { var options = new Dictionary { @@ -1132,14 +1132,14 @@ namespace Duplicati.UnitTest ["dbpath"] = DBFILE }; - string remoteurl = $"file://{source}"; + var remoteurl = $"file://{source}"; var results = new BasicBackupResults(ParsedResultType.Success); var handler = new RemoteSynchronizationHandler(remoteurl, new Options(options), results); await handler.RunAsync(); // Check if sync was not recorded - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); EnsureOperationTableCreated(cmd); cmd.CommandText = @" @@ -1147,14 +1147,14 @@ namespace Duplicati.UnitTest FROM ""Operation"" WHERE ""Description"" = 'Rsync 0' "; - var count = (long)cmd.ExecuteScalar(); + var count = (long)await cmd.ExecuteScalarAsync(); Assert.AreEqual(0, count); } [Test] [Category("RemoteSync")] - public void TestMultipleBackups_WithCountingDestinations() + public async Task TestMultipleBackups_WithCountingDestinationsAsync() { // Create test files: one folder with 2 files, 2 subfolders each with 2 files, all ~1kb var testDataFolder = System.IO.Path.Combine(DATAFOLDER, "testdata"); @@ -1214,15 +1214,15 @@ namespace Duplicati.UnitTest options["dbpath"] = DBFILE; // First backup - void Backup() + async Task BackupAsync() { - Task.Delay(1000).Wait(); // Ensure timestamp difference between backups + await Task.Delay(1000); // Ensure timestamp difference between backups using var console = new CommandLine.ConsoleOutput(Console.Out, options); using var controller = new Controller("file://" + TARGETFOLDER, options, console); - var result = controller.Backup([DATAFOLDER]); + var result = await controller.BackupAsync([DATAFOLDER]); Assert.AreEqual(ParsedResultType.Success, result.ParsedResult); } - Backup(); + await BackupAsync(); // Assert all destinations have same files bool ContainsSameFiles(string path1, string path2, bool shouldAssert = true) @@ -1243,7 +1243,7 @@ namespace Duplicati.UnitTest if (!matches) return false; - for (int i = 0; i < files1.Length; i++) + for (var i = 0; i < files1.Length; i++) { if (shouldAssert) Assert.AreEqual(files1[i], files2[i], $"Filename mismatch: {files1[i]} vs {files2[i]}"); @@ -1285,8 +1285,8 @@ namespace Duplicati.UnitTest bool DestinationsAreEqual(bool shouldAssert = true, params string[] paths) { - bool matches = true; - for (int i = 1; i < paths.Length; i++) + var matches = true; + for (var i = 1; i < paths.Length; i++) matches &= ContainsSameFiles(paths[0], paths[i], shouldAssert); return matches; } @@ -1294,7 +1294,7 @@ namespace Duplicati.UnitTest DestinationsAreEqual(true, TARGETFOLDER, syncDest1, syncDest2, syncDest3); // Assert restore from each works - void AssertRestoreWorks(string[] Urls) + async Task AssertRestoreWorksAsync(string[] Urls) { var restoreFolders = Urls.Select(url => System.IO.Path.Combine(BASEFOLDER, "restore_" + System.IO.Path.GetRandomFileName())).ToArray(); foreach (var (url, restoreFolder) in Urls.Zip(restoreFolders)) @@ -1311,36 +1311,36 @@ namespace Duplicati.UnitTest using var console = new CommandLine.ConsoleOutput(Console.Out, restoreOptions); using var controller = new Controller(url, restoreOptions, console); - var result = controller.Restore(["*"]); + var result = await controller.RestoreAsync(["*"]); } DestinationsAreEqual(true, [.. restoreFolders.Select(f => System.IO.Path.Combine(f, "data"))]); } - AssertRestoreWorks([TARGETFOLDER, syncDest1, syncDest2, syncDest3]); + await AssertRestoreWorksAsync([TARGETFOLDER, syncDest1, syncDest2, syncDest3]); // Add another file and backup NewRandomFile(testDataFolder, "file7.txt"); - Backup(); + await BackupAsync(); DestinationsAreEqual(true, TARGETFOLDER, syncDest1); Assert.IsFalse(DestinationsAreEqual(false, TARGETFOLDER, syncDest2)); // syncDest2 should be out of date Assert.IsFalse(DestinationsAreEqual(false, TARGETFOLDER, syncDest3)); // syncDest3 should be out of date - AssertRestoreWorks([TARGETFOLDER, syncDest1]); + await AssertRestoreWorksAsync([TARGETFOLDER, syncDest1]); // Add another file and backup NewRandomFile(sub1, "file8.txt"); - Backup(); + await BackupAsync(); DestinationsAreEqual(true, TARGETFOLDER, syncDest1, syncDest2); Assert.IsFalse(DestinationsAreEqual(false, TARGETFOLDER, syncDest3)); // syncDest3 should be out of date - AssertRestoreWorks([TARGETFOLDER, syncDest1, syncDest2]); + await AssertRestoreWorksAsync([TARGETFOLDER, syncDest1, syncDest2]); // Add another file and backup NewRandomFile(sub2, "file9.txt"); - Backup(); + await BackupAsync(); DestinationsAreEqual(true, TARGETFOLDER, syncDest1, syncDest3); Assert.IsFalse(DestinationsAreEqual(false, TARGETFOLDER, syncDest2)); // syncDest2 should be out of date - AssertRestoreWorks([TARGETFOLDER, syncDest1, syncDest3]); + await AssertRestoreWorksAsync([TARGETFOLDER, syncDest1, syncDest3]); } } diff --git a/Duplicati/UnitTest/RepairHandlerTests.cs b/Duplicati/UnitTest/RepairHandlerTests.cs index 66b918fc1..8886b9044 100644 --- a/Duplicati/UnitTest/RepairHandlerTests.cs +++ b/Duplicati/UnitTest/RepairHandlerTests.cs @@ -64,47 +64,43 @@ namespace Duplicati.UnitTest [TestCase(320 * 10240 * 2 + 5)] [TestCase(320 * 10240 * 3)] [TestCase(320 * 10240 * 3 + 5)] - public void RepairMissingBlocklistHashes(int dataSize) + public async Task RepairMissingBlocklistHashesAsync(int dataSize) { - byte[] data = new byte[dataSize]; - Random rng = new Random(); - for (int k = 0; k < 2; k++) + var data = new byte[dataSize]; + var rng = new Random(); + for (var k = 0; k < 2; k++) { rng.NextBytes(data); File.WriteAllBytes(Path.Combine(this.DATAFOLDER, $"{k}"), data); } - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup([this.DATAFOLDER]); + var backupResults = await c.BackupAsync([this.DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } // Mimic a damaged database that needs to be repaired. const string selectStatement = @"SELECT BlocksetID, ""Index"", Hash FROM BlocklistHash ORDER BY Hash ASC"; - List expectedBlocksetIDs = new List(); - List expectedIndexes = new List(); - List expectedHashes = new List(); - using (IDbConnection connection = SQLiteLoader.LoadConnection(options["dbpath"])) + var expectedBlocksetIDs = new List(); + var expectedIndexes = new List(); + var expectedHashes = new List(); + using (var connection = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) { // Read the contents of the BlocklistHash table so that we can // compare them to the contents after the repair operation. - using (IDbCommand command = connection.CreateCommand()) - { - using (IDataReader reader = command.ExecuteReader(selectStatement)) + using (var command = connection.CreateCommand()) + using (var reader = command.ExecuteReader(selectStatement)) + while (reader.Read()) { - while (reader.Read()) - { - expectedBlocksetIDs.Add(reader.GetInt32(0)); - expectedIndexes.Add(reader.GetInt32(1)); - expectedHashes.Add(reader.ConvertValueToString(2)); - } + expectedBlocksetIDs.Add(reader.GetInt32(0)); + expectedIndexes.Add(reader.GetInt32(1)); + expectedHashes.Add(reader.ConvertValueToString(2)); } - } - using (IDbCommand command = connection.CreateCommand()) + using (var command = connection.CreateCommand()) { command.ExecuteNonQuery(@"DELETE FROM BlocklistHash"); using (IDataReader reader = command.ExecuteReader(selectStatement)) @@ -114,30 +110,26 @@ namespace Duplicati.UnitTest } } - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Count()); } - List repairedBlocksetIDs = new List(); - List repairedIndexes = new List(); - List repairedHashes = new List(); - using (IDbConnection connection = SQLiteLoader.LoadConnection(options["dbpath"])) + var repairedBlocksetIDs = new List(); + var repairedIndexes = new List(); + var repairedHashes = new List(); + using (var connection = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) { - using (IDbCommand command = connection.CreateCommand()) - { - using (IDataReader reader = command.ExecuteReader(selectStatement)) + using (var command = connection.CreateCommand()) + using (var reader = command.ExecuteReader(selectStatement)) + while (reader.Read()) { - while (reader.Read()) - { - repairedBlocksetIDs.Add(reader.GetInt32(0)); - repairedIndexes.Add(reader.GetInt32(1)); - repairedHashes.Add(reader.ConvertValueToString(2)); - } + repairedBlocksetIDs.Add(reader.GetInt32(0)); + repairedIndexes.Add(reader.GetInt32(1)); + repairedHashes.Add(reader.ConvertValueToString(2)); } - } } CollectionAssert.AreEqual(expectedBlocksetIDs, repairedBlocksetIDs); @@ -145,9 +137,9 @@ namespace Duplicati.UnitTest CollectionAssert.AreEqual(expectedHashes, repairedHashes); // A subsequent backup should run without errors. - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup([this.DATAFOLDER]); + var backupResults = await c.BackupAsync([this.DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -157,12 +149,60 @@ namespace Duplicati.UnitTest [Category("RepairHandler")] [TestCase("true")] [TestCase("false")] - public void RepairMissingIndexFiles(string noEncryption) + public async Task RepairMissingIndexFilesAsync(string noEncryption) { - Dictionary options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption }; + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup([this.DATAFOLDER]); + var backupResults = await c.BackupAsync([this.DATAFOLDER]); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + + var dindexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*.dindex.*").ToArray(); + Assert.Greater(dindexFiles.Length, 0); + foreach (var f in dindexFiles) + File.Delete(f); + + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + var repairResults = await c.RepairAsync(); + Assert.AreEqual(0, repairResults.Errors.Count()); + Assert.AreEqual(0, repairResults.Warnings.Count()); + } + + var recreatedIndexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*dindex*").ToArray(); + Assert.AreEqual(dindexFiles.Length, recreatedIndexFiles.Length); + } + + [Test] + [Category("RepairHandler"), Category("Targeted")] + public async Task RepairMissingIndexFilesBlocklistAsync() + { + // See issue #3202 + var options = new Dictionary(this.TestOptions) + { + ["blocksize"] = "1KB", + ["no-encryption"] = "true" + }; + var filename = Path.Combine(this.DATAFOLDER, "file"); + using (var s = File.Create(filename)) + { + var size = 1024 * 32 + 1; // Blocklist size + 1 + await s.WriteAsync(new byte[size], 0, size); + } + + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + { + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + using (var s = File.OpenWrite(filename)) + { + // Change first byte + s.WriteByte(1); + } + backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -176,7 +216,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Count()); } @@ -187,7 +227,7 @@ namespace Duplicati.UnitTest [Test] [Category("RepairHandler"), Category("Targeted")] - public void RepairMissingIndexFilesBlocklist() + public async Task RecreateWithDefectIndexBlockAsync() { // See issue #3202 var options = new Dictionary(this.TestOptions) @@ -199,12 +239,12 @@ namespace Duplicati.UnitTest using (var s = File.Create(filename)) { var size = 1024 * 32 + 1; // Blocklist size + 1 - s.Write(new byte[size], 0, size); + await s.WriteAsync(new byte[size], 0, size); } using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); using (var s = File.OpenWrite(filename)) @@ -212,57 +252,7 @@ namespace Duplicati.UnitTest // Change first byte s.WriteByte(1); } - backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); - } - - var dindexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*.dindex.*").ToArray(); - Assert.Greater(dindexFiles.Length, 0); - foreach (var f in dindexFiles) - { - File.Delete(f); - } - - using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - var repairResults = c.Repair(); - Assert.AreEqual(0, repairResults.Errors.Count()); - Assert.AreEqual(0, repairResults.Warnings.Count()); - } - - var recreatedIndexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*dindex*").ToArray(); - Assert.AreEqual(dindexFiles.Length, recreatedIndexFiles.Length); - } - - [Test] - [Category("RepairHandler"), Category("Targeted")] - public void RecreateWithDefectIndexBlock() - { - // See issue #3202 - var options = new Dictionary(this.TestOptions) - { - ["blocksize"] = "1KB", - ["no-encryption"] = "true" - }; - var filename = Path.Combine(this.DATAFOLDER, "file"); - using (var s = File.Create(filename)) - { - var size = 1024 * 32 + 1; // Blocklist size + 1 - s.Write(new byte[size], 0, size); - } - - using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); - using (var s = File.OpenWrite(filename)) - { - // Change first byte - s.WriteByte(1); - } - backupResults = c.Backup(new[] { this.DATAFOLDER }); + backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -287,16 +277,16 @@ namespace Duplicati.UnitTest { using (var ms = new MemoryStream()) { - s.CopyTo(ms); + await s.CopyToAsync(ms); ms.Position = 0; ms.WriteByte(42); ms.Position = 0; - ms.CopyTo(d); + await ms.CopyToAsync(d); } } else { - s.CopyTo(d); + await s.CopyToAsync(d); } } } @@ -311,7 +301,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(1, repairResults.Warnings.Count()); } @@ -320,7 +310,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Count()); } @@ -331,7 +321,7 @@ namespace Duplicati.UnitTest // No errors with recreated index file using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Count()); } @@ -340,7 +330,7 @@ namespace Duplicati.UnitTest [Test] [Category("RepairHandler"), Category("Targeted")] - public void AutoCleanupRepairDoesNotLockDatabase() + public async Task AutoCleanupRepairDoesNotLockDatabaseAsync() { // See issue #3635, #4631 var options = new Dictionary(this.TestOptions) @@ -356,7 +346,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup([this.DATAFOLDER]); + var backupResults = await c.BackupAsync([this.DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -374,7 +364,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(delaytime); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup([this.DATAFOLDER]); + var backupResults = await c.BackupAsync([this.DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); // 1 extra file + 1 warning Assert.AreEqual(2, backupResults.Warnings.Count()); @@ -393,7 +383,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(delaytime); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup([this.DATAFOLDER]); + var backupResults = await c.BackupAsync([this.DATAFOLDER]); Assert.AreEqual(0, backupResults.Errors.Count()); // First will recreate (4 files + 1 warning) Assert.AreEqual(5, backupResults.Warnings.Count()); @@ -404,37 +394,31 @@ namespace Duplicati.UnitTest [Category("RepairHandler")] [TestCase("true")] [TestCase("false")] - public async Task RepairExtraIndexFiles(string noEncryption) + public async Task RepairExtraIndexFilesAsync(string noEncryption) { // Extra index files will be added to the database and should have a correct link established - Dictionary options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); - TestUtils.AssertResults(backupResults); - } + var options = new Dictionary(this.TestOptions) { ["no-encryption"] = noEncryption }; + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); - string[] dindexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*dindex*").ToArray(); + var dindexFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*dindex*").ToArray(); Assert.Greater(dindexFiles.Length, 0); - string origFile = dindexFiles.First(); + var origFile = dindexFiles.First(); // Duplicate index file - string dupFile = Path.Combine(TARGETFOLDER, Path.GetFileName(origFile).Replace(".dindex", "1.dindex")); + var dupFile = Path.Combine(TARGETFOLDER, Path.GetFileName(origFile).Replace(".dindex", "1.dindex")); File.Copy(origFile, dupFile); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - IRepairResults repairResults = c.Repair(); - TestUtils.AssertResults(repairResults); - } + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + TestUtils.AssertResults(await c.RepairAsync()); // Check database block link - using (LocalDatabase db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, "Test", true, null, CancellationToken.None).ConfigureAwait(false)) + using (var db = await LocalDatabase.CreateLocalDatabaseAsync(DBFILE, "Test", true, null, CancellationToken.None).ConfigureAwait(false)) { var indexVolumeId = await db - .GetRemoteVolumeID(Path.GetFileName(origFile), CancellationToken.None) + .GetRemoteVolumeIDAsync(Path.GetFileName(origFile), CancellationToken.None) .ConfigureAwait(false); var duplicateVolumeId = await db - .GetRemoteVolumeID(Path.GetFileName(dupFile), CancellationToken.None) + .GetRemoteVolumeIDAsync(Path.GetFileName(dupFile), CancellationToken.None) .ConfigureAwait(false); Assert.AreNotEqual(-1, indexVolumeId); Assert.AreNotEqual(-1, duplicateVolumeId); @@ -459,24 +443,24 @@ namespace Duplicati.UnitTest [Test] [Category("RepairHandler")] - public void RepairMissingDlistFile() + public async Task RepairMissingDlistFileAsync() { // Make two backups var options = this.TestOptions; using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); ModifyFile(); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Find and delete a dlist file var dlistFile = Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").First(); File.Delete(dlistFile); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IRepairResults repairResults = c.Repair(); + IRepairResults repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); } @@ -486,16 +470,16 @@ namespace Duplicati.UnitTest [Category("RepairHandler")] [TestCase("true")] [TestCase("false")] - public void RepairMissingDlistVolume(bool deleteRemoteFile) + public async Task RepairMissingDlistVolumeAsync(bool deleteRemoteFile) { // Make two backups var options = this.TestOptions; using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); ModifyFile(); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Find a dlist file var dlistFiles = Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*") @@ -507,40 +491,40 @@ namespace Duplicati.UnitTest dlistFiles = dlistFiles.Reverse(); var dlistFile = dlistFiles.First(); - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand("DELETE FROM RemoteVolume WHERE Name = @Name")) - Assert.AreEqual(1, cmd.SetParameterValue("@Name", Path.GetFileName(dlistFile)).ExecuteNonQuery()); + Assert.AreEqual(1, await cmd.SetParameterValue("@Name", Path.GetFileName(dlistFile)).ExecuteNonQueryAsync()); if (deleteRemoteFile) File.Delete(dlistFile); // Should catch this in validation using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - Assert.Throws(() => c.Backup(new[] { this.DATAFOLDER })); + Assert.ThrowsAsync(async () => await c.BackupAsync(new[] { this.DATAFOLDER })); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); - Assert.AreEqual(2, c.List().Filesets.Count()); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); } // Check that the entry was recreated - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand("SELECT COUNT(*) FROM RemoteVolume WHERE Name LIKE '%.dlist.%' AND State != @State")) Assert.AreEqual(2, cmd.SetParameterValue("@State", RemoteVolumeState.Deleted.ToString()).ExecuteScalarInt64(-1)); // Delete the database and check that the result is correct File.Delete(options["dbpath"]); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); TestUtils.AssertResults(repairResults); - Assert.AreEqual(2, c.List().Filesets.Count()); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); - var r = c.Test(long.MaxValue); + var r = await c.TestAsync(long.MaxValue); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); Assert.IsFalse(r.Verifications.Any(p => p.Value.Any())); @@ -549,28 +533,28 @@ namespace Duplicati.UnitTest [Test] [Category("RepairHandler")] - public void RepairMissingFilesetVolume() + public async Task RepairMissingFilesetVolumeAsync() { // Make two backups var options = this.TestOptions; using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Make room for a new backup Thread.Sleep(2000); ModifyFile(); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Remove a fileset - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand()) { var filesetId = cmd.ExecuteScalarInt64("SELECT Id FROM Fileset ORDER BY Id DESC LIMIT 1"); - Assert.AreEqual(1, cmd.SetCommandAndParameters("DELETE FROM Fileset WHERE Id = @FilesetId") + Assert.AreEqual(1, await cmd.SetCommandAndParameters("DELETE FROM Fileset WHERE Id = @FilesetId") .SetParameterValue("@FilesetId", filesetId) - .ExecuteNonQuery()); + .ExecuteNonQueryAsync()); // No longer needed because the recreate will wipe the table before restoring // cmd.SetCommandAndParameters("DELETE FROM FilesetEntry WHERE FilesetId = @FilesetId") @@ -580,68 +564,68 @@ namespace Duplicati.UnitTest // Should catch this in validation using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - Assert.Throws(() => c.Backup(new[] { this.DATAFOLDER })); + Assert.ThrowsAsync(() => c.BackupAsync(new[] { this.DATAFOLDER })); // Since we have removed the entry that is being checked here, we need to disable the check var repairOptions = new Dictionary(options) { ["repair-ignore-outdated-database"] = "true" }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, repairOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, repairOptions, null)) { - IRepairResults repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Where(x => x.IndexOf("RemoteFilesNewerThanLocalDatabase", StringComparison.OrdinalIgnoreCase) < 0).Count()); Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); - Assert.AreEqual(2, c.List().Filesets.Count()); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); } // Check that entry was recreated - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand()) Assert.AreEqual(2, cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM Fileset")); } [Test] [Category("RepairHandler")] - public void ManufactureMissingFiles() + public async Task ManufactureMissingFilesAsync() { // Make two backups var options = this.TestOptions; using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Make room for a new backup Thread.Sleep(2000); ModifyFile(); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Remove a fileset long filesetEntries; long fileLookupEntries; - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand()) { filesetEntries = cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FilesetEntry"); fileLookupEntries = cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FileLookup"); var fileId = cmd.ExecuteScalarInt64("SELECT FileId FROM FilesetEntry INNER JOIN FileLookup ON FilesetEntry.FileID = FileLookup.Id WHERE FileLookup.BlocksetID != -100 ORDER BY FilesetId DESC LIMIT 1"); - Assert.AreEqual(1, cmd.SetCommandAndParameters("DELETE FROM FileLookup WHERE Id = @FileId").SetParameterValue("@FileId", fileId).ExecuteNonQuery()); + Assert.AreEqual(1, await cmd.SetCommandAndParameters("DELETE FROM FileLookup WHERE Id = @FileId").SetParameterValue("@FileId", fileId).ExecuteNonQueryAsync()); } // Should catch this in validation using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - Assert.Throws(() => c.Backup(new[] { this.DATAFOLDER })); + Assert.ThrowsAsync(() => c.BackupAsync(new[] { this.DATAFOLDER })); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); Assert.AreEqual(0, repairResults.Errors.Count()); Assert.AreEqual(0, repairResults.Warnings.Where(x => x.IndexOf("RemoteFilesNewerThanLocalDatabase", StringComparison.OrdinalIgnoreCase) < 0).Count()); Assert.AreEqual(2, Directory.EnumerateFiles(this.TARGETFOLDER, "*.dlist.*").Count()); - Assert.AreEqual(2, c.List().Filesets.Count()); + Assert.AreEqual(2, (await c.ListAsync()).Filesets.Count()); } // Check that entry was recreated - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand()) { Assert.AreEqual(filesetEntries, cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM FilesetEntry")); @@ -651,15 +635,15 @@ namespace Duplicati.UnitTest [Test] [Category("RepairHandler")] - public void RepairReplacesZeroLengthMetadata() + public async Task RepairReplacesZeroLengthMetadataAsync() { var options = new Dictionary(this.TestOptions); File.WriteAllText(Path.Combine(this.DATAFOLDER, "a.txt"), "a"); File.WriteAllText(Path.Combine(this.DATAFOLDER, "b.txt"), "b"); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand()) { var lookupId = cmd.ExecuteScalarInt64("SELECT ID FROM FileLookup LIMIT 1"); @@ -667,17 +651,17 @@ namespace Duplicati.UnitTest .SetParameterValue("@Id", lookupId).ExecuteScalarInt64(-1); var blocksetId = cmd.SetCommandAndParameters("SELECT BlocksetID FROM Metadataset WHERE ID = @Id") .SetParameterValue("@Id", metaId).ExecuteScalarInt64(-1); - cmd.SetCommandAndParameters("UPDATE Blockset SET Length = 0 WHERE ID = @Id") - .SetParameterValue("@Id", blocksetId).ExecuteNonQuery(); + await cmd.SetCommandAndParameters("UPDATE Blockset SET Length = 0 WHERE ID = @Id") + .SetParameterValue("@Id", blocksetId).ExecuteNonQueryAsync(); } using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Repair(); + var res = await c.RepairAsync(); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); } - using (var con = SQLiteLoader.LoadConnection(options["dbpath"])) + using (var con = await SQLiteLoader.LoadConnectionAsync(options["dbpath"])) using (var cmd = con.CreateCommand()) { var remaining = cmd.ExecuteScalarInt64(@"SELECT COUNT(*) FROM Metadataset JOIN Blockset ON Metadataset.BlocksetID = Blockset.ID WHERE Blockset.Length = 0"); diff --git a/Duplicati/UnitTest/RepairWithMissingRemoteFiles.cs b/Duplicati/UnitTest/RepairWithMissingRemoteFiles.cs index 2904be91f..da3fcc7fb 100644 --- a/Duplicati/UnitTest/RepairWithMissingRemoteFiles.cs +++ b/Duplicati/UnitTest/RepairWithMissingRemoteFiles.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Interface; using Duplicati.Library.Main.Database; using Duplicati.Library.SQLiteHelper; @@ -42,7 +43,7 @@ namespace Duplicati.UnitTest [TestCase(false, true, false)] [TestCase(true, true, false)] [TestCase(true, false, false)] - public void TestRepairWithMissingDblocks(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) + public async Task TestRepairWithMissingDblocksAsync(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true, number_of_retries = 0 }); @@ -51,7 +52,7 @@ namespace Duplicati.UnitTest data.AsSpan().Fill((byte)'a'); File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files if (deleteDblockFiles) @@ -77,11 +78,11 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); // 4. Test that the database is now valid using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.TestAsync()); } [Test] @@ -89,7 +90,7 @@ namespace Duplicati.UnitTest [TestCase(false, true, false)] [TestCase(true, true, false)] [TestCase(true, false, false)] - public void TestRepairWithMissingDblocksAndMultipleSources(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) + public async Task TestRepairWithMissingDblocksAndMultipleSourcesAsync(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -101,7 +102,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "c"), data.AsSpan().Slice(0, 1024).ToArray()); File.WriteAllBytes(Path.Combine(DATAFOLDER, "d"), data.AsSpan().Slice(1024, 3).ToArray()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files if (deleteDblockFiles) @@ -128,8 +129,8 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } } @@ -138,7 +139,7 @@ namespace Duplicati.UnitTest [TestCase(false, true, false)] [TestCase(true, true, false)] [TestCase(true, false, false)] - public void TestRepairWithDataFillingMultipleBlocklists(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) + public async Task TestRepairWithDataFillingMultipleBlocklistsAsync(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -153,7 +154,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "d"), data.AsSpan().Slice(0, 1028).ToArray()); File.WriteAllBytes(Path.Combine(DATAFOLDER, "e"), data.AsSpan().Slice(4098, 67).ToArray()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files if (deleteDblockFiles) @@ -180,8 +181,8 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } } @@ -189,7 +190,7 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase(true, true, true)] [TestCase(true, false, false)] - public void TestRepairWithSmallFileOnly(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) + public async Task TestRepairWithSmallFileOnlyAsync(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -198,7 +199,7 @@ namespace Duplicati.UnitTest data.AsSpan().Fill((byte)'a'); File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files if (deleteDblockFiles) @@ -225,8 +226,8 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } } @@ -237,7 +238,7 @@ namespace Duplicati.UnitTest [TestCase(true, false, false)] [TestCase(true, true, true)] [TestCase(true, false, true)] - public void TestRepairWithDataFillingMultipleBlocklistsRandom(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) + public async Task TestRepairWithDataFillingMultipleBlocklistsRandomAsync(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -247,7 +248,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files if (deleteDblockFiles) @@ -274,13 +275,13 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } // 4. Verify that restore works using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); TestUtils.AssertDirectoryTreesAreEquivalent(DATAFOLDER, RESTOREFOLDER, !Library.Utility.Utility.ParseBoolOption(testopts, "skip-metadata"), "Restore"); } @@ -289,7 +290,7 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase(true, true, true)] [TestCase(true, false, false)] - public void TestRepairWithDryRun(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) + public async Task TestRepairWithDryRunAsync(bool deleteDblockFiles, bool deleteIndexFiles, bool deleteDlistFiles) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -299,7 +300,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files if (deleteDblockFiles) @@ -323,9 +324,9 @@ namespace Duplicati.UnitTest File.Delete(file); } - (long remoteVolumes, long duplicatedBlocks, List blockVolumeIds, List<(long, long)> indexVolumeLinks) GetDbSignature() + async Task<(long remoteVolumes, long duplicatedBlocks, List blockVolumeIds, List<(long, long)> indexVolumeLinks)> GetDbSignatureAsync() { - using var db = SQLiteLoader.LoadConnection(DBFILE); + using var db = await SQLiteLoader.LoadConnectionAsync(DBFILE); using var cmd = db.CreateCommand(); var remoteVolumes = cmd.ExecuteScalarInt64("SELECT COUNT(*) FROM RemoteVolume"); @@ -335,13 +336,13 @@ namespace Duplicati.UnitTest return (remoteVolumes, duplicatedBlocks, blockVolumeIds, indexVolumeLinks); } - var preSignature = GetDbSignature(); + var preSignature = await GetDbSignatureAsync(); // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { dry_run = true }), null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); - var postSignature = GetDbSignature(); + var postSignature = await GetDbSignatureAsync(); Assert.AreEqual(preSignature.remoteVolumes, postSignature.remoteVolumes, "Remote volumes count should be the same after dry run repair."); Assert.AreEqual(preSignature.duplicatedBlocks, postSignature.duplicatedBlocks, "Duplicated blocks count should be the same after dry run repair."); @@ -354,7 +355,7 @@ namespace Duplicati.UnitTest [Test] [Category("Targeted")] - public void TestRepairWithNoBlockAvailableFails() + public async Task TestRepairWithNoBlockAvailableFailsTaskAsync() { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -364,7 +365,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).ToList(); @@ -378,14 +379,14 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Repair(); + var res = await c.RepairAsync(); Assert.IsTrue(res.Errors.Where(x => x.Contains("purge-broken-files")).Count() == 1, "Repair should fail with missing block data."); } } [Test] [Category("Targeted")] - public void TestPartialRepairPossibleWithMissingMetadata() + public async Task TestPartialRepairPossibleWithMissingMetadataAsync() { var testopts = TestOptions.Expand(new { @@ -400,7 +401,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).ToList(); @@ -413,20 +414,20 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Repair(); + var res = await c.RepairAsync(); Assert.IsTrue(res.Warnings.Where(x => x.Contains("purge-broken-files")).Count() == 1, "Repair should warn about missing block data."); // Ensure that the partial repair did not clean up fully - Assert.Throws(() => c.Test()); + Assert.ThrowsAsync(() => c.TestAsync()); // Purge broken files should be possible - TestUtils.AssertResults(c.PurgeBrokenFiles(null)); - TestUtils.AssertResults(c.Test(int.MaxValue)); + TestUtils.AssertResults(await c.PurgeBrokenFilesAsync(null)); + TestUtils.AssertResults(await c.TestAsync(int.MaxValue)); } // 4. Verify that restore works using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); TestUtils.AssertDirectoryTreesAreEquivalent(DATAFOLDER, RESTOREFOLDER, !Library.Utility.Utility.ParseBoolOption(testopts, "skip-metadata"), "Restore"); } @@ -435,7 +436,7 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase(false)] [TestCase(true)] - public void TestPartialRepairPossibleWithDeletedFiles(bool deleteLargeFile) + public async Task TestPartialRepairPossibleWithDeletedFilesAsync(bool deleteLargeFile) { var testopts = TestOptions.Expand(new { @@ -451,7 +452,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), data.AsSpan().Slice(0, 1024).ToArray()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).ToList(); @@ -467,23 +468,23 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Repair(); + var res = await c.RepairAsync(); Assert.IsTrue(res.Warnings.Where(x => x.Contains("purge-broken-files")).Count() == 1, "Repair should warn about missing block data."); // Ensure that the partial repair did not clean up fully - Assert.Throws(() => c.Test()); + Assert.ThrowsAsync(() => c.TestAsync()); // Purge broken files should be possible - TestUtils.AssertResults(c.PurgeBrokenFiles(null)); - TestUtils.AssertResults(c.Test(int.MaxValue)); + TestUtils.AssertResults(await c.PurgeBrokenFilesAsync(null)); + TestUtils.AssertResults(await c.TestAsync(int.MaxValue)); - var files = c.List("*").Files.ToList(); + var files = (await c.ListAsync("*")).Files.ToList(); Console.WriteLine("Files: " + string.Join(", ", files.Select(x => x.Path))); } // 4. Verify that restore works using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); Assert.That(File.Exists(Path.Combine(RESTOREFOLDER, "a")), Is.EqualTo(!deleteLargeFile), "Restored file 'a' should be restored."); Assert.That(File.Exists(Path.Combine(RESTOREFOLDER, "b")), Is.EqualTo(true), "File 'b' should always be restored."); @@ -499,7 +500,7 @@ namespace Duplicati.UnitTest [TestCase(1)] // Small file only [TestCase(2)] // Both files [TestCase(3)] // Metadata only - public void TestPartialRepairPossibleWithPartialData(int scenario) + public async Task TestPartialRepairPossibleWithPartialDataAsync(int scenario) { var testopts = TestOptions.Expand(new { blocksize = "1kb", no_encryption = true, rebuild_missing_dblock_files = true }); @@ -510,7 +511,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "b"), data.AsSpan().Slice(0, 1024).ToArray()); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).ToList(); @@ -550,30 +551,30 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var res = c.Repair(); + var res = await c.RepairAsync(); if (corruptBothFiles || destroyMetadata) { - Assert.IsTrue(res.Warnings.Where(x => x.Contains("purge-broken-files")).Count() == 1, "Repair should warn about missing block data."); + Assert.IsTrue(res.Warnings.Count(x => x.Contains("purge-broken-files")) == 1, "Repair should warn about missing block data."); // Ensure that the partial repair did not clean up fully - Assert.Throws(() => c.Test()); + Assert.ThrowsAsync(() => c.TestAsync()); } else { TestUtils.AssertResults(res); - TestUtils.AssertResults(c.Test(int.MaxValue)); + TestUtils.AssertResults(await c.TestAsync(int.MaxValue)); } // Purge broken files should be possible - TestUtils.AssertResults(c.PurgeBrokenFiles(null)); - TestUtils.AssertResults(c.Test(int.MaxValue)); + TestUtils.AssertResults(await c.PurgeBrokenFilesAsync(null)); + TestUtils.AssertResults(await c.TestAsync(int.MaxValue)); } // 4. Verify that restore works using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) { - var res = c.Restore(null); + var res = await c.RestoreAsync(null); if (corruptBothFiles) { // We get a warning because neither file can be restored @@ -598,7 +599,7 @@ namespace Duplicati.UnitTest [TestCase(1)] [TestCase(5)] [TestCase(10)] - public void TestRepairPossibleWithMultipleDblockFiles(int dblocksToDelete) + public async Task TestRepairPossibleWithMultipleDblockFilesAsync(int dblocksToDelete) { var testopts = TestOptions.Expand(new { @@ -615,7 +616,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Backup([DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([DATAFOLDER])); // 2. Delete files var dblockFiles = Directory.GetFiles(TARGETFOLDER, "*.dblock.*", SearchOption.TopDirectoryOnly).ToArray(); @@ -626,13 +627,13 @@ namespace Duplicati.UnitTest // 3. Try to repair the remote destination using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - TestUtils.AssertResults(c.Repair()); - TestUtils.AssertResults(c.Test()); + TestUtils.AssertResults(await c.RepairAsync()); + TestUtils.AssertResults(await c.TestAsync()); } // 4. Verify that restore works using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) - TestUtils.AssertResults(c.Restore(null)); + TestUtils.AssertResults(await c.RestoreAsync(null)); TestUtils.AssertDirectoryTreesAreEquivalent(DATAFOLDER, RESTOREFOLDER, !Library.Utility.Utility.ParseBoolOption(testopts, "skip-metadata"), "Restore"); } diff --git a/Duplicati/UnitTest/RestoreHandlerTests.cs b/Duplicati/UnitTest/RestoreHandlerTests.cs index 5a25787d2..e11fafe93 100644 --- a/Duplicati/UnitTest/RestoreHandlerTests.cs +++ b/Duplicati/UnitTest/RestoreHandlerTests.cs @@ -23,6 +23,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Threading.Tasks; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Main; @@ -38,57 +39,55 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public void DisablePipedStreaming() + public async Task DisablePipedStreamingAsync() { - string filePath = Path.Combine(this.DATAFOLDER, "file"); + var filePath = Path.Combine(this.DATAFOLDER, "file"); File.WriteAllBytes(filePath, new byte[] { 0 }); - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) - { - c.Backup(new[] { this.DATAFOLDER }); - } + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) + await c.BackupAsync(new[] { this.DATAFOLDER }); - Dictionary restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; + var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; // This is now the default behavior, so we cannot explicitly disable it //restoreOptions["disable-piped-streaming"] = "true"; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] { filePath }); + var restoreResults = await c.RestoreAsync(new[] { filePath }); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); } - string restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); + var restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); Assert.IsTrue(File.Exists(restoredFilePath)); } [Test] [Category("RestoreHandler")] - public void RestoreEmptyFile() + public async Task RestoreEmptyFileAsync() { - string folderPath = Path.Combine(this.DATAFOLDER, "folder"); + var folderPath = Path.Combine(this.DATAFOLDER, "folder"); Directory.CreateDirectory(folderPath); - string filePath = Path.Combine(folderPath, "empty_file"); + var filePath = Path.Combine(folderPath, "empty_file"); File.WriteAllBytes(filePath, new byte[] { }); - Dictionary options = new Dictionary(this.TestOptions); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + var options = new Dictionary(this.TestOptions); + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } // Issue #4148 described a situation where the folders containing the empty file were not recreated properly. - Dictionary restoreOptions = new Dictionary(this.TestOptions) + var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER, ["dont-compress-restore-paths"] = "true" }; - using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(new[] { filePath }); + var restoreResults = await c.RestoreAsync(new[] { filePath }); Assert.AreEqual(0, restoreResults.Errors.Count()); // TODO The expected warning is expected, as the 'dont-compress-restore-paths' option results in a warning about a folder not being created before restoring a file. Assert.AreEqual(1, restoreResults.Warnings.Count()); @@ -96,8 +95,8 @@ namespace Duplicati.UnitTest // We need to strip the root part of the path. Otherwise, Path.Combine will simply return the second argument // if it's determined to be an absolute path. - string rootString = SystemIO.IO_OS.GetPathRoot(filePath); - string newPathPart = filePath.Substring(rootString.Length); + var rootString = SystemIO.IO_OS.GetPathRoot(filePath); + var newPathPart = filePath.Substring(rootString.Length); if (OperatingSystem.IsWindows()) { // On Windows, the drive letter is included in the path when the dont-compress-restore-paths option is used. @@ -105,13 +104,13 @@ namespace Duplicati.UnitTest newPathPart = Path.Combine(rootString.Substring(0, 1), filePath.Substring(rootString.Length)); } - string restoredFilePath = Path.Combine(restoreOptions["restore-path"], newPathPart); + var restoredFilePath = Path.Combine(restoreOptions["restore-path"], newPathPart); Assert.IsTrue(File.Exists(restoredFilePath)); } [Test] [Category("RestoreHandler")] - public void RestoreInheritanceBreaks() + public async Task RestoreInheritanceBreaksAsync() { if (!OperatingSystem.IsWindows()) { @@ -130,13 +129,13 @@ namespace Duplicati.UnitTest var options = new Dictionary(this.TestOptions); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // First, restore without restoring permissions. var restoreOptions = new Dictionary(this.TestOptions) { ["restore-path"] = this.RESTOREFOLDER }; using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - TestUtils.AssertResults(c.Restore(new[] { filePath })); + TestUtils.AssertResults(await c.RestoreAsync(new[] { filePath })); var restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); Assert.IsTrue(File.Exists(restoredFilePath)); @@ -154,7 +153,7 @@ namespace Duplicati.UnitTest restoreOptions["restore-permissions"] = "true"; using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - TestUtils.AssertResults(c.Restore(new[] { filePath })); + TestUtils.AssertResults(await c.RestoreAsync(new[] { filePath })); var restoredFilePath = Path.Combine(this.RESTOREFOLDER, "file"); Assert.IsTrue(File.Exists(restoredFilePath)); @@ -166,7 +165,7 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public async System.Threading.Tasks.Task RestoreVolumeCache([Values("0b", "1mb", "5mb", "1gb", null)] string? cache_size, [Values("0", "1", null)] string? channel_size) + public async Task RestoreVolumeCacheAsync([Values("0b", "1mb", "5mb", "1gb", null)] string? cache_size, [Values("0", "1", null)] string? channel_size) { var opts = TestOptions; opts["dblock-size"] = "1mb"; @@ -193,34 +192,34 @@ namespace Duplicati.UnitTest } using var c = new Controller("file://" + this.TARGETFOLDER, opts, null); - TestUtils.AssertResults(c.Backup([this.DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([this.DATAFOLDER])); // Start a 30 second timeout var timeout_task = System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(30)); - var restore_task = System.Threading.Tasks.Task.Run(() => + var restore_task = System.Threading.Tasks.Task.Run(async () => { - TestUtils.AssertResults(c.Restore(["*"])); + TestUtils.AssertResults(await c.RestoreAsync(["*"])); }); var t = await System.Threading.Tasks.Task.WhenAny(timeout_task, restore_task); if (t == timeout_task) { - c.Abort(); + await c.AbortAsync().ConfigureAwait(false); await restore_task; // Ensure we wait for the restore task to complete Assert.Fail("Restore timed out"); } else if (t == restore_task) // Throw any exceptions it might have - t.GetAwaiter().GetResult(); + await t.ConfigureAwait(false); TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, "Restoring with different volume cache sizes"); } [Test] [Category("RestoreHandler")] - public void RestoreInternalProfilingLogsCacheUsage() + public async Task RestoreInternalProfilingLogsCacheUsageAsync() { var sourceFilePath = Path.Combine(this.DATAFOLDER, "profiled-restore.bin"); File.WriteAllBytes(sourceFilePath, new byte[256 * 1024]); @@ -232,7 +231,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller("file://" + this.TARGETFOLDER, backupOptions, null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); var restoreOptions = new Dictionary(backupOptions) { @@ -244,7 +243,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) - TestUtils.AssertResults(c.Restore(new[] { "*" })); + TestUtils.AssertResults(await c.RestoreAsync(new[] { "*" })); var logContents = File.ReadAllText(this.LOGFILE); Assert.That(logContents, Does.Contain("Max used cache size:")); @@ -252,7 +251,7 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public void RestoreWithoutLocalData([Values("true", "false")] string noLocalDb, [Values("true", "false")] string patchWithLocalBlocks) + public async Task RestoreWithoutLocalDataAsync([Values("true", "false")] string noLocalDb, [Values("true", "false")] string patchWithLocalBlocks) { var file1Path = Path.Combine(this.DATAFOLDER, "file1"); File.WriteAllBytes(file1Path, new byte[] { 1, 2, 3 }); @@ -264,8 +263,8 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(folderPath); systemIO.FileCopy(file1Path, Path.Combine(folderPath, "file1 copy"), true); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + using (var c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); var restoreOptions = new Dictionary(this.TestOptions) { @@ -275,14 +274,14 @@ namespace Duplicati.UnitTest }; using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) - TestUtils.AssertResults(c.Restore(new[] { "*" })); + TestUtils.AssertResults(await c.RestoreAsync(new[] { "*" })); TestUtils.AssertDirectoryTreesAreEquivalent(this.DATAFOLDER, this.RESTOREFOLDER, true, "Restoring without local data"); } [Test] [Category("RestoreHandler")] - public void RestoreThreeVersionsWithoutLocalDb([Values(0, 1, 2)] int version) + public async Task RestoreThreeVersionsWithoutLocalDbAsync([Values(0, 1, 2)] int version) { // Create three versions with different file contents var file1Path = Path.Combine(this.DATAFOLDER, "file1.txt"); @@ -291,19 +290,19 @@ namespace Duplicati.UnitTest // Version 0 (oldest): Create initial files File.WriteAllText(file1Path, "version 0 content"); File.WriteAllText(file2Path, "version 0 file2"); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + using (var c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Version 1: Modify file1, keep file2 File.WriteAllText(file1Path, "version 1 content"); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + using (var c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Version 2 (newest): Modify both files File.WriteAllText(file1Path, "version 2 content"); File.WriteAllText(file2Path, "version 2 file2"); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) - TestUtils.AssertResults(c.Backup(new[] { this.DATAFOLDER })); + using (var c = new Controller("file://" + this.TARGETFOLDER, new Dictionary(this.TestOptions), null)) + TestUtils.AssertResults(await c.BackupAsync(new[] { this.DATAFOLDER })); // Prepare expected content based on version being restored // Note: In Duplicati, version 0 is the MOST RECENT backup @@ -336,7 +335,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) - TestUtils.AssertResults(c.Restore(new[] { "*" })); + TestUtils.AssertResults(await c.RestoreAsync(new[] { "*" })); // Verify restored files match the expected version var restoredFile1Path = Path.Combine(this.RESTOREFOLDER, "file1.txt"); @@ -350,7 +349,7 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public void RestoreOtherProcessIsUsingFile() + public async Task RestoreOtherProcessIsUsingFileAsync() { var file1Path = Path.Combine(this.DATAFOLDER, "file1"); byte[] original_contents = [1, 2, 3]; @@ -361,14 +360,14 @@ namespace Duplicati.UnitTest using var c = new Controller("file://" + this.TARGETFOLDER, opts, null); - var res_backup = c.Backup([this.DATAFOLDER]); + var res_backup = await c.BackupAsync([this.DATAFOLDER]); TestUtils.AssertResults(res_backup); File.WriteAllBytes(file1Path, [4, 5, 6]); using (var fs = new FileStream(file1Path, FileMode.Open, FileAccess.Read, FileShare.None)) { - var res_failing = c.Restore(["*"]); + var res_failing = await c.RestoreAsync(["*"]); Assert.AreEqual(4, res_failing.Errors.Count()); var first_error = res_failing.Errors.First(); Assert.IsTrue( @@ -378,14 +377,14 @@ namespace Duplicati.UnitTest ); } - var res_restore = c.Restore(["*"]); + var res_restore = await c.RestoreAsync(["*"]); TestUtils.AssertResults(res_restore); Assert.AreEqual(original_contents, File.ReadAllBytes(file1Path)); } [Test] [Category("RestoreHandler")] - public async System.Threading.Tasks.Task RestoreVolumeCacheDiskPressure() + public async Task RestoreVolumeCacheDiskPressureAsync() { var opts = TestOptions; opts["dblock-size"] = "1mb"; @@ -409,26 +408,26 @@ namespace Duplicati.UnitTest } using var c = new Controller("file://" + this.TARGETFOLDER, opts, null); - TestUtils.AssertResults(c.Backup([this.DATAFOLDER])); + TestUtils.AssertResults(await c.BackupAsync([this.DATAFOLDER])); var timeout_task = System.Threading.Tasks.Task.Delay(TimeSpan.FromSeconds(120)); RestoreResults? result = null; - var restore_task = System.Threading.Tasks.Task.Run(() => + var restore_task = System.Threading.Tasks.Task.Run(async () => { - result = (RestoreResults)c.Restore(["*"]); + result = (RestoreResults)await c.RestoreAsync(["*"]); }); var t = await System.Threading.Tasks.Task.WhenAny(timeout_task, restore_task); if (t == timeout_task) { - c.Abort(); + await c.AbortAsync().ConfigureAwait(false); await restore_task; Assert.Fail("Restore timed out"); } else { - t.GetAwaiter().GetResult(); + await t.ConfigureAwait(false); } Assert.IsNotNull(result); diff --git a/Duplicati/UnitTest/RestorePathTraversalTests.cs b/Duplicati/UnitTest/RestorePathTraversalTests.cs index 8617d918f..e63c9e49c 100644 --- a/Duplicati/UnitTest/RestorePathTraversalTests.cs +++ b/Duplicati/UnitTest/RestorePathTraversalTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.IO.Compression; +using System.Threading.Tasks; using Duplicati.Library.Common.IO; using Duplicati.Library.Interface; using Duplicati.Library.Main; @@ -15,7 +16,7 @@ namespace Duplicati.UnitTest { [Test] [Category("RestoreHandler")] - public void RestoreToSymlinkTarget() + public async Task RestoreToSymlinkTargetAsync() { if (OperatingSystem.IsWindows()) Assert.Ignore("Symlink tests are not supported on Windows"); @@ -28,9 +29,9 @@ namespace Duplicati.UnitTest File.WriteAllText(filePath, "secret data"); // 2. Backup - using (Controller c = new Controller("file://" + this.TARGETFOLDER, this.TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, this.TestOptions, null)) { - var backupResults = c.Backup(new[] { sourceFolder }); + var backupResults = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.Greater(backupResults.ExaminedFiles, 0); } @@ -56,7 +57,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { // We expect a UserInformationException due to path traversal detection - var ex = NUnit.Framework.Assert.Throws(() => c.Restore(null)); + var ex = NUnit.Framework.Assert.ThrowsAsync(() => c.RestoreAsync(null)); NUnit.Framework.Assert.That(ex.Message.Contains("Path traversal detected"), "Expected path traversal error message"); } @@ -75,7 +76,7 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public void RestoreSymlinkPointingOutside() + public async Task RestoreSymlinkPointingOutsideAsync() { if (OperatingSystem.IsWindows()) Assert.Ignore("Symlink tests are not supported on Windows"); @@ -92,7 +93,7 @@ namespace Duplicati.UnitTest // 2. Backup using (var c = new Controller("file://" + this.TARGETFOLDER, this.TestOptions, null)) { - var backupResults = c.Backup(new[] { sourceFolder }); + var backupResults = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, backupResults.Errors.Count()); } @@ -104,7 +105,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - var restoreResults = c.Restore(null); + var restoreResults = await c.RestoreAsync(null); Assert.AreEqual(0, restoreResults.Errors.Count()); } @@ -126,7 +127,7 @@ namespace Duplicati.UnitTest [Test] [Category("RestoreHandler")] - public void RestoreRelativePathInDlist() + public async Task RestoreRelativePathInDlistAsync() { // 1. Setup source data var sourceFolder = Path.Combine(this.DATAFOLDER, "source"); @@ -141,7 +142,7 @@ namespace Duplicati.UnitTest // 2. Backup using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { sourceFolder }); + IBackupResults backupResults = await c.BackupAsync(new[] { sourceFolder }); Assert.AreEqual(0, backupResults.Errors.Count()); } @@ -174,7 +175,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var repairResults = c.Repair(); + var repairResults = await c.RepairAsync(); // We expect warnings about invalid path var foundWarning = repairResults.Warnings.Any(w => w.Contains("Path traversal detected") || w.Contains("Invalid path")); diff --git a/Duplicati/UnitTest/RunScriptTests.cs b/Duplicati/UnitTest/RunScriptTests.cs index 4084d4ab0..e1d8cf0dd 100644 --- a/Duplicati/UnitTest/RunScriptTests.cs +++ b/Duplicati/UnitTest/RunScriptTests.cs @@ -26,6 +26,7 @@ using System.Linq; using Duplicati.Library.Interface; using Duplicati.Library.Main; using NUnit.Framework; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -41,20 +42,20 @@ namespace Duplicati.UnitTest [TestCase(4)] [TestCase(5)] [TestCase(6)] - public void RunScriptAfter(int exitCode) + public async Task RunScriptAfterAsync(int exitCode) { const string expectedMessage = "Hello"; - string expectedFile = Path.Combine(this.RESTOREFOLDER, "hello.txt"); - List customCommands = new List + var expectedFile = Path.Combine(this.RESTOREFOLDER, "hello.txt"); + var customCommands = new List { $"echo {expectedMessage}>\"{expectedFile}\"" }; - Dictionary options = this.TestOptions; + var options = new Dictionary(this.TestOptions); options["run-script-after"] = CreateScript(exitCode, null, null, 0, customCommands); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); switch (exitCode) { @@ -88,10 +89,10 @@ namespace Duplicati.UnitTest [Test] [Category("Border")] - public void RunScriptBefore() + public async Task RunScriptBeforeAsync() { var blocksize = 10 * 1024; - var options = TestOptions; + var options = new Dictionary(TestOptions); options["blocksize"] = blocksize.ToString() + "b"; options["run-script-timeout"] = "5s"; @@ -102,7 +103,7 @@ namespace Duplicati.UnitTest using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, options, null)) { - var res = c.Backup(new string[] { DATAFOLDER }); + var res = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); Assert.AreEqual(0, res.Warnings.Count()); if (res.ParsedResult != ParsedResultType.Success) @@ -110,7 +111,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(0); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Success) throw new Exception("Unexpected result from backup with return code 0"); if (res.ExaminedFiles <= 0) @@ -118,7 +119,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(1); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Success) throw new Exception("Unexpected result from backup with return code 1"); if (res.ExaminedFiles > 0) @@ -126,7 +127,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(2); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Warning) throw new Exception("Unexpected result from backup with return code 2"); if (res.ExaminedFiles <= 0) @@ -134,7 +135,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(3); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Warning) throw new Exception("Unexpected result from backup with return code 3"); if (res.ExaminedFiles > 0) @@ -142,7 +143,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(4); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Error) throw new Exception("Unexpected result from backup with return code 4"); if (res.ExaminedFiles <= 0) @@ -152,7 +153,7 @@ namespace Duplicati.UnitTest { System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(exitCode); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Error) throw new Exception($"Unexpected result from backup with return code {exitCode}"); if (res.ExaminedFiles > 0) @@ -161,7 +162,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(2, "TEST WARNING MESSAGE"); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Warning) throw new Exception("Unexpected result from backup with return code 2"); if (res.ExaminedFiles <= 0) @@ -171,7 +172,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(3, "TEST WARNING MESSAGE"); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Warning) throw new Exception("Unexpected result from backup with return code 3"); if (res.ExaminedFiles > 0) @@ -181,7 +182,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(4, "TEST ERROR MESSAGE"); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Error) throw new Exception("Unexpected result from backup with return code 4"); if (res.ExaminedFiles <= 0) @@ -191,7 +192,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(5, "TEST ERROR MESSAGE"); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Error) throw new Exception("Unexpected result from backup with return code 5"); if (res.ExaminedFiles > 0) @@ -201,7 +202,7 @@ namespace Duplicati.UnitTest System.Threading.Thread.Sleep(PAUSE_TIME); options["run-script-before"] = CreateScript(0, sleeptime: 10); - res = c.Backup(new string[] { DATAFOLDER }); + res = await c.BackupAsync(new string[] { DATAFOLDER }); if (res.ParsedResult != ParsedResultType.Warning) throw new Exception("Unexpected result from backup with timeout script"); if (res.ExaminedFiles <= 0) @@ -227,10 +228,10 @@ namespace Duplicati.UnitTest [TestCase(4)] [TestCase(5)] [TestCase(6)] - public void RunScriptParsedResult(int exitCode) + public async Task RunScriptParsedResultAsync(int exitCode) { - string parsedResultFile = Path.Combine(this.RESTOREFOLDER, "result.txt"); - List customCommands = new List(); + var parsedResultFile = Path.Combine(this.RESTOREFOLDER, "result.txt"); + var customCommands = new List(); if (OperatingSystem.IsWindows()) { customCommands.Add($"echo %DUPLICATI__PARSED_RESULT%>\"{parsedResultFile}\""); @@ -240,12 +241,12 @@ namespace Duplicati.UnitTest customCommands.Add($"echo $DUPLICATI__PARSED_RESULT>\"{parsedResultFile}\""); } - Dictionary options = this.TestOptions; + var options = new Dictionary(this.TestOptions); options["run-script-before"] = this.CreateScript(exitCode); options["run-script-after"] = this.CreateScript(0, null, null, 0, customCommands); using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); bool expectBackup; string expectedParsedResult; @@ -300,7 +301,7 @@ namespace Duplicati.UnitTest break; } - IEnumerable targetEntries = Directory.EnumerateFileSystemEntries(this.TARGETFOLDER); + var targetEntries = Directory.EnumerateFileSystemEntries(this.TARGETFOLDER); if (expectBackup) { // We expect a dblock, dlist, and dindex file. @@ -311,7 +312,7 @@ namespace Duplicati.UnitTest Assert.AreEqual(0, targetEntries.Count()); } - string[] lines = File.ReadAllLines(parsedResultFile); + var lines = File.ReadAllLines(parsedResultFile); Assert.AreEqual(1, lines.Length); Assert.AreEqual(expectedParsedResult, lines[0]); } @@ -319,21 +320,21 @@ namespace Duplicati.UnitTest [Test] [Category("Border")] - public void CustomRemoteURL() + public async Task CustomRemoteURLAsync() { - string customTargetFolder = Path.Combine(this.TARGETFOLDER, "destination"); + var customTargetFolder = Path.Combine(this.TARGETFOLDER, "destination"); Directory.CreateDirectory(customTargetFolder); - List customCommands = new List + var customCommands = new List { $"echo --remoteurl = \"{customTargetFolder}\"" }; - Dictionary options = this.TestOptions; + var options = new Dictionary(this.TestOptions); options["run-script-before"] = CreateScript(0, null, null, 0, customCommands); - using (Controller c = new Library.Main.Controller("file://" + this.TARGETFOLDER, options, null)) + using (var c = new Library.Main.Controller("file://" + this.TARGETFOLDER, options, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } diff --git a/Duplicati/UnitTest/SQLiteTests.cs b/Duplicati/UnitTest/SQLiteTests.cs index cd9ed644c..c7c2e24f0 100755 --- a/Duplicati/UnitTest/SQLiteTests.cs +++ b/Duplicati/UnitTest/SQLiteTests.cs @@ -34,9 +34,9 @@ namespace Duplicati.UnitTest [TestFixture] public class SQLiteTests : BasicSetupHelper { - private static async Task CreateDummyDatabase(string path) + private static async Task CreateDummyDatabaseAsync(string path) { - var connection = SQLiteLoader.LoadConnection(path); + var connection = await SQLiteLoader.LoadConnectionAsync(path); using (var command = connection.CreateCommand()) { @@ -53,10 +53,10 @@ namespace Duplicati.UnitTest [Test] [Category("SQLite")] - public async Task TestEmptyTransaction() + public async Task TestEmptyTransactionAsync() { using var tf = new TempFile(); - using var connection = await CreateDummyDatabase(tf) + using var connection = await CreateDummyDatabaseAsync(tf) .ConfigureAwait(false); using var t1 = connection.BeginTransaction(); @@ -68,10 +68,10 @@ namespace Duplicati.UnitTest [Test] [Category("SQLite")] - public async Task TestListExpansion() + public async Task TestListExpansionAsync() { using var tf = new TempFile(); - using var connection = await CreateDummyDatabase(tf).ConfigureAwait(false); + using var connection = await CreateDummyDatabaseAsync(tf).ConfigureAwait(false); using var rtr = new ReusableTransaction(connection); using (var command = connection.CreateCommand("SELECT COUNT(*) FROM TestTable WHERE ID IN (@List)")) diff --git a/Duplicati/UnitTest/SVNCheckoutsTest.cs b/Duplicati/UnitTest/SVNCheckoutsTest.cs index dcab361eb..2632b0e8b 100644 --- a/Duplicati/UnitTest/SVNCheckoutsTest.cs +++ b/Duplicati/UnitTest/SVNCheckoutsTest.cs @@ -68,7 +68,7 @@ namespace Duplicati.UnitTest /// /// The folders to backup. Folder at index 0 is the base, all others are incrementals /// The target destination for the backups - public static void RunTest(string[] folders, Dictionary options, string target) + public static async Task RunTestAsync(string[] folders, Dictionary options, string target) { string tempdir = System.IO.Path.Combine(System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "tempdir"); string logfilename = System.IO.Path.Combine(tempdir, string.Format("unittest-{0}.log", Library.Utility.Utility.SerializeDateTime(DateTime.Now))); @@ -176,7 +176,7 @@ namespace Duplicati.UnitTest using (var bk = Duplicati.Library.DynamicLoader.BackendLoader.GetBackend(target, options)) foreach (var f in bk.ListAsync(System.Threading.CancellationToken.None).ToBlockingEnumerable()) if (!f.IsFolder) - Utility.Await(bk.DeleteAsync(f.Name, System.Threading.CancellationToken.None)); + await bk.DeleteAsync(f.Name, System.Threading.CancellationToken.None); } catch (Duplicati.Library.Interface.FolderMissingException) { @@ -195,7 +195,7 @@ namespace Duplicati.UnitTest TestUtils.CopyDirectoryRecursive(folders[0], fhsourcefolder); } - RunBackup(usingFHWithRestore ? (string)fhsourcefolder : folders[0], target, options, folders[0]); + await RunBackupAsync(usingFHWithRestore ? (string)fhsourcefolder : folders[0], target, options, folders[0]); for (int i = 1; i < folders.Length; i++) { @@ -210,7 +210,7 @@ namespace Duplicati.UnitTest } //Call function to simplify profiling - RunBackup(usingFHWithRestore ? (string)fhsourcefolder : folders[i], target, options, folders[i]); + await RunBackupAsync(usingFHWithRestore ? (string)fhsourcefolder : folders[i], target, options, folders[i]); } } @@ -227,7 +227,7 @@ namespace Duplicati.UnitTest IList entries; using (var console = new CommandLine.ConsoleOutput(Console.Out, options)) using (var i = new Duplicati.Library.Main.Controller(target, options, console)) - entries = (from n in i.List().Filesets select n.Time.ToLocalTime()).ToList(); + entries = (from n in (await i.ListAsync()).Filesets select n.Time.ToLocalTime()).ToList(); if (entries.Count != folders.Length) { @@ -259,7 +259,7 @@ namespace Duplicati.UnitTest List sourcefiles; using (var console = new CommandLine.ConsoleOutput(Console.Out, options)) using (var inst = new Library.Main.Controller(target, options, console)) - sourcefiles = (from n in inst.List("*").Files select n.Path).ToList(); + sourcefiles = (from n in (await inst.ListAsync("*")).Files select n.Path).ToList(); //Remove all folders from list for (int j = 0; j < sourcefiles.Count; j++) @@ -322,7 +322,7 @@ namespace Duplicati.UnitTest testfiles = (from n in testfiles select n.Substring(tfe.Length)).ToList(); //Call function to simplify profiling - RunPartialRestore(folders[i], target, ptf, options, filterlist.ToArray()); + await RunPartialRestoreAsync(folders[i], target, ptf, options, filterlist.ToArray()); if (!skipverify) { @@ -336,7 +336,7 @@ namespace Duplicati.UnitTest if (!skipfullrestore) { //Call function to simplify profiling - RunRestore(folders[i], target, ttf, options); + await RunRestoreAsync(folders[i], target, ttf, options); if (!skipverify) { @@ -435,33 +435,33 @@ namespace Duplicati.UnitTest } } - private static void RunBackup(string source, string target, Dictionary options, string sourcename) + private static async Task RunBackupAsync(string source, string target, Dictionary options, string sourcename) { BasicSetupHelper.ProgressWriteLine("Backing up the copy: " + sourcename); using (new Timer(LOGTAG, "BackupRun", "Backup of " + sourcename)) using (var console = new CommandLine.ConsoleOutput(Console.Out, options)) using (var i = new Duplicati.Library.Main.Controller(target, options, console)) - Log.WriteInformationMessage(LOGTAG, "BackupOutput", i.Backup(source.Split(System.IO.Path.PathSeparator)).ToString()); + Log.WriteInformationMessage(LOGTAG, "BackupOutput", (await i.BackupAsync(source.Split(System.IO.Path.PathSeparator))).ToString()); } - private static void RunRestore(string source, string target, string tempfolder, Dictionary options) + private static async Task RunRestoreAsync(string source, string target, string tempfolder, Dictionary options) { var tops = new Dictionary(options); tops["restore-path"] = tempfolder; using (new Timer(LOGTAG, "RestoreRun", "Restore of " + source)) using (var console = new CommandLine.ConsoleOutput(Console.Out, options)) using (var i = new Duplicati.Library.Main.Controller(target, tops, console)) - Log.WriteInformationMessage(LOGTAG, "RestoreOutput", i.Restore(null).ToString()); + Log.WriteInformationMessage(LOGTAG, "RestoreOutput", (await i.RestoreAsync(null)).ToString()); } - private static void RunPartialRestore(string source, string target, string tempfolder, Dictionary options, string[] files) + private static async Task RunPartialRestoreAsync(string source, string target, string tempfolder, Dictionary options, string[] files) { var tops = new Dictionary(options); tops["restore-path"] = tempfolder; using (new Timer(LOGTAG, "PartialRestore", "Partial restore of " + source)) using (var console = new CommandLine.ConsoleOutput(Console.Out, options)) using (var i = new Duplicati.Library.Main.Controller(target, tops, console)) - Log.WriteInformationMessage(LOGTAG, "PartialRestoreOutput", i.Restore(files).ToString()); + Log.WriteInformationMessage(LOGTAG, "PartialRestoreOutput", (await i.RestoreAsync(files)).ToString()); } } } diff --git a/Duplicati/UnitTest/SecretProviderHelperTests.cs b/Duplicati/UnitTest/SecretProviderHelperTests.cs index 79c44517c..197940f28 100644 --- a/Duplicati/UnitTest/SecretProviderHelperTests.cs +++ b/Duplicati/UnitTest/SecretProviderHelperTests.cs @@ -85,7 +85,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceSecretsWithDefaultSettings() + public async Task ReplaceSecretsWithDefaultSettingsAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -117,7 +117,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceSecretsWithSlashKeys() + public async Task ReplaceSecretsWithSlashKeysAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -149,7 +149,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceSecretsWithDashKeys() + public async Task ReplaceSecretsWithDashKeysAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -181,7 +181,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplacesSecretsWithUrlEscaping() + public async Task ReplacesSecretsWithUrlEscapingAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret 1%&+abc"; @@ -211,7 +211,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceSecretsWithExtendedPattern() + public async Task ReplaceSecretsWithExtendedPatternAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -244,7 +244,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceSecretsWithExtendedLongPattern() + public async Task ReplaceSecretsWithExtendedLongPatternAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -277,7 +277,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task CachedProviderRetainsPattern() + public async Task CachedProviderRetainsPatternAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["sec1"] = "secret1"; @@ -352,7 +352,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceUsesInMemoryCache() + public async Task ReplaceUsesInMemoryCacheAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -382,7 +382,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceUsesPersistedCache() + public async Task ReplaceUsesPersistedCacheAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -413,7 +413,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceUsesPersistedCacheOnRestart() + public async Task ReplaceUsesPersistedCacheOnRestartAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; @@ -459,7 +459,7 @@ public class SecretProviderHelperTests : BasicSetupHelper [Test] [Category("SecretHelper")] - public async Task ReplaceWarnsOnPartialMatches() + public async Task ReplaceWarnsOnPartialMatchesAsync() { var secretProvider = new MockedSecretProvider(); secretProvider.Secrets["key1"] = "secret1"; diff --git a/Duplicati/UnitTest/SecretProviderSetSecretTests.cs b/Duplicati/UnitTest/SecretProviderSetSecretTests.cs index efcb2e0fd..a5a42ed79 100644 --- a/Duplicati/UnitTest/SecretProviderSetSecretTests.cs +++ b/Duplicati/UnitTest/SecretProviderSetSecretTests.cs @@ -53,7 +53,7 @@ namespace Duplicati.UnitTest; public class SecretProviderSetSecretTests { [Test] - public async Task FileProvider_SetSecret_WritesToFile() + public async Task FileProvider_SetSecret_WritesToFile_Async() { var tempFile = Path.Combine(Path.GetTempPath(), $"duplicati-secrets-{Guid.NewGuid():N}.json"); var passphrase = "test1234"; @@ -90,7 +90,7 @@ public class SecretProviderSetSecretTests [Test] [Category("SecretProviders.Remote")] - public async Task AwsProvider_SetSecret_Works() + public async Task AwsProvider_SetSecret_Works_Async() { var url = Environment.GetEnvironmentVariable("DUPLICATI_TEST_AWSSM_URL"); @@ -169,7 +169,7 @@ public class SecretProviderSetSecretTests [Test] [Category("SecretProviders.Remote")] - public async Task AzureProvider_SetSecret_Works() + public async Task AzureProvider_SetSecret_Works_Async() { var url = Environment.GetEnvironmentVariable("DUPLICATI_TEST_AZKV_URL"); @@ -228,7 +228,7 @@ public class SecretProviderSetSecretTests [Test] [Category("SecretProviders.Remote")] - public async Task GcsProvider_SetSecret_Works() + public async Task GcsProvider_SetSecret_Works_Async() { var url = Environment.GetEnvironmentVariable("DUPLICATI_TEST_GCS_URL"); @@ -284,7 +284,7 @@ public class SecretProviderSetSecretTests [Test] [Category("SecretProviders.Remote")] - public async Task HcVaultProvider_SetSecret_Works() + public async Task HcVaultProvider_SetSecret_Works_Async() { var url = Environment.GetEnvironmentVariable("DUPLICATI_TEST_HCV_URL"); @@ -364,7 +364,7 @@ public class SecretProviderSetSecretTests return secrets ?? new Dictionary(); } - private static async Task IsDockerAvailable() + private static async Task IsDockerAvailable_Async() { try { @@ -381,9 +381,9 @@ public class SecretProviderSetSecretTests [Test] [Category("SecretProviders.Remote")] - public async Task HcVaultProvider_SetSecret_Works_WithTestcontainers() + public async Task HcVaultProvider_SetSecret_Works_WithTestcontainers_Async() { - if (!await IsDockerAvailable()) + if (!await IsDockerAvailable_Async()) Assert.Ignore("Testcontainers not available"); const string rootToken = "duplicati-root-token"; diff --git a/Duplicati/UnitTest/ServerApiIntegrationTests.cs b/Duplicati/UnitTest/ServerApiIntegrationTests.cs index 7910ce3da..85f521aac 100644 --- a/Duplicati/UnitTest/ServerApiIntegrationTests.cs +++ b/Duplicati/UnitTest/ServerApiIntegrationTests.cs @@ -57,7 +57,7 @@ public class ServerApiIntegrationTests : BasicSetupHelper [Test] [Category("Integration")] - public async Task ServerBackupLifecycle() + public async Task ServerBackupLifecycle_Async() { var backupPassphrase = "integration-passphrase"; var backupName = $"API integration backup {Guid.NewGuid():N}"; @@ -106,7 +106,7 @@ public class ServerApiIntegrationTests : BasicSetupHelper [Test] [Category("Integration")] - public async Task ImportSupportsProvidingPassphraseOverride() + public async Task ImportSupportsProvidingPassphraseOverride_Async() { var backupPassphrase = "integration-passphrase"; @@ -140,7 +140,7 @@ public class ServerApiIntegrationTests : BasicSetupHelper [Test] [Category("Integration")] - public async Task ServerMetadataEndpointsReturnData() + public async Task ServerMetadataEndpointsReturnData_Async() { var entryAssemblyLocation = Duplicati.Library.Utility.Utility.getEntryAssembly().Location; var installationRoot = Path.GetDirectoryName(entryAssemblyLocation) ?? "."; @@ -224,7 +224,7 @@ public class ServerApiIntegrationTests : BasicSetupHelper [Test] [Category("Integration")] - public async Task ServerRepairUpdateListsRootPaths() + public async Task ServerRepairUpdateListsRootPaths_Async() { var backupPassphrase = "repair-update-passphrase"; var sampleFilePath = Path.Combine(this.DATAFOLDER, "sample.txt"); @@ -314,7 +314,7 @@ public class ServerApiIntegrationTests : BasicSetupHelper }; ServerProgram.ServerStartedEvent.Reset(); - serverTask = RunServerInBackground(applicationSettings, serverArgs); + serverTask = RunServerInBackgroundAsync(applicationSettings, serverArgs); if (!ServerProgram.ServerStartedEvent.WaitOne(TimeSpan.FromSeconds(60))) Assert.Fail("Server did not start within the allotted time"); @@ -526,7 +526,7 @@ public class ServerApiIntegrationTests : BasicSetupHelper ?? throw new InvalidOperationException("Task state response was empty"); } - private static Task RunServerInBackground(ApplicationSettings applicationSettings, string[] args) + private static Task RunServerInBackgroundAsync(ApplicationSettings applicationSettings, string[] args) { var tcs = new TaskCompletionSource(); var thread = new Thread(() => diff --git a/Duplicati/UnitTest/SetLocksHandlerTests.cs b/Duplicati/UnitTest/SetLocksHandlerTests.cs index ed8955bf3..7ec1fd6ce 100644 --- a/Duplicati/UnitTest/SetLocksHandlerTests.cs +++ b/Duplicati/UnitTest/SetLocksHandlerTests.cs @@ -111,12 +111,12 @@ namespace Duplicati.UnitTest [Test] [Category("LockHandler")] - public async Task AppliesLocksForVolumesInFileset() + public async Task AppliesLocksForVolumesInFilesetAsync() { var options = new Dictionary(TestOptions); using (var controller = new Controller("file://" + TARGETFOLDER, options, null)) { - controller.Backup([DATAFOLDER]); + await controller.BackupAsync([DATAFOLDER]); } var lockDbPath = Path.Combine(BASEFOLDER, $"locktest-{Guid.NewGuid():N}.sqlite"); @@ -125,12 +125,12 @@ namespace Duplicati.UnitTest await using var db = await LocalLockDatabase.CreateAsync(lockDbPath, null, CancellationToken.None).ConfigureAwait(false); var filesets = new List>(); - await foreach (var entry in db.FilesetTimes(CancellationToken.None).ConfigureAwait(false)) + await foreach (var entry in db.FilesetTimesAsync(CancellationToken.None).ConfigureAwait(false)) filesets.Add(entry); var targetFileset = filesets.Last(); var expectedVolumes = new List(); - await foreach ((var volume, _) in db.GetRemoteVolumesDependingOnFilesets(new[] { targetFileset.Key }, CancellationToken.None).ConfigureAwait(false)) + await foreach ((var volume, _) in db.GetRemoteVolumesDependingOnFilesetsAsync(new[] { targetFileset.Key }, CancellationToken.None).ConfigureAwait(false)) expectedVolumes.Add(volume); var lockingOptions = new Options(new Dictionary(options.ToDictionary(kvp => kvp.Key, kvp => (string?)kvp.Value)) @@ -150,12 +150,12 @@ namespace Duplicati.UnitTest [Test] [Category("LockHandler")] - public async Task ContinuesWhenLockingFails() + public async Task ContinuesWhenLockingFailsAsync() { var options = new Dictionary(TestOptions); using (var controller = new Controller("file://" + TARGETFOLDER, options, null)) { - controller.Backup([DATAFOLDER]); + await controller.BackupAsync([DATAFOLDER]); } var lockDbPath = Path.Combine(BASEFOLDER, $"locktest-{Guid.NewGuid():N}.sqlite"); @@ -164,12 +164,12 @@ namespace Duplicati.UnitTest await using var db = await LocalLockDatabase.CreateAsync(lockDbPath, null, CancellationToken.None).ConfigureAwait(false); var filesets = new List>(); - await foreach (var entry in db.FilesetTimes(CancellationToken.None).ConfigureAwait(false)) + await foreach (var entry in db.FilesetTimesAsync(CancellationToken.None).ConfigureAwait(false)) filesets.Add(entry); var latestFileset = filesets.Last(); var expectedVolumes = new List(); - await foreach ((var volume, _) in db.GetRemoteVolumesDependingOnFilesets(new[] { latestFileset.Key }, CancellationToken.None).ConfigureAwait(false)) + await foreach ((var volume, _) in db.GetRemoteVolumesDependingOnFilesetsAsync(new[] { latestFileset.Key }, CancellationToken.None).ConfigureAwait(false)) expectedVolumes.Add(volume); var lockingOptions = new Options(new Dictionary(options.ToDictionary(kvp => kvp.Key, kvp => (string?)kvp.Value)) @@ -192,10 +192,10 @@ namespace Duplicati.UnitTest /// [Test] [Category("Database")] - public async Task GetRemoteVolumesDependingOnFilesets_WithLargeInput_UsesTemporaryTable() + public async Task GetRemoteVolumesDependingOnFilesets_WithLargeInput_UsesTemporaryTable_Async() { using var dbfile = new TempFile(); - using var db = SQLiteLoader.LoadConnection(dbfile); + using var db = await SQLiteLoader.LoadConnectionAsync(dbfile); // Use DatabaseUpgrader to create the schema from embedded resources DatabaseUpgrader.UpgradeDatabase(db, dbfile, typeof(DatabaseSchemaMarker)); @@ -204,7 +204,7 @@ namespace Duplicati.UnitTest // Insert an operation record (required for LocalDatabase initialization) cmd.CommandText = @"INSERT INTO ""Operation"" (""Description"", ""Timestamp"") VALUES ('Test', 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Create 150 fileset IDs (exceeds CHUNK_SIZE of 128) to trigger temporary table path var filesetIds = new List(); @@ -214,19 +214,19 @@ namespace Duplicati.UnitTest cmd.CommandText = $@" INSERT INTO ""Remotevolume"" (""ID"", ""OperationID"", ""Name"", ""Type"", ""State"", ""VerificationCount"", ""DeleteGraceTime"", ""ArchiveTime"", ""LockExpirationTime"") VALUES ({i + 1}, 1, 'volume-{i}.zip', 'Files', 'Verified', 0, 0, 0, 0)"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); // Insert a fileset referencing this volume cmd.CommandText = $@" INSERT INTO ""Fileset"" (""ID"", ""OperationID"", ""VolumeID"", ""IsFullBackup"", ""Timestamp"") VALUES ({i + 1}, 1, {i + 1}, 1, {i})"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); filesetIds.Add(i + 1); } // Close the connection so LocalDatabase can open it - db.Close(); + await db.CloseAsync(); // Create LocalDatabase instance await using var localDb = await LocalDatabase.CreateLocalDatabaseAsync( @@ -240,7 +240,7 @@ namespace Duplicati.UnitTest // Act: Call GetRemoteVolumesDependingOnFilesets with 150 fileset IDs // This should trigger the temporary table code path var volumes = new List(); - await foreach ((var volume, _) in localDb.GetRemoteVolumesDependingOnFilesets(filesetIds, CancellationToken.None).ConfigureAwait(false)) + await foreach ((var volume, _) in localDb.GetRemoteVolumesDependingOnFilesetsAsync(filesetIds, CancellationToken.None).ConfigureAwait(false)) volumes.Add(volume); // Assert: Should return all 150 volumes diff --git a/Duplicati/UnitTest/SoftDeleteTests.cs b/Duplicati/UnitTest/SoftDeleteTests.cs index f4d918ab2..21689a930 100644 --- a/Duplicati/UnitTest/SoftDeleteTests.cs +++ b/Duplicati/UnitTest/SoftDeleteTests.cs @@ -19,7 +19,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; using System.Collections.Generic; using System.IO; using System.Linq; @@ -38,7 +37,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteWithSimplePrefix() + public async Task TestSoftDeleteWithSimplePrefixAsync() { // Use a simple prefix (not a folder path) var softDeletePrefix = "deleted-"; @@ -48,7 +47,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 1"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -61,7 +60,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 2"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } @@ -70,7 +69,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 3"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 3 should have no errors"); } @@ -93,7 +92,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteWithFolderPrefix() + public async Task TestSoftDeleteWithFolderPrefixAsync() { // Use a folder-based prefix var softDeletePrefix = "recycled/"; @@ -106,7 +105,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 1"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -115,7 +114,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 2"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } @@ -124,7 +123,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 3"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 3 should have no errors"); } @@ -149,7 +148,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteWithoutRenameEnabledBackend() + public async Task TestSoftDeleteWithoutRenameEnabledBackendAsync() { // Register the NoRenameBackend Library.DynamicLoader.BackendLoader.AddBackend(new NoRenameBackend()); @@ -162,7 +161,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 1"); using (var c = new Controller(new NoRenameBackend().ProtocolKey + "://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -175,7 +174,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 2"); using (var c = new Controller(new NoRenameBackend().ProtocolKey + "://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } @@ -184,7 +183,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 3"); using (var c = new Controller(new NoRenameBackend().ProtocolKey + "://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 3 should have no errors"); } @@ -208,7 +207,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteWithFolderPrefixWithoutRenameBackend() + public async Task TestSoftDeleteWithFolderPrefixWithoutRenameBackendAsync() { // Register the NoRenameBackend Library.DynamicLoader.BackendLoader.AddBackend(new NoRenameBackend()); @@ -224,7 +223,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 1"); using (var c = new Controller(new NoRenameBackend().ProtocolKey + "://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -233,7 +232,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 2"); using (var c = new Controller(new NoRenameBackend().ProtocolKey + "://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } @@ -242,7 +241,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 3"); using (var c = new Controller(new NoRenameBackend().ProtocolKey + "://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 3 should have no errors"); } @@ -267,7 +266,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteWithPreventBackendRename() + public async Task TestSoftDeleteWithPreventBackendRenameAsync() { // Use a simple prefix var softDeletePrefix = "deleted-"; @@ -277,7 +276,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 1"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2, prevent_backend_rename = "true" }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -286,7 +285,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 2"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2, prevent_backend_rename = "true" }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } @@ -295,7 +294,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 3"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2, prevent_backend_rename = "true" }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 3 should have no errors"); } @@ -317,7 +316,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestWithoutSoftDeleteFilesAreActuallyDeleted() + public async Task TestWithoutSoftDeleteFilesAreActuallyDeletedAsync() { var testFile = Path.Combine(this.DATAFOLDER, "testfile.txt"); @@ -325,7 +324,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 1"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -338,7 +337,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 2"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } @@ -347,7 +346,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, "test content version 3"); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 3 should have no errors"); } @@ -370,7 +369,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteWithCompact() + public async Task TestSoftDeleteWithCompactAsync() { var softDeletePrefix = "deleted-"; var testFile = Path.Combine(this.DATAFOLDER, "testfile.txt"); @@ -380,7 +379,7 @@ namespace Duplicati.UnitTest File.WriteAllText(testFile, largeContent); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 1 should have no errors"); } @@ -388,21 +387,21 @@ namespace Duplicati.UnitTest File.Delete(testFile); using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, "Backup 2 should have no errors"); } // 3. Delete the first version to trigger compact using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix }), null)) { - var result = c.Delete(); + var result = await c.DeleteAsync(); Assert.That(result.Errors, Is.Empty, "Delete should have no errors"); } // 4. Run compact with aggressive settings using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, small_file_size = "1", small_file_max_count = "0" }), null)) { - var result = c.Compact(); + var result = await c.CompactAsync(); Assert.That(result.Errors, Is.Empty, "Compact should have no errors"); } @@ -420,7 +419,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("SoftDelete")] - public void TestSoftDeleteHandlesDuplicateFilenames() + public async Task TestSoftDeleteHandlesDuplicateFilenamesAsync() { var softDeletePrefix = "deleted-"; var testFile = Path.Combine(this.DATAFOLDER, "testfile.txt"); @@ -432,7 +431,7 @@ namespace Duplicati.UnitTest Thread.Sleep(1100); // Ensure timestamp difference using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions.Expand(new { soft_delete_prefix = softDeletePrefix, keep_versions = 2 }), null)) { - var result = c.Backup(new[] { this.DATAFOLDER }); + var result = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.That(result.Errors, Is.Empty, $"Backup {i} should have no errors"); } } diff --git a/Duplicati/UnitTest/StatisticsTests.cs b/Duplicati/UnitTest/StatisticsTests.cs index b646a8a2b..b524e71d0 100644 --- a/Duplicati/UnitTest/StatisticsTests.cs +++ b/Duplicati/UnitTest/StatisticsTests.cs @@ -23,6 +23,7 @@ using System.IO; using System.Threading; using Duplicati.Library.Main; using NUnit.Framework; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -31,14 +32,14 @@ namespace Duplicati.UnitTest { [Test] [Category("Controller")] - public void RunTest() + public async Task RunTestAsync() { // Add a single file File.WriteAllBytes(Path.Combine(this.DATAFOLDER, "file1"), new byte[] { 0, 1, 2 }); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(1, backupResults.AddedFiles); Assert.AreEqual(0, backupResults.ModifiedFiles); @@ -57,9 +58,9 @@ namespace Duplicati.UnitTest Thread.Sleep(1000); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(1, backupResults.AddedFiles); Assert.AreEqual(0, backupResults.ModifiedFiles); @@ -76,9 +77,9 @@ namespace Duplicati.UnitTest // Modify the first file File.WriteAllBytes(Path.Combine(this.DATAFOLDER, "file1"), new byte[] { 0, 1, 2, 3 }); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(0, backupResults.AddedFiles); Assert.AreEqual(1, backupResults.ModifiedFiles); @@ -96,9 +97,9 @@ namespace Duplicati.UnitTest // Delete the second file File.Delete(Path.Combine(this.DATAFOLDER, "file2")); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(0, backupResults.AddedFiles); Assert.AreEqual(1, backupResults.DeletedFiles); @@ -116,9 +117,9 @@ namespace Duplicati.UnitTest // Modify the folder Directory.SetLastWriteTime(Path.Combine(this.DATAFOLDER, "folder1"), DateTime.Now.AddDays(1)); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(0, backupResults.AddedFiles); Assert.AreEqual(0, backupResults.ModifiedFiles); @@ -135,9 +136,9 @@ namespace Duplicati.UnitTest // Delete the folder Directory.Delete(Path.Combine(this.DATAFOLDER, "folder1"), true); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(0, backupResults.AddedFiles); Assert.AreEqual(0, backupResults.ModifiedFiles); @@ -159,9 +160,9 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(Path.Combine(this.DATAFOLDER, "folder3")); Directory.CreateDirectory(Path.Combine(this.DATAFOLDER, "folder4")); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(2, backupResults.AddedFiles); Assert.AreEqual(0, backupResults.ModifiedFiles); @@ -179,9 +180,9 @@ namespace Duplicati.UnitTest // Modify two folders Directory.SetLastWriteTime(Path.Combine(this.DATAFOLDER, "folder2"), DateTime.Now.AddDays(1)); Directory.SetLastWriteTime(Path.Combine(this.DATAFOLDER, "folder3"), DateTime.Now.AddDays(2)); - using (Controller c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) + using (var c = new Controller("file://" + this.TARGETFOLDER, TestOptions, null)) { - var backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); TestUtils.AssertResults(backupResults); Assert.AreEqual(0, backupResults.AddedFiles); Assert.AreEqual(0, backupResults.ModifiedFiles); diff --git a/Duplicati/UnitTest/SymLinkTests.cs b/Duplicati/UnitTest/SymLinkTests.cs index 01656e535..629355d43 100755 --- a/Duplicati/UnitTest/SymLinkTests.cs +++ b/Duplicati/UnitTest/SymLinkTests.cs @@ -26,6 +26,7 @@ using Duplicati.Library.Interface; using Duplicati.Library.Main; using Duplicati.Library.Snapshots; using NUnit.Framework; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -35,7 +36,7 @@ namespace Duplicati.UnitTest { [Test] [Category("SymLink")] - public void SymlinkExists() + public async Task SymlinkExistsAsync() { // Create symlink target directory const string targetDirName = "target"; @@ -70,7 +71,7 @@ namespace Duplicati.UnitTest }; using (Controller c = new("file://" + this.TARGETFOLDER, this.TestOptions, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } @@ -78,7 +79,7 @@ namespace Duplicati.UnitTest // Restore all files using (Controller c = new("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(null); + var restoreResults = await c.RestoreAsync(null); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); @@ -93,7 +94,7 @@ namespace Duplicati.UnitTest // Restore again, trying to overwrite existing symlink using (Controller c = new("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(null); + var restoreResults = await c.RestoreAsync(null); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); } @@ -104,7 +105,7 @@ namespace Duplicati.UnitTest [TestCase(Options.SymlinkStrategy.Store)] [TestCase(Options.SymlinkStrategy.Follow)] [TestCase(Options.SymlinkStrategy.Ignore)] - public void SymLinkPolicy(Options.SymlinkStrategy symlinkPolicy) + public async Task SymLinkPolicyAsync(Options.SymlinkStrategy symlinkPolicy) { // Create symlink target directory const string targetDirName = "target"; @@ -140,14 +141,14 @@ namespace Duplicati.UnitTest Dictionary backupOptions = new Dictionary(this.TestOptions) { ["symlink-policy"] = symlinkPolicy.ToString() }; using (Controller c = new Controller("file://" + this.TARGETFOLDER, backupOptions, null)) { - IBackupResults backupResults = c.Backup(new[] { this.DATAFOLDER }); + var backupResults = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, backupResults.Errors.Count()); Assert.AreEqual(0, backupResults.Warnings.Count()); } // Restore all files using (Controller c = new Controller("file://" + this.TARGETFOLDER, restoreOptions, null)) { - IRestoreResults restoreResults = c.Restore(null); + var restoreResults = await c.RestoreAsync(null); Assert.AreEqual(0, restoreResults.Errors.Count()); Assert.AreEqual(0, restoreResults.Warnings.Count()); diff --git a/Duplicati/UnitTest/SyntheticFilelistMetadataTests.cs b/Duplicati/UnitTest/SyntheticFilelistMetadataTests.cs index 887d325a8..a51cb6a19 100644 --- a/Duplicati/UnitTest/SyntheticFilelistMetadataTests.cs +++ b/Duplicati/UnitTest/SyntheticFilelistMetadataTests.cs @@ -242,7 +242,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "First backup should succeed"); } @@ -277,8 +277,8 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, options, null)) { - Assert.Throws(() => - c.Backup(new[] { this.DATAFOLDER })); + Assert.ThrowsAsync(() => + c.BackupAsync(new[] { this.DATAFOLDER })); } Assert.IsTrue(failed, "Expected dlist upload to fail"); @@ -296,7 +296,7 @@ namespace Duplicati.UnitTest // for the interrupted backup, then continue with the new backup. using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "Recovery backup should succeed"); } @@ -355,7 +355,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); } @@ -377,8 +377,8 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, options, null)) { - Assert.Throws(() => - c.Backup(new[] { this.DATAFOLDER })); + Assert.ThrowsAsync(() => + c.BackupAsync(new[] { this.DATAFOLDER })); } Assert.IsTrue(failed, "Expected dlist upload to fail"); @@ -387,7 +387,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); } @@ -400,7 +400,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, testOptions, null)) { - var testRes = c.Test(long.MaxValue); + var testRes = await c.TestAsync(long.MaxValue); Assert.AreEqual(0, testRes.Errors.Count(), "Test operation should not fail. Errors indicate synthetic filelist metadata is missing."); } @@ -449,7 +449,7 @@ namespace Duplicati.UnitTest // 1. Successful initial backup → FS1 with FilesetEntry → F2 (testfile.txt) using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "First backup should succeed"); } DumpDatabaseState(this.DBFILE, "After first successful backup"); @@ -468,8 +468,8 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, options, null)) { - Assert.Throws(() => - c.Backup(new[] { this.DATAFOLDER }), + Assert.ThrowsAsync(() => + c.BackupAsync(new[] { this.DATAFOLDER }), "Interrupted backup should fail at the dlist upload"); } DumpDatabaseState(this.DBFILE, "After interrupted backup"); @@ -495,11 +495,11 @@ namespace Duplicati.UnitTest await using (var db = await TestDatabase.CreateAsync(this.DBFILE, default).ConfigureAwait(false)) { long interruptedFilesetId = -1; - await foreach (var fs in db.GetIncompleteFilesets(default).ConfigureAwait(false)) + await foreach (var fs in db.GetIncompleteFilesetsAsync(default).ConfigureAwait(false)) interruptedFilesetId = fs.Key; Assert.That(interruptedFilesetId, Is.GreaterThan(0), "Should have located the interrupted fileset"); - interruptedDlistName = (await db.GetRemoteVolumeFromFilesetID(interruptedFilesetId, default).ConfigureAwait(false)).Name; + interruptedDlistName = (await db.GetRemoteVolumeFromFilesetIDAsync(interruptedFilesetId, default).ConfigureAwait(false)).Name; // Find the FilesetEntry for testfile.txt in the interrupted // fileset. There should be exactly one such row (one for the @@ -537,7 +537,7 @@ namespace Duplicati.UnitTest await using (var db = await TestDatabase.CreateAsync(this.DBFILE, default).ConfigureAwait(false)) { TestContext.Progress.WriteLine($"Removing dblock from DB: {Path.GetFileName(dblockToRemove)}"); - await db.RemoveRemoteVolumes(new[] { Path.GetFileName(dblockToRemove) }, default) + await db.RemoveRemoteVolumesAsync(new[] { Path.GetFileName(dblockToRemove) }, default) .ConfigureAwait(false); await db.Transaction.CommitAsync("test-remove-dblock", true, default).ConfigureAwait(false); } @@ -559,7 +559,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "Recovery backup should succeed"); } DumpDatabaseState(this.DBFILE, "After recovery backup"); @@ -593,7 +593,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller("file://" + this.TARGETFOLDER, testOptions, null)) { - var testRes = c.Test(long.MaxValue); + var testRes = await c.TestAsync(long.MaxValue); Assert.AreEqual(0, testRes.Errors.Count(), "Test operation should not fail after recovery from the interrupted backup"); } @@ -628,7 +628,7 @@ namespace Duplicati.UnitTest // 1. Complete backup using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count()); } @@ -652,8 +652,8 @@ namespace Duplicati.UnitTest using (var c = new Controller(failtarget, options, null)) { - Assert.Throws(() => - c.Backup(new[] { this.DATAFOLDER })); + Assert.Throws(async () => + await c.BackupAsync(new[] { this.DATAFOLDER })); } Assert.IsTrue(failed, "Expected dlist upload to fail"); @@ -666,7 +666,7 @@ namespace Duplicati.UnitTest // that are no longer referenced by any remaining fileset. using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var filesets = c.List().Filesets.ToList(); + var filesets = (await c.ListAsync()).Filesets.ToList(); Assert.That(filesets.Count, Is.GreaterThanOrEqualTo(1), "Should have at least one complete fileset to delete"); } @@ -677,7 +677,7 @@ namespace Duplicati.UnitTest }; using (var c = new Controller("file://" + this.TARGETFOLDER, deleteOptions, null)) { - var delRes = c.Delete(); + var delRes = await c.DeleteAsync(); Assert.AreEqual(0, delRes.Errors.Count(), "Delete should succeed"); } @@ -689,7 +689,7 @@ namespace Duplicati.UnitTest // interrupted backup even though the previous complete backup was deleted. using (var c = new Controller("file://" + this.TARGETFOLDER, options, null)) { - var res = c.Backup(new[] { this.DATAFOLDER }); + var res = await c.BackupAsync(new[] { this.DATAFOLDER }); Assert.AreEqual(0, res.Errors.Count(), "Recovery backup should succeed"); } @@ -723,7 +723,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + this.TARGETFOLDER, testOptions, null)) { - var testRes = c.Test(long.MaxValue); + var testRes = await c.TestAsync(long.MaxValue); Assert.AreEqual(0, testRes.Errors.Count(), "Test operation should not fail after recovery and delete workflow."); } diff --git a/Duplicati/UnitTest/TestUtils.cs b/Duplicati/UnitTest/TestUtils.cs index d56221bbf..ad1eb4193 100644 --- a/Duplicati/UnitTest/TestUtils.cs +++ b/Duplicati/UnitTest/TestUtils.cs @@ -52,7 +52,7 @@ namespace Duplicati.UnitTest } } - public static async Task GrowingFile(string testFile, CancellationToken token) + public static async Task GrowingFileAsync(string testFile, CancellationToken token) { try { diff --git a/Duplicati/UnitTest/ToolTests.cs b/Duplicati/UnitTest/ToolTests.cs index 20491f6a6..c8b6ed3f8 100644 --- a/Duplicati/UnitTest/ToolTests.cs +++ b/Duplicati/UnitTest/ToolTests.cs @@ -47,7 +47,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestDryRun() + public async Task TestDryRunAsync() { var l1 = Path.Combine(TARGETFOLDER, "l1"); var l2 = Path.Combine(TARGETFOLDER, "l2"); @@ -55,12 +55,12 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(l1); Directory.CreateDirectory(l2); - GenerateTestData(l1, 5, 0, 0, 1024).Wait(); + await GenerateTestDataAsync(l1, 5, 0, 0, 1024).ConfigureAwait(false); var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm", "--dry-run" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); Assert.IsFalse(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are equal"); @@ -72,7 +72,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestEmptySourceAndDestination() + public async Task TestEmptySourceAndDestinationAsync() { var l1 = Path.Combine(TARGETFOLDER, "empty_src"); var l2 = Path.Combine(TARGETFOLDER, "l2"); @@ -82,8 +82,8 @@ namespace Duplicati.UnitTest var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); @@ -94,7 +94,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestEmptySourceDeletesDestination() + public async Task TestEmptySourceDeletesDestinationAsync() { var l1 = Path.Combine(TARGETFOLDER, "empty_src"); var l2 = Path.Combine(TARGETFOLDER, "l2"); @@ -102,12 +102,12 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(l1); Directory.CreateDirectory(l2); - GenerateTestData(l2, 5, 0, 0, 1024).Wait(); + await GenerateTestDataAsync(l2, 5, 0, 0, 1024).ConfigureAwait(false); var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); @@ -118,7 +118,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestEmptySourceRenamesDestination() + public async Task TestEmptySourceRenamesDestinationAsync() { var l1 = Path.Combine(TARGETFOLDER, "empty_src"); var l2 = Path.Combine(TARGETFOLDER, "l2"); @@ -126,15 +126,15 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(l1); Directory.CreateDirectory(l2); - GenerateTestData(l2, 5, 0, 0, 1024).Wait(); + await GenerateTestDataAsync(l2, 5, 0, 0, 1024).ConfigureAwait(false); var filelist = Directory.EnumerateFiles(l2).ToList(); var files = filelist.Select(x => File.ReadAllBytes(x)).ToList(); var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm", "--retention" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); @@ -225,11 +225,11 @@ namespace Duplicati.UnitTest foreach (var args in testCases) { - int result = RemoteSynchronization.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult(); + int result = RemoteSynchronization.Program.MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult(); Assert.AreEqual(0, result, $"Failed for args: {string.Join(" ", args)}"); } - int failed_result = RemoteSynchronization.Program.Main(["source", "destination", "--bogus-option"]).ConfigureAwait(false).GetAwaiter().GetResult(); + int failed_result = RemoteSynchronization.Program.MainAsync(["source", "destination", "--bogus-option"]).ConfigureAwait(false).GetAwaiter().GetResult(); Assert.AreEqual(1, failed_result, "Invalid option did not return 1"); } @@ -238,7 +238,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestRemoteSynchronization() + public async Task TestRemoteSynchronizationAsync() { var l1 = Path.Combine(TARGETFOLDER, "l1"); var l2 = Path.Combine(TARGETFOLDER, "l2"); @@ -248,7 +248,7 @@ namespace Duplicati.UnitTest var options = TestOptions; var now = DateTime.Now; - GenerateTestData(DATAFOLDER, 5, 2, 2, 1024).Wait(); + await GenerateTestDataAsync(DATAFOLDER, 5, 2, 2, 1024).ConfigureAwait(false); Console.WriteLine($"Generated test data in {DATAFOLDER} in {DateTime.Now - now}"); // Create the directories if they do not exist @@ -262,7 +262,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l1}", options, null)) { now = DateTime.Now; - var results = c.Backup([DATAFOLDER]); + var results = await c.BackupAsync([DATAFOLDER]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Backed up {results.AddedFiles} files to {l1} in {DateTime.Now - now}"); @@ -270,7 +270,7 @@ namespace Duplicati.UnitTest // Call the tool now = DateTime.Now; - var exe = RemoteSynchronization.Program.Main; + var exe = RemoteSynchronization.Program.MainAsync; string[] args = [ $"file://{l1}", $"file://{l2}", "--global-options", ..options.Select(x => $"{x.Key}={x.Value}"), @@ -280,8 +280,8 @@ namespace Duplicati.UnitTest "--confirm" ]; var async_call = exe(args); - async_call.Wait(); - Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + await async_call.ConfigureAwait(false); + Assert.AreEqual(0, await async_call, "Remote synchronization tool did not return 0."); Console.WriteLine($"Remote synchronization tool returned 0 in {DateTime.Now - now}"); // Verify that the directories are equal @@ -292,7 +292,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l1}", options, null)) { now = DateTime.Now; - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); @@ -304,7 +304,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l2}", options, null)) { now = DateTime.Now; - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); @@ -325,7 +325,7 @@ namespace Duplicati.UnitTest now = DateTime.Now; try { - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); } catch (RemoteListVerificationException) { @@ -336,8 +336,8 @@ namespace Duplicati.UnitTest // Run the tool again to copy the missing file now = DateTime.Now; async_call = exe(args); - async_call.Wait(); - Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + await async_call.ConfigureAwait(false); + Assert.AreEqual(0, await async_call, "Remote synchronization tool did not return 0."); Console.WriteLine($"Remote synchronization tool returned 0 in {DateTime.Now - now}"); // Try to restore the second level again @@ -345,7 +345,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l2}", options, null)) { now = DateTime.Now; - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); @@ -354,13 +354,13 @@ namespace Duplicati.UnitTest Assert.IsTrue(DirectoriesAndContentsAreEqual(DATAFOLDER, l2r), "Restored second level files is not equal to original files"); // Add some more files to the source - GenerateTestData(Path.Combine(DATAFOLDER, "brand_new_files"), 5, 2, 2, 1024).Wait(); + await GenerateTestDataAsync(Path.Combine(DATAFOLDER, "brand_new_files"), 5, 2, 2, 1024).ConfigureAwait(false); // Backup the new files to l1 using (var c = new Controller($"file://{l1}", options, null)) { now = DateTime.Now; - var results = c.Backup([DATAFOLDER]); + var results = await c.BackupAsync([DATAFOLDER]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Backed up {results.AddedFiles} files to {l1} in {DateTime.Now - now}"); @@ -369,8 +369,8 @@ namespace Duplicati.UnitTest // Run the tool again to copy the new files now = DateTime.Now; async_call = exe(args); - async_call.Wait(); - Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + await async_call.ConfigureAwait(false); + Assert.AreEqual(0, await async_call, "Remote synchronization tool did not return 0."); Console.WriteLine($"Remote synchronization tool returned 0 in {DateTime.Now - now}"); // Try to restore the second level again @@ -378,7 +378,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l2}", options, null)) { now = DateTime.Now; - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); @@ -396,7 +396,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l1}", options, null)) { now = DateTime.Now; - var results = c.Backup([DATAFOLDER]); + var results = await c.BackupAsync([DATAFOLDER]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Backed up {results.AddedFiles} files to {l1} in {DateTime.Now - now}"); @@ -406,7 +406,7 @@ namespace Duplicati.UnitTest using (var c = new Controller($"file://{l1}", options, null)) { now = DateTime.Now; - var results = c.Compact(); + var results = await c.CompactAsync().ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Compacted backup in {DateTime.Now - now}"); @@ -415,15 +415,15 @@ namespace Duplicati.UnitTest // Run the tool again to copy the new files now = DateTime.Now; async_call = exe(args); - async_call.Wait(); - Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + var result = await async_call.ConfigureAwait(false); + Assert.AreEqual(0, result, "Remote synchronization tool did not return 0."); // Try to restore the second level again options["restore-path"] = l2r; using (var c = new Controller($"file://{l2}", options, null)) { now = DateTime.Now; - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); @@ -437,15 +437,15 @@ namespace Duplicati.UnitTest // Perform a forced synchronization with retention to check that retention doesn't break a restore now = DateTime.Now; async_call = exe([.. args, "--force", "--retention"]); - async_call.Wait(); - Assert.AreEqual(0, async_call.Result, "Remote synchronization tool did not return 0."); + var res = await async_call.ConfigureAwait(false); + Assert.AreEqual(0, res, "Remote synchronization tool did not return 0."); // Try to restore the second level again options["restore-path"] = l2r; using (var c = new Controller($"file://{l2}", options, null)) { now = DateTime.Now; - var results = c.Restore([]); + var results = await c.RestoreAsync([]).ConfigureAwait(false); Assert.AreEqual(0, results.Errors.Count()); Assert.AreEqual(0, results.Warnings.Count()); Console.WriteLine($"Restored {results.RestoredFiles} files to {options["restore-path"]} in {DateTime.Now - now}"); @@ -464,7 +464,7 @@ namespace Duplicati.UnitTest [TestCase(false, true, "1,3")] // Fail middle transfers on destination. [TestCase(false, true, "4")] // Fail last transfer on destination. [TestCase(false, true, "0,1,2,3,4")] // Fail all transfers on destination. - public void TestRemoteSynchronizationWithFaultyBackend(bool failSource, bool failDest, string failIndices) + public async Task TestRemoteSynchronizationWithFaultyBackendAsync(bool failSource, bool failDest, string failIndices) { var expect_to_fail = failIndices == "0,1,2,3,4"; @@ -495,7 +495,7 @@ namespace Duplicati.UnitTest return false; // No failure }; - GenerateTestData(l1, 5, 0, 0, 1024).Wait(); + await GenerateTestDataAsync(l1, 5, 0, 0, 1024).ConfigureAwait(false); // Setup backend URLs with failure injection var protocol = new DeterministicErrorBackend().ProtocolKey; @@ -519,8 +519,8 @@ namespace Duplicati.UnitTest Console.SetError(errorBuffer); } - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); // Expect nonzero return code if any backend fails less than the number of retries if (expect_to_fail) @@ -533,7 +533,7 @@ namespace Duplicati.UnitTest catch { Console.SetError(originalError); // Restore standard error - Console.Error.WriteLine(errorBuffer?.ToString()); + await Console.Error.WriteLineAsync(errorBuffer?.ToString()); throw; } } @@ -549,7 +549,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestVerifies() + public async Task TestVerifiesAsync() { var l1 = Path.Combine(TARGETFOLDER, "l1"); var l2 = Path.Combine(TARGETFOLDER, "l2"); @@ -557,7 +557,7 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(l1); Directory.CreateDirectory(l2); - GenerateTestData(l1, 5, 0, 0, 1024).Wait(); + await GenerateTestDataAsync(l1, 5, 0, 0, 1024).ConfigureAwait(false); var filenames = Directory.EnumerateFiles(l1).Take(2).ToList(); var first_file = filenames.First(); @@ -576,8 +576,8 @@ namespace Duplicati.UnitTest var args = new string[] { $"file://{l1}", $"file://{l2}", "--confirm", "--verify-contents", "--verify-get-after-put" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreEqual(0, return_code, "Remote synchronization tool did not return 0."); Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Synchronized directories are not equal"); @@ -632,26 +632,26 @@ namespace Duplicati.UnitTest /// How many subdirectories the directory should have. /// How deep the number of subdirectories within subdirectories should go. /// The maximum size of the files to generate. - public static async Task GenerateTestData(string dir, int n_files, int n_dirs, int n_levels, int max_file_size) + public static async Task GenerateTestDataAsync(string dir, int n_files, int n_dirs, int n_levels, int max_file_size) { if (!SystemIO.IO_OS.DirectoryExists(dir)) SystemIO.IO_OS.DirectoryCreate(dir); var fs = Enumerable.Range(0, n_files) - .Select(i => GenerateTestFile(dir, i, max_file_size)); + .Select(i => GenerateTestFileAsync(dir, i, max_file_size)); var ds = n_levels > 0 ? Enumerable.Range(0, n_dirs) .Select(i => { var subdir = Path.Combine(dir, $"dir_{i}"); - return GenerateTestData(subdir, n_files, n_dirs, n_levels - 1, max_file_size); + return GenerateTestDataAsync(subdir, n_files, n_dirs, n_levels - 1, max_file_size); }) : []; await Task.WhenAll([.. fs, .. ds]); } - public static async Task GenerateTestFile(string dir, int i, int max_file_size) + public static async Task GenerateTestFileAsync(string dir, int i, int max_file_size) { var rnd = new Random(); var file = Path.Combine(dir, $"file_{i}.txt"); @@ -683,7 +683,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestRemoteSynchronizationQuotaCheckFails() + public async Task TestRemoteSynchronizationQuotaCheckFailsAsync() { var l1 = Path.Combine(TARGETFOLDER, "quota_src"); var l2 = Path.Combine(TARGETFOLDER, "quota_dst"); @@ -692,7 +692,7 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(l2); // Generate test data: 5 files of ~1KB each, total ~5KB - GenerateTestData(l1, 5, 0, 0, 2048).Wait(); + await GenerateTestDataAsync(l1, 5, 0, 0, 2048).ConfigureAwait(false); // Register the test backend Library.DynamicLoader.BackendLoader.AddBackend(new FileBackendWith1Kb()); @@ -700,8 +700,8 @@ namespace Duplicati.UnitTest // Use the test backend for destination with only 1KB free quota var args = new string[] { $"file://{l1}", $"quotatest://{l2}", "--confirm" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreNotEqual(0, return_code, "Remote synchronization should fail due to insufficient quota."); } @@ -711,7 +711,7 @@ namespace Duplicati.UnitTest /// [Test] [Category("Tools/RemoteSynchronization")] - public void TestRemoteSynchronizationQuotaCheckSucceeds() + public async Task TestRemoteSynchronizationQuotaCheckSucceedsAsync() { var l1 = Path.Combine(TARGETFOLDER, "quota_src2"); var l2 = Path.Combine(TARGETFOLDER, "quota_dst2"); @@ -720,7 +720,7 @@ namespace Duplicati.UnitTest Directory.CreateDirectory(l2); // Generate small test data: 1 file of ~500B - GenerateTestData(l1, 1, 0, 0, 512).Wait(); + await GenerateTestDataAsync(l1, 1, 0, 0, 512).ConfigureAwait(false); // Register the test backend Library.DynamicLoader.BackendLoader.AddBackend(new FileBackendWith1Kb()); @@ -728,8 +728,8 @@ namespace Duplicati.UnitTest // Use the test backend for destination with 1KB free quota (sufficient) var args = new string[] { $"file://{l1}", $"quotatest://{l2}", "--confirm", "--dst-options", "freequota=1024" }; - var async_call = RemoteSynchronization.Program.Main(args); - var return_code = async_call.ConfigureAwait(false).GetAwaiter().GetResult(); + var async_call = RemoteSynchronization.Program.MainAsync(args); + var return_code = await async_call.ConfigureAwait(false); Assert.AreEqual(0, return_code, "Remote synchronization should succeed with sufficient quota."); Assert.IsTrue(DirectoriesAndContentsAreEqual(l1, l2), "Directories should be synchronized."); diff --git a/Duplicati/UnitTest/UtilityExtraTests.cs b/Duplicati/UnitTest/UtilityExtraTests.cs index b53d8cf23..792f45e5d 100644 --- a/Duplicati/UnitTest/UtilityExtraTests.cs +++ b/Duplicati/UnitTest/UtilityExtraTests.cs @@ -60,11 +60,11 @@ namespace Duplicati.UnitTest [Test] [Category("Utility")] - public async Task UpdateOptionsFromDbPopulatesMissingValues() + public async Task UpdateOptionsFromDbPopulatesMissingValuesAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary + await db.SetDbOptionsAsync(new Dictionary { { "prefix", "custom-prefix" }, { "blocksize", "16384" }, @@ -77,7 +77,7 @@ namespace Duplicati.UnitTest var options = new Options(new Dictionary()); - await Utility.UpdateOptionsFromDb(db, options, CancellationToken.None); + await Utility.UpdateOptionsFromDbAsync(db, options, CancellationToken.None); Assert.That(options.RawOptions.TryGetValue("prefix", out var prefix), Is.True); Assert.That(prefix, Is.EqualTo("custom-prefix")); @@ -97,11 +97,11 @@ namespace Duplicati.UnitTest [Test] [Category("Utility")] - public async Task UpdateOptionsFromDbRespectsExistingValues() + public async Task UpdateOptionsFromDbRespectsExistingValuesAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary + await db.SetDbOptionsAsync(new Dictionary { { "prefix", "stored-prefix" }, { "blocksize", "4096" }, @@ -123,7 +123,7 @@ namespace Duplicati.UnitTest { "encryption-module", "gpg" } }); - await Utility.UpdateOptionsFromDb(db, options, CancellationToken.None); + await Utility.UpdateOptionsFromDbAsync(db, options, CancellationToken.None); Assert.That(options.RawOptions["prefix"], Is.EqualTo("cli-prefix")); Assert.That(options.RawOptions["blocksize"], Is.EqualTo("32kb")); @@ -136,18 +136,18 @@ namespace Duplicati.UnitTest [Test] [Category("Utility")] - public async Task UpdateOptionsFromDbRestoresNoEncryptionWhenStoredBackupIsUnencrypted() + public async Task UpdateOptionsFromDbRestoresNoEncryptionWhenStoredBackupIsUnencryptedAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary + await db.SetDbOptionsAsync(new Dictionary { { "passphrase", "no-encryption" } }, CancellationToken.None); var options = new Options(new Dictionary()); - await Utility.UpdateOptionsFromDb(db, options, CancellationToken.None); + await Utility.UpdateOptionsFromDbAsync(db, options, CancellationToken.None); Assert.That(options.RawOptions.TryGetValue("no-encryption", out var noEncryption), Is.True); Assert.That(noEncryption, Is.EqualTo("true")); @@ -155,40 +155,40 @@ namespace Duplicati.UnitTest [Test] [Category("Utility")] - public async Task ContainsOptionsForVerificationDetectsSensitiveEntries() + public async Task ContainsOptionsForVerificationDetectsSensitiveEntriesAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary + await db.SetDbOptionsAsync(new Dictionary { { "compression-module", "zip" } }, CancellationToken.None); - var result = await Utility.ContainsOptionsForVerification(db, CancellationToken.None); + var result = await Utility.ContainsOptionsForVerificationAsync(db, CancellationToken.None); Assert.That(result, Is.True); } [Test] [Category("Utility")] - public async Task ContainsOptionsForVerificationReturnsFalseWhenOptionsAbsent() + public async Task ContainsOptionsForVerificationReturnsFalseWhenOptionsAbsentAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary(), CancellationToken.None); + await db.SetDbOptionsAsync(new Dictionary(), CancellationToken.None); - var result = await Utility.ContainsOptionsForVerification(db, CancellationToken.None); + var result = await Utility.ContainsOptionsForVerificationAsync(db, CancellationToken.None); Assert.That(result, Is.False); } [Test] [Category("Utility")] - public async Task VerifyOptionsThrowsWhenAddingPassphraseWithoutPermission() + public async Task VerifyOptionsThrowsWhenAddingPassphraseWithoutPermissionAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary + await db.SetDbOptionsAsync(new Dictionary { { "blocksize", "16384" }, { "blockhash", Library.Utility.HashFactory.SHA256 }, @@ -206,17 +206,17 @@ namespace Duplicati.UnitTest }); var ex = Assert.ThrowsAsync(async () => - await Utility.VerifyOptionsAndUpdateDatabase(db, options, CancellationToken.None)); + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, options, CancellationToken.None)); Assert.That(ex?.Message, Does.Contain("add a passphrase")); - var storedOptions = await db.GetDbOptions(CancellationToken.None); + var storedOptions = await db.GetDbOptionsAsync(CancellationToken.None); Assert.That(storedOptions["passphrase"], Is.EqualTo("no-encryption")); } [Test] [Category("Utility")] - public async Task VerifyOptionsAndUpdateDatabasePersistsWritePathValues() + public async Task VerifyOptionsAndUpdateDatabasePersistsWritePathValuesAsync() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); @@ -233,9 +233,9 @@ namespace Duplicati.UnitTest { "passphrase", "secret" } }); - await Utility.VerifyOptionsAndUpdateDatabase(db, options, CancellationToken.None); + await Utility.VerifyOptionsAndUpdateDatabaseAsync(db, options, CancellationToken.None); - var storedOptions = await db.GetDbOptions(CancellationToken.None); + var storedOptions = await db.GetDbOptionsAsync(CancellationToken.None); Assert.That(storedOptions["prefix"], Is.EqualTo("persisted-prefix")); Assert.That(storedOptions["blocksize"], Is.EqualTo(options.Blocksize.ToString())); Assert.That(storedOptions["blockhash"], Is.EqualTo(Library.Utility.HashFactory.SHA256)); @@ -249,11 +249,11 @@ namespace Duplicati.UnitTest [Test] [Category("Utility")] - public async Task PersistOptionsToDatabaseRemovesStaleEncryptionMarkersForUnencryptedBackup() + public async Task PersistOptionsToDatabaseRemovesStaleEncryptionMarkersForUnencryptedBackup_Async() { using var tempDb = new TempFile(); await using var db = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "test", true, null, CancellationToken.None); - await db.SetDbOptions(new Dictionary + await db.SetDbOptionsAsync(new Dictionary { { "compression-module", "zip" }, { "encryption-module", "aes" }, @@ -270,10 +270,10 @@ namespace Duplicati.UnitTest { "no-encryption", bool.TrueString } }); - await Utility.PersistOptionsToDatabaseWithoutValidation(tempDb, options, "UnitTestPersistOptions", CancellationToken.None); + await Utility.PersistOptionsToDatabaseWithoutValidationAsync(tempDb, options, "UnitTestPersistOptions", CancellationToken.None); await using var reopened = await LocalDatabase.CreateLocalDatabaseAsync(tempDb, "verify", true, null, CancellationToken.None); - var storedOptions = await reopened.GetDbOptions(CancellationToken.None); + var storedOptions = await reopened.GetDbOptionsAsync(CancellationToken.None); Assert.That(storedOptions["compression-module"], Is.EqualTo("zip")); Assert.That(storedOptions["passphrase"], Is.EqualTo("no-encryption")); diff --git a/Duplicati/UnitTest/UtilityTests.cs b/Duplicati/UnitTest/UtilityTests.cs index 72071fda5..ffb16516c 100644 --- a/Duplicati/UnitTest/UtilityTests.cs +++ b/Duplicati/UnitTest/UtilityTests.cs @@ -112,7 +112,7 @@ namespace Duplicati.UnitTest long fixedGrowingStreamLength, limitStreamLength, nextGrowingStreamLength; using (CancellationTokenSource tokenSource = new CancellationTokenSource()) { - Task task = TestUtils.GrowingFile(growingFilename, tokenSource.Token); + Task task = TestUtils.GrowingFileAsync(growingFilename, tokenSource.Token); Thread.Sleep(100); using (FileStream growingStream = new FileStream(growingFilename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { @@ -135,7 +135,7 @@ namespace Duplicati.UnitTest [Test] [Category("Utility")] - public static void ReadLimitLengthStream() + public static async Task ReadLimitLengthStreamAsync() { Action, IEnumerable> assertArray = (expected, actual) => { @@ -187,12 +187,12 @@ namespace Duplicati.UnitTest Assert.AreEqual(5, stream.Length); // Test reading past array bounds - Assert.AreEqual(5, stream.Read(readBuffer, 0, 10), "Read count"); + Assert.AreEqual(5, await stream.ReadAsync(readBuffer, 0, 10), "Read count"); assertArray(Enumerable.Range(0, 5), readBuffer.Take(5)); // Set the position directly and read a shorter range stream.Position = 2; - Assert.AreEqual(2, stream.ReadAsync(readBuffer, 0, 2).Await(), "Read count"); + Assert.AreEqual(2, await stream.ReadAsync(readBuffer, 0, 2), "Read count"); assertArray(Enumerable.Range(2, 2), readBuffer.Take(2)); } @@ -204,14 +204,14 @@ namespace Duplicati.UnitTest Assert.AreEqual(0, stream.Position); // Test basic read - Assert.AreEqual(4, stream.Read(readBuffer, 0, 4), "Read count"); + Assert.AreEqual(4, await stream.ReadAsync(readBuffer, 0, 4), "Read count"); assertArray(Enumerable.Range(2, 4), readBuffer.Take(4)); // Test CopyTo using (MemoryStream destination = new MemoryStream()) { stream.Position = 0; - stream.CopyTo(destination); + await stream.CopyToAsync(destination); assertArray(Enumerable.Range(2, 4), destination.ToArray()); } @@ -219,7 +219,7 @@ namespace Duplicati.UnitTest using (MemoryStream destination = new MemoryStream()) { stream.Position = 0; - stream.CopyToAsync(destination).Await(); + await stream.CopyToAsync(destination); assertArray(Enumerable.Range(2, 4), destination.ToArray()); } @@ -269,7 +269,7 @@ namespace Duplicati.UnitTest Assert.AreEqual(0, stream.Position); // Test basic read - Assert.AreEqual(0, stream.Read(readBuffer, 0, 4), "Read count"); + Assert.AreEqual(0, await stream.ReadAsync(readBuffer, 0, 4), "Read count"); // Test seeking testSeek(stream, 0, -1); diff --git a/Duplicati/UnitTest/VacuumAndBugReportTests.cs b/Duplicati/UnitTest/VacuumAndBugReportTests.cs index 9714d92f0..48c883e4b 100644 --- a/Duplicati/UnitTest/VacuumAndBugReportTests.cs +++ b/Duplicati/UnitTest/VacuumAndBugReportTests.cs @@ -28,6 +28,7 @@ using System.Threading; using Duplicati.Library.Main; using NUnit.Framework; using NUnit.Framework.Legacy; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -37,21 +38,21 @@ namespace Duplicati.UnitTest { [Test] [Category("Vacuum")] - public void VacuumDatabase() + public async Task VacuumDatabaseAsync() { var opts = new Dictionary(TestOptions); using (var conn = new Microsoft.Data.Sqlite.SqliteConnection($"Data Source={DBFILE};Pooling=false")) { - conn.Open(); + await conn.OpenAsync(); using var cmd = conn.CreateCommand(); cmd.CommandText = "CREATE TABLE test (id INTEGER); INSERT INTO test(id) VALUES (1);"; - cmd.ExecuteNonQuery(); + await cmd.ExecuteNonQueryAsync(); } using (var c = new Controller("file://" + TARGETFOLDER, opts, null)) { - var res = c.Vacuum(); + var res = await c.VacuumAsync(); TestUtils.AssertResults(res); Assert.AreEqual(OperationMode.Vacuum, ((dynamic)res).MainOperation); } @@ -78,7 +79,7 @@ namespace Duplicati.UnitTest [Test] [Category("CreateBugReport")] - public void CreateBugReportZip([Values(true, false)] bool vacuum) + public async Task CreateBugReportZipAsync([Values(true, false)] bool vacuum) { File.WriteAllBytes(Path.Combine(DATAFOLDER, "file.txt"), new byte[] { 1 }); @@ -89,7 +90,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, opts, null)) { - var backup = c.Backup(new[] { DATAFOLDER }); + var backup = await c.BackupAsync(new[] { DATAFOLDER }); TestUtils.AssertResults(backup); } for (int i = 0; i < 3; i++) @@ -103,7 +104,7 @@ namespace Duplicati.UnitTest using (var c = new Controller("file://" + TARGETFOLDER, opts, null)) { - var res = c.CreateLogDatabase(basepath); + var res = await c.CreateLogDatabaseAsync(basepath); TestUtils.AssertResults(res); Assert.AreEqual(expected, res.TargetPath); diff --git a/Duplicati/UnitTest/ZipFallbackTest.cs b/Duplicati/UnitTest/ZipFallbackTest.cs index f7cdd4596..9f1e4d1ca 100644 --- a/Duplicati/UnitTest/ZipFallbackTest.cs +++ b/Duplicati/UnitTest/ZipFallbackTest.cs @@ -24,6 +24,7 @@ using System.IO; using System.Linq; using Duplicati.Library.Utility; using NUnit.Framework; +using System.Threading.Tasks; using Assert = NUnit.Framework.Legacy.ClassicAssert; namespace Duplicati.UnitTest @@ -32,7 +33,7 @@ namespace Duplicati.UnitTest { [Test] [Category("Targeted")] - public void FallbackToSharpCompressOnDecompressLzmaStreams() + public async Task FallbackToSharpCompressOnDecompressLzmaStreamsAsync() { var testopts = TestOptions; testopts["zip-compression-method"] = "lzma"; @@ -43,7 +44,7 @@ namespace Duplicati.UnitTest File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Backup(new string[] { DATAFOLDER }); + var r = await c.BackupAsync(new string[] { DATAFOLDER }); Assert.AreEqual(0, r.Errors.Count()); Assert.AreEqual(0, r.Warnings.Count()); } @@ -58,7 +59,7 @@ namespace Duplicati.UnitTest // Recreate the database using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) { - var r = c.Repair(); + var r = await c.RepairAsync(); Assert.AreEqual(0, r.Errors.Count()); // Ensure that we get warnings for the fallback, one for the dlist and one for the dindex Assert.AreEqual(2, r.Warnings.Count()); @@ -68,7 +69,7 @@ namespace Duplicati.UnitTest // Restore files using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) { - var r = c.Restore(null); + var r = await c.RestoreAsync(null); Assert.AreEqual(0, r.Errors.Count()); // Ensure that we get warnings for the fallback, one for the dblock file Assert.AreEqual(1, r.Warnings.Count()); @@ -83,7 +84,7 @@ namespace Duplicati.UnitTest [Category("Targeted")] [TestCase("zip-sc")] [TestCase("zip-io")] - public void SupportZip64WithDefaultSettings(string module) + public async Task SupportZip64WithDefaultSettingsAsync(string module) { const long testfileSize = 5L * 1024 * 1024 * 1024; var testopts = TestOptions; @@ -111,7 +112,7 @@ namespace Duplicati.UnitTest var data = new byte[1024 * 1024 * 10]; File.WriteAllBytes(Path.Combine(DATAFOLDER, "a"), data); using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { control_files = tempfile.Name }), null)) - TestUtils.AssertResults(c.Backup(new string[] { DATAFOLDER })); + TestUtils.AssertResults(await c.BackupAsync(new string[] { DATAFOLDER })); } // Delete the local database @@ -119,14 +120,14 @@ namespace Duplicati.UnitTest // Recreate the database using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts, null)) - TestUtils.AssertResults(c.Repair()); + TestUtils.AssertResults(await c.RepairAsync()); var restoredfile = Path.Combine(RESTOREFOLDER, tempfilename); try { // Restore control file using (var c = new Library.Main.Controller("file://" + TARGETFOLDER, testopts.Expand(new { restore_path = RESTOREFOLDER }), null)) - TestUtils.AssertResults(c.RestoreControlFiles(["*"])); + TestUtils.AssertResults(await c.RestoreControlFilesAsync(["*"])); // Check that the control file was restored Assert.That(File.Exists(restoredfile), Is.True, "Control file was not restored"); diff --git a/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs b/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs index ca9761038..eba518cca 100644 --- a/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs +++ b/Duplicati/WebserverCore/Abstractions/ILoginProvider.cs @@ -32,7 +32,7 @@ public interface ILoginProvider /// Whether to issue a short-lived refresh token. /// The cancellation token. /// The access token, refresh token, and nonce. - Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithSigninToken(string signinTokenString, bool shortLived, CancellationToken ct); + Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithSigninTokenAsync(string signinTokenString, bool shortLived, CancellationToken ct); /// /// Performs a login with a refresh token. @@ -40,7 +40,7 @@ public interface ILoginProvider /// The refresh token. /// The cancellation token. /// The access and refresh tokens. - Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithRefreshToken(string refreshTokenString, string? nonce, CancellationToken ct); + Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithRefreshTokenAsync(string refreshTokenString, string? nonce, CancellationToken ct); /// /// Performs a login with a password. @@ -49,7 +49,7 @@ public interface ILoginProvider /// Whether to issue a short-lived refresh token. /// The cancellation token. /// The access token, refresh token, and nonce. - Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithPassword(string password, bool shortLived, CancellationToken ct); + Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithPasswordAsync(string password, bool shortLived, CancellationToken ct); /// /// Performs a logout with a refresh token. @@ -58,7 +58,7 @@ public interface ILoginProvider /// The nonce associated with the refresh token. /// The cancellation token. /// The task. - Task PerformLogoutWithRefreshToken(string refreshTokenString, string? nonce, CancellationToken ct); + Task PerformLogoutWithRefreshTokenAsync(string refreshTokenString, string? nonce, CancellationToken ct); /// /// Performs a complete logout for a user. @@ -66,5 +66,5 @@ public interface ILoginProvider /// The user ID. /// The cancellation token. /// The task. - Task PerformCompleteLogout(string userId, CancellationToken ct); + Task PerformCompleteLogoutAsync(string userId, CancellationToken ct); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs b/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs index f7da3b6bc..c723b94bf 100644 --- a/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs +++ b/Duplicati/WebserverCore/Abstractions/IRemoteControllerRegistration.cs @@ -30,13 +30,13 @@ public interface IRemoteControllerRegistration /// /// The URL to register the machine with /// The task to wait on - Task RegisterMachine(string registrationUrl); + Task RegisterMachineAsync(string registrationUrl); /// /// Waits for the registration to complete. /// /// The task to wait on - public Task WaitForRegistration(); + public Task WaitForRegistrationAsync(); /// /// Cancels the registration of the machine diff --git a/Duplicati/WebserverCore/Abstractions/ITaskQueueService.cs b/Duplicati/WebserverCore/Abstractions/ITaskQueueService.cs index 010030312..8b026ac91 100644 --- a/Duplicati/WebserverCore/Abstractions/ITaskQueueService.cs +++ b/Duplicati/WebserverCore/Abstractions/ITaskQueueService.cs @@ -43,11 +43,11 @@ public interface ITaskQueueService /// Stops a task with the specified task ID. This will stop the task if it is currently running. /// /// The ID of the task to stop. - void StopTask(long taskid); + Task StopTaskAsync(long taskid); /// /// Aborts a task with the specified task ID. This will immediately terminate the task with minimal waiting for it to finish. /// /// The ID of the task to abort. - void AbortTask(long taskid); + Task AbortTaskAsync(long taskid); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs b/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs index 6bb26a1b2..ae5d76a1f 100644 --- a/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs +++ b/Duplicati/WebserverCore/Abstractions/ITokenFamilyStore.cs @@ -32,14 +32,14 @@ public interface ITokenFamilyStore /// The family ID. /// The cancellation token. /// The token family. - Task GetTokenFamily(string userId, string familyId, CancellationToken ct); + Task GetTokenFamilyAsync(string userId, string familyId, CancellationToken ct); /// /// Creates a token family. /// /// The user ID. /// The cancellation token. /// The token family. - Task CreateTokenFamily(string userId, CancellationToken ct); + Task CreateTokenFamilyAsync(string userId, CancellationToken ct); /// /// Invalidates a token family. /// @@ -47,26 +47,26 @@ public interface ITokenFamilyStore /// The family /// The cancellation token. /// The task. - Task InvalidateTokenFamily(string userId, string familyId, CancellationToken ct); + Task InvalidateTokenFamilyAsync(string userId, string familyId, CancellationToken ct); /// /// Invalidates all token families for a given userId. /// /// The user ID. /// The cancellation token. /// The task. - Task InvalidateAllTokenFamilies(string userId, CancellationToken ct); + Task InvalidateAllTokenFamiliesAsync(string userId, CancellationToken ct); /// /// Invalidates all token families. /// /// The cancellation token. - Task InvalidateAllTokens(CancellationToken ct); + Task InvalidateAllTokensAsync(CancellationToken ct); /// /// Increments a token family. /// /// The token family. /// The cancellation token. /// The incremented token family. - Task IncrementTokenFamily(TokenFamily tokenFamily, CancellationToken ct); + Task IncrementTokenFamilyAsync(TokenFamily tokenFamily, CancellationToken ct); /// /// Represents a token family. diff --git a/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs b/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs index e82b9d7d2..3f3e63094 100644 --- a/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs +++ b/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAccessor.cs @@ -77,5 +77,5 @@ public interface IWebsocketAccessor /// The new WebSocket connection to add. /// If true, the connection will subscribe to legacy status updates. /// A task representing the asynchronous operation. - Task AddConnection(WebSocket newConnection, bool subscribeToLegacyStatus); + Task AddConnectionAsync(WebSocket newConnection, bool subscribeToLegacyStatus); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAuthenticator.cs b/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAuthenticator.cs index b8409c5ea..f43dd74a6 100644 --- a/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAuthenticator.cs +++ b/Duplicati/WebserverCore/Abstractions/Notifications/IWebsocketAuthenticator.cs @@ -32,5 +32,5 @@ public interface IWebsocketAuthenticator /// /// The new WebSocket connection to add. /// A task representing the asynchronous operation. - Task AddConnection(WebSocket newConnection); + Task AddConnectionAsync(WebSocket newConnection); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Client/DuplicatiServerClient.cs b/Duplicati/WebserverCore/Client/DuplicatiServerClient.cs index 5523a64e9..01f7f9680 100644 --- a/Duplicati/WebserverCore/Client/DuplicatiServerClient.cs +++ b/Duplicati/WebserverCore/Client/DuplicatiServerClient.cs @@ -77,7 +77,7 @@ public class DuplicatiServerClient : IDisposable /// The idea of having a separate Authenticate method is to allow for explicit authentication to avoid getting a 401 result on the server log. /// /// The cancellation token. Optional, defaults to . - public async Task Authenticate(CancellationToken cancellationToken = default) + public async Task AuthenticateAsync(CancellationToken cancellationToken = default) { await RefreshTokenAsync(cancellationToken).ConfigureAwait(false); } diff --git a/Duplicati/WebserverCore/DuplicatiWebserver.cs b/Duplicati/WebserverCore/DuplicatiWebserver.cs index 9dd8a1640..7b641b40a 100644 --- a/Duplicati/WebserverCore/DuplicatiWebserver.cs +++ b/Duplicati/WebserverCore/DuplicatiWebserver.cs @@ -281,7 +281,7 @@ public class DuplicatiWebserver OnTokenValidated = context => { var store = context.HttpContext.RequestServices.GetRequiredService(); - return JWTTokenProvider.ValidateAccessToken(context, store); + return JWTTokenProvider.ValidateAccessTokenAsync(context, store); } }; }); @@ -470,7 +470,7 @@ public class DuplicatiWebserver /// Starts the webserver /// /// The task that will be set when the server is terminated - public Task Start() + public Task StartAsync() { App.MapHealthChecks("/health"); App.AddEndpoints(CorsEnabled) @@ -483,8 +483,6 @@ public class DuplicatiWebserver /// Stops the webserver /// /// An awaitable task - public async Task Stop() - { - await App.StopAsync(); - } + public Task StopAsync() + => App.StopAsync(); } diff --git a/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs b/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs index 6ec5e112b..6091b126b 100644 --- a/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs +++ b/Duplicati/WebserverCore/Endpoints/Shared/SharedRemoteOperation.cs @@ -77,7 +77,7 @@ public class SharedRemoteOperation /// The prefix, if this is a remote source /// The cancellation token /// The unmasked URL and the options after the expansion - public static async Task<(string Url, Dictionary Options)> ExpandUrl(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, string? sourcePrefix, CancellationToken cancelToken) + public static async Task<(string Url, Dictionary Options)> ExpandUrlAsync(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, string? sourcePrefix, CancellationToken cancelToken) { url = UnmaskUrl(connection, url, backupId, connectionStringId, sourcePrefix); var uri = new Library.Utility.Uri(url); @@ -98,26 +98,26 @@ public class SharedRemoteOperation return (url, opts); } - public static async Task GetBackend(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken) + public static async Task GetBackendAsync(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, CancellationToken cancelToken) { - (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, null, cancelToken); + (url, var opts) = await ExpandUrlAsync(connection, applicationSettings, url, backupId, connectionStringId, null, cancelToken); var modules = ConfigureModules(opts); var backend = Library.DynamicLoader.BackendLoader.GetBackend(url, opts); return new BackendTupleDisposeWrapper(backend, modules); } - public static async Task GetSourceProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, string? sourcePrefix, CancellationToken cancelToken) + public static async Task GetSourceProviderForTestingAsync(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, string? sourcePrefix, CancellationToken cancelToken) { - (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, sourcePrefix, cancelToken); + (url, var opts) = await ExpandUrlAsync(connection, applicationSettings, url, backupId, connectionStringId, sourcePrefix, cancelToken); var modules = ConfigureModules(opts); var sourceProvider = await Library.DynamicLoader.SourceProviderLoader.GetSourceProviderForTesting(url, "", opts, cancelToken); return new SourceProviderTupleDisposeWrapper(sourceProvider, modules); } - public static async Task GetRestoreDestinationProviderForTesting(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, string? sourcePrefix, CancellationToken cancelToken) + public static async Task GetRestoreDestinationProviderForTestingAsync(Connection connection, IApplicationSettings applicationSettings, string url, string? backupId, long connectionStringId, string? sourcePrefix, CancellationToken cancelToken) { - (url, var opts) = await ExpandUrl(connection, applicationSettings, url, backupId, connectionStringId, sourcePrefix, cancelToken); + (url, var opts) = await ExpandUrlAsync(connection, applicationSettings, url, backupId, connectionStringId, sourcePrefix, cancelToken); var modules = ConfigureModules(opts); var restoreDestinationProvider = await Library.DynamicLoader.RestoreDestinationProviderLoader.GetRestoreDestinationProviderForTesting(url, opts, cancelToken); diff --git a/Duplicati/WebserverCore/Endpoints/V1/Auth.cs b/Duplicati/WebserverCore/Endpoints/V1/Auth.cs index 91360a2de..83f2cbf61 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Auth.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Auth.cs @@ -47,7 +47,7 @@ public partial class Auth : IEndpointV1 { try { - var result = await loginProvider.PerformLoginWithRefreshToken(refreshTokenString, input?.Nonce ?? "", ct); + var result = await loginProvider.PerformLoginWithRefreshTokenAsync(refreshTokenString, input?.Nonce ?? "", ct); AddCookie(httpContextAccessor.HttpContext, cookieName, result.RefreshToken, DateTimeOffset.UtcNow.AddMinutes(jWTConfig.RefreshTokenDurationInMinutes)); return new Dto.AccessTokenOutputDto(result.AccessToken, result.Nonce); } @@ -70,7 +70,7 @@ public partial class Auth : IEndpointV1 var cookieName = GetCookieName(httpContextAccessor); try { - var result = await loginProvider.PerformLoginWithSigninToken(input.SigninToken, !(input.RememberMe ?? false), ct); + var result = await loginProvider.PerformLoginWithSigninTokenAsync(input.SigninToken, !(input.RememberMe ?? false), ct); if (!string.IsNullOrWhiteSpace(result.RefreshToken)) AddCookie(httpContextAccessor.HttpContext!, cookieName, result.RefreshToken, DateTimeOffset.UtcNow.AddMinutes(jWTConfig.RefreshTokenDurationInMinutes)); return new Dto.AccessTokenOutputDto(result.AccessToken, result.Nonce); @@ -92,7 +92,7 @@ public partial class Auth : IEndpointV1 var cookieName = GetCookieName(httpContextAccessor); try { - var result = await loginProvider.PerformLoginWithPassword(input.Password, !(input.RememberMe ?? false), ct); + var result = await loginProvider.PerformLoginWithPasswordAsync(input.Password, !(input.RememberMe ?? false), ct); if (!string.IsNullOrWhiteSpace(result.RefreshToken)) AddCookie(httpContextAccessor.HttpContext!, cookieName, result.RefreshToken, DateTimeOffset.UtcNow.AddMinutes(jWTConfig.RefreshTokenDurationInMinutes)); return new Dto.AccessTokenOutputDto(result.AccessToken, result.Nonce); @@ -191,7 +191,7 @@ public partial class Auth : IEndpointV1 { try { - loginProvider.PerformLogoutWithRefreshToken(refreshTokenString, input?.Nonce, CancellationToken.None); + loginProvider.PerformLogoutWithRefreshTokenAsync(refreshTokenString, input?.Nonce, CancellationToken.None); } catch { diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs index 52340926e..e7168460c 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs @@ -42,7 +42,7 @@ public class BackupGet : IEndpointV1 .RequireAuthorization(); group.MapGet("/backup/{id}/files", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromRoute] string id, [FromQuery] string? filter, [FromQuery] string? time, [FromQuery(Name = "all-versions")] bool? allVersions, [FromQuery(Name = "prefix-only")] bool? prefixOnly, [FromQuery(Name = "folder-contents")] bool? folderContents) - => ExecuteGetFiles(queueRunnerService, GetBackup(connection, id), filter, time, allVersions ?? false, prefixOnly ?? false, folderContents ?? false, new Dictionary())) + => ExecuteGetFilesAsync(queueRunnerService, GetBackup(connection, id), filter, time, allVersions ?? false, prefixOnly ?? false, folderContents ?? false, new Dictionary())) .RequireAuthorization(); group.MapGet("/backup/{id}/log", ([FromServices] Connection connection, [FromRoute] string id, [FromQuery] long? offset, [FromQuery] long? pagesize) @@ -54,7 +54,7 @@ public class BackupGet : IEndpointV1 .RequireAuthorization(); group.MapGet("/backup/{id}/filesets", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromRoute] string id, [FromQuery(Name = "include-metadata")] bool? includeMetadata, [FromQuery(Name = "from-remote-only")] bool? fromRemoteOnly) - => ExecuteGetFilesets(queueRunnerService, GetBackup(connection, id), includeMetadata ?? false, fromRemoteOnly ?? false)) + => ExecuteGetFilesetsAsync(queueRunnerService, GetBackup(connection, id), includeMetadata ?? false, fromRemoteOnly ?? false)) .RequireAuthorization(); group.MapGet("/backup/{id}/export-argsonly", ([FromServices] Connection connection, [FromRoute] string id, [FromQuery(Name = "export-passwords")] bool? exportPasswords, [FromQuery] string? passphrase) @@ -65,7 +65,7 @@ public class BackupGet : IEndpointV1 => ExecuteGetExportCmdline(connection, GetBackup(connection, id), exportPasswords ?? false)) .RequireAuthorization(); - group.MapGet("/backup/{id}/export", ([FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromRoute] string id, [FromQuery(Name = "export-passwords")] bool? exportPasswords, [FromQuery] string? passphrase, [FromQuery] string token, CancellationToken ct) => + group.MapGet("/backup/{id}/export", async ([FromServices] Connection connection, [FromServices] IHttpContextAccessor httpContextAccessor, [FromServices] IJWTTokenProvider jWTTokenProvider, [FromRoute] string id, [FromQuery(Name = "export-passwords")] bool? exportPasswords, [FromQuery] string? passphrase, [FromQuery] string token, CancellationToken ct) => { // Custom authorization check var singleOperationToken = jWTTokenProvider.ReadSingleOperationToken(token); @@ -78,7 +78,7 @@ public class BackupGet : IEndpointV1 resp.ContentLength = data.Length; resp.ContentType = "application/octet-stream"; resp.Headers.Append("Content-Disposition", $"attachment; filename={filename}"); - resp.Body.WriteAsync(data, ct); + await resp.Body.WriteAsync(data, ct).ConfigureAwait(false); }); group.MapGet("/backup/{id}/isdbusedelsewhere", ([FromServices] Connection connection, [FromRoute] string id) @@ -153,7 +153,7 @@ public class BackupGet : IEndpointV1 ); } - private static Dictionary SearchFiles(IQueueRunnerService queueRunnerService, IBackup backup, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues) + private static async Task> SearchFilesAsync(IQueueRunnerService queueRunnerService, IBackup backup, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues) { if (string.IsNullOrWhiteSpace(timestring) && !allVersions) throw new BadRequestException("Invalid or missing time"); @@ -162,7 +162,7 @@ public class BackupGet : IEndpointV1 if (!allVersions) time = Library.Utility.Timeparser.ParseTimeInterval(timestring, DateTime.Now); - var r = queueRunnerService.RunImmediately(Runner.CreateListTask(backup, filter == null ? null : [filter], prefixOnly, allVersions, folderContents, time)) as IListResults; + var r = await queueRunnerService.RunImmediatelyAsync(Runner.CreateListTask(backup, filter == null ? null : [filter], prefixOnly, allVersions, folderContents, time)).ConfigureAwait(false) as IListResults; if (r == null) throw new ServerErrorException("No result from list operation"); @@ -181,8 +181,8 @@ public class BackupGet : IEndpointV1 return result; } - private static Dictionary ExecuteGetFiles(IQueueRunnerService queueRunnerService, IBackup bk, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues) - => SearchFiles(queueRunnerService, bk, filter, timestring, allVersions, prefixOnly, folderContents, extraValues); + private static async Task> ExecuteGetFilesAsync(IQueueRunnerService queueRunnerService, IBackup bk, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues) + => await SearchFilesAsync(queueRunnerService, bk, filter, timestring, allVersions, prefixOnly, folderContents, extraValues).ConfigureAwait(false); private static List> ExecuteGetLog(Connection connection, IBackup bk, long? offset, long pagesize) { @@ -213,7 +213,7 @@ public class BackupGet : IEndpointV1 } } - private static IEnumerable ExecuteGetFilesets(IQueueRunnerService queueRunnerService, IBackup bk, bool includeMetadata, bool fromRemoteOnly) + private static async Task> ExecuteGetFilesetsAsync(IQueueRunnerService queueRunnerService, IBackup bk, bool includeMetadata, bool fromRemoteOnly) { var extra = new Dictionary { @@ -229,7 +229,7 @@ public class BackupGet : IEndpointV1 try { - var r = queueRunnerService.RunImmediately(Runner.CreateTask(DuplicatiOperation.List, bk, extra)) as IListResults; + var r = await queueRunnerService.RunImmediatelyAsync(Runner.CreateTask(DuplicatiOperation.List, bk, extra)).ConfigureAwait(false) as IListResults; if (r == null) throw new ServerErrorException("No result from list operation"); diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs index ff7771db4..1ebcf2a57 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPost.cs @@ -46,7 +46,7 @@ public class BackupPost : IEndpointV1 .RequireAuthorization(); group.MapPost("/backup/{id}/restore", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromServices] IApplicationSettings applicationSettings, [FromRoute] string id, [FromBody] Dto.RestoreInputDto input, CancellationToken cancellationToken) - => ExecuteRestore(connection, applicationSettings, id, queueRunnerService, input, cancellationToken)) + => ExecuteRestoreAsync(connection, applicationSettings, id, queueRunnerService, input, cancellationToken)) .RequireAuthorization(); group.MapPost("/backup/{id}/createreport", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromRoute] string id) @@ -114,13 +114,13 @@ public class BackupPost : IEndpointV1 - private static async Task ExecuteRestore(Connection connection, IApplicationSettings applicationSettings, string id, IQueueRunnerService queueRunnerService, Dto.RestoreInputDto input, CancellationToken cancellationToken) + private static async Task ExecuteRestoreAsync(Connection connection, IApplicationSettings applicationSettings, string id, IQueueRunnerService queueRunnerService, Dto.RestoreInputDto input, CancellationToken cancellationToken) { var backup = GetBackup(connection, id); var restorepath = input.restore_path; if (restorepath != null && restorepath.StartsWith("@")) { - var res = await SharedRemoteOperation.ExpandUrl(connection, applicationSettings, restorepath.Substring(1), id, input.connection_string_id ?? -1, input.source_prefix, cancellationToken); + var res = await SharedRemoteOperation.ExpandUrlAsync(connection, applicationSettings, restorepath.Substring(1), id, input.connection_string_id ?? -1, input.source_prefix, cancellationToken); if (!string.IsNullOrWhiteSpace(res.Url)) restorepath = "@" + res.Url; } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs index 10df91724..46a053a3e 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs @@ -18,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using Duplicati.Library.Utility; using Duplicati.Server; using Duplicati.Server.Database; using Duplicati.Server.Serialization.Interface; @@ -144,7 +145,7 @@ public class BackupPutDelete : IEndpointV1 bool hasPaused = liveControls.State != LiveControls.LiveControlState.Paused; if (hasPaused) liveControls.Pause(true); - nt.Abort(); + nt.AbortAsync().Await(); for (int i = 0; i < 10; i++) { diff --git a/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs b/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs index de11adf49..155eeff80 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/RemoteControl.cs @@ -70,8 +70,8 @@ public class RemoteControl : IEndpointV1 throw new BadRequestException("Existing remote control must be removed before registering"); var task = registration.IsRegistering - ? registration.WaitForRegistration() - : registration.RegisterMachine(registrationUrl ?? throw new BadRequestException("Registration URL must be provided")); + ? registration.WaitForRegistrationAsync() + : registration.RegisterMachineAsync(registrationUrl ?? throw new BadRequestException("Registration URL must be provided")); // Wait for registration, or at most 5 seconds // Client must poll if the registration is not completed by then @@ -92,7 +92,7 @@ public class RemoteControl : IEndpointV1 if (!registration.IsRegistering) throw new BadRequestException("No ongoing registration to wait for"); - var task = registration.WaitForRegistration(); + var task = registration.WaitForRegistrationAsync(); // Wait for registration, or at most 5 seconds await Task.WhenAny(task, Task.Delay(TimeSpan.FromSeconds(4.5), cancellationToken)); @@ -134,8 +134,8 @@ public class RemoteControl : IEndpointV1 IsEnabled: remoteController.IsEnabled, IsConnected: remoteController.Connected, IsRegistering: registration.IsRegistering, - IsRegisteringFaulted: registration.IsRegistering && registration.WaitForRegistration().IsFaulted, - IsRegisteringCompleted: registration.IsRegistering && registration.WaitForRegistration().IsCompleted, + IsRegisteringFaulted: registration.IsRegistering && registration.WaitForRegistrationAsync().IsFaulted, + IsRegisteringCompleted: registration.IsRegistering && registration.WaitForRegistrationAsync().IsCompleted, RegistrationUrl: registration.RegistrationUrl ); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs index 3b46757bb..23b71c7ef 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs @@ -40,11 +40,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1 .RequireAuthorization(); group.MapPost("/remoteoperation/test", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromQuery] bool? autocreate, [FromQuery] Dto.V2.RemoteDestinationType? type, [FromBody] RemoteOperationInput input, CancellationToken cancelToken) - => ExecuteTest(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, input.sourcePrefix, autocreate ?? false, type ?? Dto.V2.RemoteDestinationType.Backend, cancelToken)) + => ExecuteTestAsync(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, input.sourcePrefix, autocreate ?? false, type ?? Dto.V2.RemoteDestinationType.Backend, cancelToken)) .RequireAuthorization(); group.MapPost("/remoteoperation/create", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromBody] RemoteOperationInput input, CancellationToken cancelToken) - => ExecuteCreate(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, cancelToken)) + => ExecuteCreateAsync(connection, applicationSettings, input.path, input.backupId, input.connectionStringId, cancelToken)) .RequireAuthorization(); } @@ -54,25 +54,25 @@ namespace Duplicati.WebserverCore.Endpoints.V1 return new Dto.GetDbPathDto(!string.IsNullOrWhiteSpace(path), path); } - private static async Task ExecuteTest(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, string? sourcePrefix, bool autoCreate, Dto.V2.RemoteDestinationType type, CancellationToken cancelToken) + private static async Task ExecuteTestAsync(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, string? sourcePrefix, bool autoCreate, Dto.V2.RemoteDestinationType type, CancellationToken cancelToken) { try { if (type == Dto.V2.RemoteDestinationType.SourceProvider) { - using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, sourcePrefix, cancelToken); + using var wrapper = await SharedRemoteOperation.GetSourceProviderForTestingAsync(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, sourcePrefix, cancelToken); using (var s = wrapper.SourceProvider) await s.Test(cancelToken).ConfigureAwait(false); } else if (type == Dto.V2.RemoteDestinationType.RestoreDestinationProvider) { - using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, sourcePrefix, cancelToken); + using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTestingAsync(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, sourcePrefix, cancelToken); using (var r = wrapper.RestoreDestinationProvider) await r.Test(cancelToken).ConfigureAwait(false); } else { - using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken); + using var wrapper = await SharedRemoteOperation.GetBackendAsync(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken); using (var b = wrapper.Backend) { try { await b.TestAsync(cancelToken).ConfigureAwait(false); } @@ -120,11 +120,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1 } - private static async Task ExecuteCreate(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, CancellationToken cancelToken) + private static async Task ExecuteCreateAsync(Connection connection, IApplicationSettings applicationSettings, string maskedurl, string? backupId, long? connectionStringId, CancellationToken cancelToken) { try { - using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken); + using var wrapper = await SharedRemoteOperation.GetBackendAsync(connection, applicationSettings, maskedurl, backupId, connectionStringId ?? -1, cancelToken); using (var b = wrapper.Backend) await b.CreateFolderAsync(cancelToken).ConfigureAwait(false); } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs b/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs index 16657fa67..d0f03e714 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Tasks.cs @@ -34,7 +34,7 @@ public class Tasks : IEndpointV1 { group.MapGet("/tasks", ([FromServices] ITaskQueueService taskQueueService) => taskQueueService.GetTaskQueue()).RequireAuthorization(); group.MapGet("/task/{taskid}", ([FromRoute] long taskId, [FromServices] ITaskQueueService taskQueueService) => taskQueueService.GetTaskInfo(taskId)).RequireAuthorization(); - group.MapPost("/task/{taskid}/stop", ([FromRoute] long taskId, [FromServices] ITaskQueueService taskQueueService) => taskQueueService.StopTask(taskId)).RequireAuthorization(); - group.MapPost("/task/{taskid}/abort", ([FromRoute] long taskId, [FromServices] ITaskQueueService taskQueueService) => taskQueueService.AbortTask(taskId)).RequireAuthorization(); + group.MapPost("/task/{taskid}/stop", ([FromRoute] long taskId, [FromServices] ITaskQueueService taskQueueService) => taskQueueService.StopTaskAsync(taskId)).RequireAuthorization(); + group.MapPost("/task/{taskid}/abort", ([FromRoute] long taskId, [FromServices] ITaskQueueService taskQueueService) => taskQueueService.AbortTaskAsync(taskId)).RequireAuthorization(); } } diff --git a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs index 839a2071f..763949b8d 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs @@ -35,7 +35,7 @@ public record WebModules : IEndpointV1 { group.MapGet("/webmodules", ExecuteGet).RequireAuthorization(); group.MapPost("/webmodule/{modulekey}", ([FromServices] Connection connection, [FromServices] IApplicationSettings applicationSettings, [FromRoute] string modulekey, [FromBody] Dictionary options, CancellationToken cancellationToken) - => ExecutePost(connection, applicationSettings, modulekey, options, cancellationToken)) + => ExecutePostAsync(connection, applicationSettings, modulekey, options, cancellationToken)) .RequireAuthorization(); } @@ -43,7 +43,7 @@ public record WebModules : IEndpointV1 => Library.DynamicLoader.WebLoader.Modules; - private static async Task ExecutePost(Connection connection, IApplicationSettings applicationSettings, string modulekey, Dictionary inputOptions, CancellationToken cancellationToken) + private static async Task ExecutePostAsync(Connection connection, IApplicationSettings applicationSettings, string modulekey, Dictionary inputOptions, CancellationToken cancellationToken) { var m = Library.DynamicLoader.WebLoader.Modules.FirstOrDefault(x => x.Key.Equals(modulekey, StringComparison.OrdinalIgnoreCase)) ?? throw new NotFoundException("No such module found"); @@ -55,7 +55,7 @@ public record WebModules : IEndpointV1 var url = options.GetValueOrDefault("url"); if (!string.IsNullOrWhiteSpace(url)) { - var (newUrl, opts) = await SharedRemoteOperation.ExpandUrl(connection, applicationSettings, url, options.GetValueOrDefault("backup-id"), Library.Utility.Utility.ParseLongOption(options, "connection-string-id", -1), options.GetValueOrDefault("source-prefix"), cancellationToken); + var (newUrl, opts) = await SharedRemoteOperation.ExpandUrlAsync(connection, applicationSettings, url, options.GetValueOrDefault("backup-id"), Library.Utility.Utility.ParseLongOption(options, "connection-string-id", -1), options.GetValueOrDefault("source-prefix"), cancellationToken); options["url"] = newUrl; foreach (var k in opts.Keys) options[k] = opts[k]; diff --git a/Duplicati/WebserverCore/Endpoints/V2/BackupListing.cs b/Duplicati/WebserverCore/Endpoints/V2/BackupListing.cs index c75732b58..ea2633d03 100644 --- a/Duplicati/WebserverCore/Endpoints/V2/BackupListing.cs +++ b/Duplicati/WebserverCore/Endpoints/V2/BackupListing.cs @@ -33,33 +33,33 @@ public class BackupListing : IEndpointV2 { public static void Map(RouteGroupBuilder group) { - group.MapPost("/backup/list-filesets", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.ListFilesetsRequestDto input) - => ExecuteGetFilesets(queueRunnerService, GetBackup(connection, input.BackupId), input)) + group.MapPost("/backup/list-filesets", async ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.ListFilesetsRequestDto input) + => await ExecuteGetFilesetsAsync(queueRunnerService, GetBackup(connection, input.BackupId), input).ConfigureAwait(false)) .RequireAuthorization(); - group.MapPost("/backup/list-folder", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.ListFolderContentRequestDto input) - => ExecuteListFolder(queueRunnerService, GetBackup(connection, input.BackupId), input)) + group.MapPost("/backup/list-folder", async ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.ListFolderContentRequestDto input) + => await ExecuteListFolderAsync(queueRunnerService, GetBackup(connection, input.BackupId), input).ConfigureAwait(false)) .RequireAuthorization(); - group.MapPost("/backup/list-versions", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.ListFileVersionsRequestDto input) - => ExecuteListVersions(queueRunnerService, GetBackup(connection, input.BackupId), input)) + group.MapPost("/backup/list-versions", async ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.ListFileVersionsRequestDto input) + => await ExecuteListVersionsAsync(queueRunnerService, GetBackup(connection, input.BackupId), input).ConfigureAwait(false)) .RequireAuthorization(); - group.MapPost("/backup/search", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.SearchEntriesRequestDto input) - => ExecuteSearch(queueRunnerService, GetBackup(connection, input.BackupId), input)) + group.MapPost("/backup/search", async ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromBody] Dto.V2.SearchEntriesRequestDto input) + => await ExecuteSearchAsync(queueRunnerService, GetBackup(connection, input.BackupId), input).ConfigureAwait(false)) .RequireAuthorization(); } private static IBackup GetBackup(Connection connection, string id) => connection.GetBackup(id) ?? throw new NotFoundException("Backup not found"); - private static Dto.V2.ListFolderContentResponseDto ExecuteListFolder(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.ListFolderContentRequestDto input) + private static async Task ExecuteListFolderAsync(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.ListFolderContentRequestDto input) { var time = string.IsNullOrWhiteSpace(input.Time) ? new DateTime(0) : Library.Utility.Timeparser.ParseTimeInterval(input.Time, DateTime.Now); - var r = queueRunnerService.RunImmediately(Runner.CreateListFolderContents(bk, input.Paths, time, input.PageSize ?? 1000, input.Page ?? 0, input.ReturnExtended ?? false)) as IListFolderResults; + var r = await queueRunnerService.RunImmediatelyAsync(Runner.CreateListFolderContents(bk, input.Paths, time, input.PageSize ?? 1000, input.Page ?? 0, input.ReturnExtended ?? false)).ConfigureAwait(false) as IListFolderResults; if (r == null) throw new ServerErrorException("No result from list operation"); @@ -79,7 +79,7 @@ public class BackupListing : IEndpointV2 r.Entries.TotalCount); } - private static Dto.V2.ListFilesetsResponseDto ExecuteGetFilesets(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.ListFilesetsRequestDto input) + private static async Task ExecuteGetFilesetsAsync(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.ListFilesetsRequestDto input) { var extra = new Dictionary(); @@ -91,7 +91,7 @@ public class BackupListing : IEndpointV2 try { - var r = queueRunnerService.RunImmediately(Runner.CreateListFilesetsTask(bk, extra)) as IListFilesetResults; + var r = await queueRunnerService.RunImmediatelyAsync(Runner.CreateListFilesetsTask(bk, extra)).ConfigureAwait(false) as IListFilesetResults; if (r == null) throw new ServerErrorException("No result from list operation"); @@ -116,9 +116,9 @@ public class BackupListing : IEndpointV2 } } - private static Dto.V2.ListFileVersionsOutputDto ExecuteListVersions(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.ListFileVersionsRequestDto input) + private static async Task ExecuteListVersionsAsync(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.ListFileVersionsRequestDto input) { - var r = queueRunnerService.RunImmediately(Runner.ListFileVersionsTask(bk, input.Paths, input.PageSize ?? 1000, input.Page ?? 0)) as IListFileVersionsResults; + var r = await queueRunnerService.RunImmediatelyAsync(Runner.ListFileVersionsTask(bk, input.Paths, input.PageSize ?? 1000, input.Page ?? 0)).ConfigureAwait(false) as IListFileVersionsResults; if (r == null) throw new ServerErrorException("No result from list operation"); @@ -139,13 +139,13 @@ public class BackupListing : IEndpointV2 r.FileVersions.TotalCount); } - private static Dto.V2.SearchEntriesResponseDto ExecuteSearch(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.SearchEntriesRequestDto input) + private static async Task ExecuteSearchAsync(IQueueRunnerService queueRunnerService, IBackup bk, Dto.V2.SearchEntriesRequestDto input) { var time = string.IsNullOrWhiteSpace(input.Time) ? new DateTime(0) : Library.Utility.Timeparser.ParseTimeInterval(input.Time, DateTime.Now); - var r = queueRunnerService.RunImmediately(Runner.CreateSearchEntriesTask(bk, input.Filters, input.Paths, time, input.Version, input.PageSize ?? 1000, input.Page ?? 0, input.ReturnExtended ?? false)) as ISearchFilesResults; + var r = await queueRunnerService.RunImmediatelyAsync(Runner.CreateSearchEntriesTask(bk, input.Filters, input.Paths, time, input.Version, input.PageSize ?? 1000, input.Page ?? 0, input.ReturnExtended ?? false)).ConfigureAwait(false) as ISearchFilesResults; if (r == null) throw new ServerErrorException("No result from list operation"); diff --git a/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs b/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs index 4acf3eba8..f621bfc1b 100644 --- a/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs +++ b/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs @@ -47,7 +47,7 @@ public class DestinationVerify : IEndpointV2 { if (destinationType == RemoteDestinationType.SourceProvider) { - using var wrapper = await SharedRemoteOperation.GetSourceProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, input.SourcePrefix, cancelToken); + using var wrapper = await SharedRemoteOperation.GetSourceProviderForTestingAsync(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, input.SourcePrefix, cancelToken); // We do not call TestAsync here, because we may have read-only access to the source, and test will verify write access // Instead we just check if we can enumerate the files, which is the main thing we need to verify for a source provider @@ -63,7 +63,7 @@ public class DestinationVerify : IEndpointV2 } else if (destinationType == RemoteDestinationType.RestoreDestinationProvider) { - using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTesting(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, input.SourcePrefix, cancelToken); + using var wrapper = await SharedRemoteOperation.GetRestoreDestinationProviderForTestingAsync(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, input.SourcePrefix, cancelToken); // Here we do call TestAsync, because we need to verify write access await wrapper.RestoreDestinationProvider.Test(cancelToken); @@ -76,7 +76,7 @@ public class DestinationVerify : IEndpointV2 } else { - using var wrapper = await SharedRemoteOperation.GetBackend(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken); + using var wrapper = await SharedRemoteOperation.GetBackendAsync(connection, applicationSettings, input.DestinationUrl, input.BackupId, input.ConnectionStringId ?? -1, cancelToken); using (var b = wrapper.Backend) { diff --git a/Duplicati/WebserverCore/Middlewares/JWTProvider.cs b/Duplicati/WebserverCore/Middlewares/JWTProvider.cs index 081054c26..2d49f58af 100644 --- a/Duplicati/WebserverCore/Middlewares/JWTProvider.cs +++ b/Duplicati/WebserverCore/Middlewares/JWTProvider.cs @@ -197,7 +197,7 @@ public class JWTTokenProvider(JWTConfig jWTConfig) : IJWTTokenProvider }; } - public static async Task ValidateAccessToken(TokenValidatedContext context, ITokenFamilyStore store) + public static async Task ValidateAccessTokenAsync(TokenValidatedContext context, ITokenFamilyStore store) { var tokenHandler = new JwtSecurityTokenHandler(); var jwtToken = context.SecurityToken as JsonWebToken ?? throw new Exception("Invalid token"); @@ -223,7 +223,7 @@ public class JWTTokenProvider(JWTConfig jWTConfig) : IJWTTokenProvider if (tokenFamilyClaim.Value != TemporaryFamilyId) { - var tokenFamily = await store.GetTokenFamily(userIdClaim.Value, tokenFamilyClaim.Value, context.HttpContext.RequestAborted); + var tokenFamily = await store.GetTokenFamilyAsync(userIdClaim.Value, tokenFamilyClaim.Value, context.HttpContext.RequestAborted); if (tokenFamily == null) { context.Fail("Invalid token."); diff --git a/Duplicati/WebserverCore/Middlewares/SynologyAuthMiddleware.cs b/Duplicati/WebserverCore/Middlewares/SynologyAuthMiddleware.cs index 40c5eaeb9..cdfe723af 100644 --- a/Duplicati/WebserverCore/Middlewares/SynologyAuthMiddleware.cs +++ b/Duplicati/WebserverCore/Middlewares/SynologyAuthMiddleware.cs @@ -156,7 +156,7 @@ public sealed class SynologyDsmAuthMiddleware _fullyDisabled = true; } - public async Task Invoke(HttpContext context) + public async Task InvokeAsync(HttpContext context) { // If scripts are missing, return 503, the system is not looking as expected if (_fullyDisabled) @@ -217,7 +217,7 @@ public sealed class SynologyDsmAuthMiddleware try { // Call login.cgi to get XSRF token - var resp = ShellExec(_opt.LoginCgi, env: env).Result; + var resp = await ShellExecAsync(_opt.LoginCgi, env: env).ConfigureAwait(false); var m = _synoTokenRegex.Match(resp); if (m.Success) @@ -244,7 +244,7 @@ public sealed class SynologyDsmAuthMiddleware try { // Call authenticate.cgi to get username - username = await ShellExec(_opt.AuthenticateCgi, shell: false, exitcode: 0, env: env, ct: context.RequestAborted); + username = await ShellExecAsync(_opt.AuthenticateCgi, shell: false, exitcode: 0, env: env, ct: context.RequestAborted); } catch { @@ -266,7 +266,7 @@ public sealed class SynologyDsmAuthMiddleware // Admin-only check, look up group membership if (_opt.AdminOnly) { - var isAdmin = await IsAdminUser(username, context.RequestAborted); + var isAdmin = await IsAdminUserAsync(username, context.RequestAborted); if (!isAdmin) { context.Response.StatusCode = StatusCodes.Status403Forbidden; @@ -345,7 +345,7 @@ public sealed class SynologyDsmAuthMiddleware private static string BuildAuthCacheKey(string loginId) => $"{nameof(SynologyDsmAuthMiddleware)}:auth:{loginId}"; - private async Task IsAdminUser(string username, CancellationToken ct) + private async Task IsAdminUserAsync(string username, CancellationToken ct) { // 1) Prefer group-name membership (more robust). if (!string.IsNullOrWhiteSpace(_opt.AdminGroupName)) @@ -353,7 +353,7 @@ public sealed class SynologyDsmAuthMiddleware try { // id -Gn prints group names - var groupsByName = await ShellExec("id", $"-Gn {EscapeArg(username)}", shell: false, exitcode: 0, env: null, ct: ct); + var groupsByName = await ShellExecAsync("id", $"-Gn {EscapeArg(username)}", shell: false, exitcode: 0, env: null, ct: ct); var names = (groupsByName ?? string.Empty) .Split(new[] { ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); @@ -373,7 +373,7 @@ public sealed class SynologyDsmAuthMiddleware try { // id -G prints numeric group IDs - gids = await ShellExec("id", $"-G {EscapeArg(username)}", shell: false, exitcode: 0, env: null, ct: ct) ?? string.Empty; + gids = await ShellExecAsync("id", $"-G {EscapeArg(username)}", shell: false, exitcode: 0, env: null, ct: ct) ?? string.Empty; gids = gids.Replace(Environment.NewLine, string.Empty); } catch @@ -396,7 +396,7 @@ public sealed class SynologyDsmAuthMiddleware return value; } - private static async Task ShellExec( + private static async Task ShellExecAsync( string command, string? args = null, bool shell = false, diff --git a/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs b/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs index 56efd61c8..dd2873ef1 100644 --- a/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs +++ b/Duplicati/WebserverCore/Middlewares/WebsocketExtensions.cs @@ -46,7 +46,7 @@ public static class WebsocketExtensions if (context.WebSockets.IsWebSocketRequest) { using var webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await context.RequestServices.GetRequiredService().AddConnection(webSocket); + await context.RequestServices.GetRequiredService().AddConnectionAsync(webSocket); } else { @@ -58,7 +58,7 @@ public static class WebsocketExtensions if (context.WebSockets.IsWebSocketRequest) { using var webSocket = await context.WebSockets.AcceptWebSocketAsync(); - await context.RequestServices.GetRequiredService().AddConnection(webSocket, true); + await context.RequestServices.GetRequiredService().AddConnectionAsync(webSocket, true); } else { diff --git a/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs b/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs index c383ebb06..64c1b6f51 100644 --- a/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs +++ b/Duplicati/WebserverCore/Notifications/WebsocketAccessor.cs @@ -75,13 +75,13 @@ public class WebsocketAccessor : IWebsocketAccessor _remoteController = remoteController; _remoteControllerRegistration = remoteControllerRegistration; - eventPollNotify.NewEvent += async (_, _) => { await Send(SubscriptionService.LegacyStatus); }; - eventPollNotify.ServerSettingsUpdate += async (_, _) => { await Send(SubscriptionService.ServerSettings); }; - eventPollNotify.BackupListUpdate += async (_, _) => { await Send(SubscriptionService.BackupList); }; - eventPollNotify.NotificationsUpdated += async (_, _) => { await Send(SubscriptionService.Notifications); }; - eventPollNotify.TaskQueueUpdate += async (_, _) => { await Send(SubscriptionService.TaskQueue); }; - eventPollNotify.TaskCompleted += async (_, taskId) => { await SendTaskCompleted(taskId, GetConnections()); }; - eventPollNotify.RemoteControlUpdate += async (_, _) => { await Send(SubscriptionService.RemoteControl); }; + eventPollNotify.NewEvent += async (_, _) => { await SendAsync(SubscriptionService.LegacyStatus); }; + eventPollNotify.ServerSettingsUpdate += async (_, _) => { await SendAsync(SubscriptionService.ServerSettings); }; + eventPollNotify.BackupListUpdate += async (_, _) => { await SendAsync(SubscriptionService.BackupList); }; + eventPollNotify.NotificationsUpdated += async (_, _) => { await SendAsync(SubscriptionService.Notifications); }; + eventPollNotify.TaskQueueUpdate += async (_, _) => { await SendAsync(SubscriptionService.TaskQueue); }; + eventPollNotify.TaskCompleted += async (_, taskId) => { await SendTaskCompletedAsync(taskId, GetConnections()); }; + eventPollNotify.RemoteControlUpdate += async (_, _) => { await SendAsync(SubscriptionService.RemoteControl); }; eventPollNotify.ProgressUpdate += async (_, progress) => { if (progress == null) @@ -91,11 +91,11 @@ public class WebsocketAccessor : IWebsocketAccessor if (!_subscribers.Any(c => c.Value.ContainsKey(SubscriptionService.Progress))) return; - await SendData(SubscriptionService.Progress, progress(), GetConnections()); + await SendDataAsync(SubscriptionService.Progress, progress(), GetConnections()); }; } - public async Task AddConnection(WebSocket newConnection, bool subscribeToLegacyStatus) + public async Task AddConnectionAsync(WebSocket newConnection, bool subscribeToLegacyStatus) { var subscribed = new ConcurrentDictionary(); if (subscribeToLegacyStatus) @@ -104,12 +104,12 @@ public class WebsocketAccessor : IWebsocketAccessor _connections.Add(newConnection); if (subscribeToLegacyStatus) - await Send(SubscriptionService.LegacyStatus, [newConnection]); + await SendAsync(SubscriptionService.LegacyStatus, [newConnection]); ClearClosed(); - await HandleClientData(newConnection); + await HandleClientDataAsync(newConnection); } - private async Task HandleClientData(WebSocket webSocket, CancellationToken cancellationToken = default) + private async Task HandleClientDataAsync(WebSocket webSocket, CancellationToken cancellationToken = default) { var buffer = new byte[1024 * 4]; var result = await ReceiveAsync(); @@ -138,7 +138,7 @@ public class WebsocketAccessor : IWebsocketAccessor if (receiveResult is not null && receiveResult.CloseStatus is null) { var message = Encoding.UTF8.GetString(buffer[..receiveResult.Count]); - await HandleClientMessage(webSocket, message); + await HandleClientMessageAsync(webSocket, message); } return receiveResult; @@ -173,7 +173,7 @@ public class WebsocketAccessor : IWebsocketAccessor private Dto.RemoteControlStatusOutput GetRemoteControlStatus() { var registrationTask = _remoteControllerRegistration.IsRegistering - ? _remoteControllerRegistration.WaitForRegistration() + ? _remoteControllerRegistration.WaitForRegistrationAsync() : null; return new Dto.RemoteControlStatusOutput( @@ -194,22 +194,22 @@ public class WebsocketAccessor : IWebsocketAccessor return new ArraySegment(bytes); } - private Task SendRequestReply(WebSocket socket, string id, string? service, string message, bool success, T? data = default) + private Task SendRequestReplyAsync(WebSocket socket, string id, string? service, string message, bool success, T? data = default) => socket.SendAsync(GetBytes(new WebSocketReply(APIVersion, id, service, message, success, data)), WebSocketMessageType.Text, true, CancellationToken.None); - private Task SendRequestSuccessReply(WebSocket socket, WebSocketRequest req, string message = "OK") - => SendRequestSuccessReply(socket, req, message, null); + private Task SendRequestSuccessReplyAsync(WebSocket socket, WebSocketRequest req, string message = "OK") + => SendRequestSuccessReplyAsync(socket, req, message, null); - private Task SendRequestSuccessReply(WebSocket socket, WebSocketRequest req, string message = "OK", T? data = default) + private Task SendRequestSuccessReplyAsync(WebSocket socket, WebSocketRequest req, string message = "OK", T? data = default) => socket.SendAsync(GetBytes(new WebSocketReply(APIVersion, req.Id, req.Service, message, true, data)), WebSocketMessageType.Text, true, CancellationToken.None); - private Task SendRequestFailureReply(WebSocket socket, WebSocketRequest req, string message) - => SendRequestFailureReply(socket, req, message, null); + private Task SendRequestFailureReplyAsync(WebSocket socket, WebSocketRequest req, string message) + => SendRequestFailureReplyAsync(socket, req, message, null); - private Task SendRequestFailureReply(WebSocket socket, WebSocketRequest req, string message, T? data = default) + private Task SendRequestFailureReplyAsync(WebSocket socket, WebSocketRequest req, string message, T? data = default) => socket.SendAsync(GetBytes(new WebSocketReply(APIVersion, req.Id, req.Service, message, false, data)), WebSocketMessageType.Text, true, CancellationToken.None); - private async Task SendTaskCompleted(long taskId, IEnumerable connections) + private async Task SendTaskCompletedAsync(long taskId, IEnumerable connections) { var task = _taskQueueService.GetTaskInfo(taskId); if (task == null) @@ -217,11 +217,11 @@ public class WebsocketAccessor : IWebsocketAccessor Log.WriteWarningMessage(LOGTAG, "WebsocketTaskNotFound", null, $"Task with ID {taskId} not found for completion notification."); return; } - await SendData(SubscriptionService.TaskCompleted, task, connections); + await SendDataAsync(SubscriptionService.TaskCompleted, task, connections); } - private async Task Send(SubscriptionService key, IEnumerable connections) + private async Task SendAsync(SubscriptionService key, IEnumerable connections) { // Avoid generating data for subscriptions that are not active if (!_subscribers.Any(c => c.Value.ContainsKey(key))) @@ -230,10 +230,10 @@ public class WebsocketAccessor : IWebsocketAccessor switch (key) { case SubscriptionService.LegacyStatus: - await SendData(SubscriptionService.LegacyStatus, _statusService.GetStatus(), connections); + await SendDataAsync(SubscriptionService.LegacyStatus, _statusService.GetStatus(), connections); break; case SubscriptionService.ServerSettings: - await SendData(SubscriptionService.ServerSettings, _settingsService.GetSettingsMasked(), connections); + await SendDataAsync(SubscriptionService.ServerSettings, _settingsService.GetSettingsMasked(), connections); break; case SubscriptionService.BackupList: var targets = connections.ToHashSet(); @@ -247,14 +247,14 @@ public class WebsocketAccessor : IWebsocketAccessor }) .Where(c => c.found) .GroupBy(c => c.order) - .Select(c => SendData(SubscriptionService.BackupList, _backupListService.List(c.Key), c.Select(x => x.Key))) + .Select(c => SendDataAsync(SubscriptionService.BackupList, _backupListService.List(c.Key), c.Select(x => x.Key))) ); break; case SubscriptionService.Notifications: - await SendData(SubscriptionService.Notifications, _notificationService.GetNotifications(), connections); + await SendDataAsync(SubscriptionService.Notifications, _notificationService.GetNotifications(), connections); break; case SubscriptionService.TaskQueue: - await SendData(SubscriptionService.TaskQueue, _taskQueueService.GetTaskQueue(), connections); + await SendDataAsync(SubscriptionService.TaskQueue, _taskQueueService.GetTaskQueue(), connections); break; case SubscriptionService.TaskCompleted: // This event is sent when a task completes, so we do not send initial data @@ -263,7 +263,7 @@ public class WebsocketAccessor : IWebsocketAccessor // Progress updates are sent via the event system, so we cannot send information in advance break; case SubscriptionService.RemoteControl: - await SendData(SubscriptionService.RemoteControl, GetRemoteControlStatus(), connections); + await SendDataAsync(SubscriptionService.RemoteControl, GetRemoteControlStatus(), connections); break; default: Log.WriteWarningMessage(LOGTAG, "WebsocketUnknownSubscription", null, $"Unknown subscription service: {key}"); @@ -271,7 +271,7 @@ public class WebsocketAccessor : IWebsocketAccessor } } - private async Task SendData(SubscriptionService key, T? data, IEnumerable connections) + private async Task SendDataAsync(SubscriptionService key, T? data, IEnumerable connections) { try { @@ -293,9 +293,9 @@ public class WebsocketAccessor : IWebsocketAccessor } } - public Task Send(SubscriptionService key) => Send(key, GetConnections()); + public Task SendAsync(SubscriptionService key) => SendAsync(key, GetConnections()); - public async Task HandleClientMessage(WebSocket socket, string messagestr) + public async Task HandleClientMessageAsync(WebSocket socket, string messagestr) { WebSocketRequest? message; try @@ -305,7 +305,7 @@ public class WebsocketAccessor : IWebsocketAccessor catch (Exception ex) { Log.WriteErrorMessage(LOGTAG, "WebsocketDeserializationError", ex, $"Failed to deserialize websocket message"); - await SendRequestReply(socket, "", null, "Invalid message format", false); + await SendRequestReplyAsync(socket, "", null, "Invalid message format", false); return; } @@ -314,27 +314,27 @@ public class WebsocketAccessor : IWebsocketAccessor if (message.Version != APIVersion) { - await SendRequestFailureReply(socket, message, "Unsupported API version"); + await SendRequestFailureReplyAsync(socket, message, "Unsupported API version"); return; } switch (message.Action) { case "status": - await SendRequestReply(socket, message.Id, message.Service, "Status request received", true, _statusService.GetStatus()); + await SendRequestReplyAsync(socket, message.Id, message.Service, "Status request received", true, _statusService.GetStatus()); return; case "ping": - await SendRequestSuccessReply(socket, message, "pong"); + await SendRequestSuccessReplyAsync(socket, message, "pong"); return; case "auth": - await SendRequestSuccessReply(socket, message, "Already authenticated"); + await SendRequestSuccessReplyAsync(socket, message, "Already authenticated"); return; case "sub": case "unsub": { if (!Enum.TryParse(message.Service, true, out var serviceEnum)) { - await SendRequestFailureReply(socket, message, "Unknown subscription service"); + await SendRequestFailureReplyAsync(socket, message, "Unknown subscription service"); return; } @@ -354,13 +354,13 @@ public class WebsocketAccessor : IWebsocketAccessor } subscribed.AddOrUpdate(serviceEnum, config, (key, oldValue) => config); - await SendRequestSuccessReply(socket, message, "Subscribed successfully"); - await Send(serviceEnum, [socket]); + await SendRequestSuccessReplyAsync(socket, message, "Subscribed successfully"); + await SendAsync(serviceEnum, [socket]); } else if (message.Action == "unsub") { subscribed.TryRemove(serviceEnum, out _); - await SendRequestSuccessReply(socket, message, "Unsubscribed successfully"); + await SendRequestSuccessReplyAsync(socket, message, "Unsubscribed successfully"); } } @@ -370,7 +370,7 @@ public class WebsocketAccessor : IWebsocketAccessor default: { Log.WriteWarningMessage(LOGTAG, "WebsocketUnknownAction", null, $"Unknown websocket action: {message.Action}"); - await SendRequestFailureReply(socket, message, "Unknown action"); + await SendRequestFailureReplyAsync(socket, message, "Unknown action"); return; } } diff --git a/Duplicati/WebserverCore/Notifications/WebsocketAuthenticator.cs b/Duplicati/WebserverCore/Notifications/WebsocketAuthenticator.cs index e3c67040e..4a6e9cb1f 100644 --- a/Duplicati/WebserverCore/Notifications/WebsocketAuthenticator.cs +++ b/Duplicati/WebserverCore/Notifications/WebsocketAuthenticator.cs @@ -49,31 +49,31 @@ public class WebsocketAuthenticator( public string Type => "auth"; } - public async Task AddConnection(WebSocket newConnection) + public async Task AddConnectionAsync(WebSocket newConnection) { _connections.TryAdd(newConnection, DateTime.UtcNow); // Set up a task to clear closed connections after the maximum authentication time plus a small buffer var _ = Task.Run(async () => { await Task.Delay(MaxAuthTime.Add(TimeSpan.FromSeconds(5))); - await ClearClosed(); + await ClearClosedAsync(); }); - await HandleClientData(newConnection); + await HandleClientDataAsync(newConnection); } - private async Task HandleClientData(WebSocket webSocket, CancellationToken cancellationToken = default) + private async Task HandleClientDataAsync(WebSocket webSocket, CancellationToken cancellationToken = default) { var buffer = new byte[1024 * 4]; var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), cancellationToken); if (result is not null && result.CloseStatus is null) { var message = Encoding.UTF8.GetString(buffer[..result.Count]); - await HandleClientMessage(webSocket, message); + await HandleClientMessageAsync(webSocket, message); } } - private async Task ClearClosed() + private async Task ClearClosedAsync() { // Snapshot the connections to avoid modifying the collection while iterating var connections = _connections.ToList(); @@ -102,7 +102,7 @@ public class WebsocketAuthenticator( var bytes = Encoding.UTF8.GetBytes(json); return new ArraySegment(bytes); } - private async Task SendRequestReply(WebSocket socket, string message, bool success) + private async Task SendRequestReplyAsync(WebSocket socket, string message, bool success) { var reply = new WebSocketAuthReply(APIVersion, message, success); var bytes = GetBytes(reply); @@ -114,7 +114,7 @@ public class WebsocketAuthenticator( _connections.TryRemove(socket, out _); } - public async Task HandleClientMessage(WebSocket socket, string messagestr) + public async Task HandleClientMessageAsync(WebSocket socket, string messagestr) { WebSocketAuthRequest? message = null; try @@ -128,13 +128,13 @@ public class WebsocketAuthenticator( if (message == null) { - await SendRequestReply(socket, "Invalid message format", false); + await SendRequestReplyAsync(socket, "Invalid message format", false); return; } if (message.Version != APIVersion) { - await SendRequestReply(socket, "Unsupported API version", false); + await SendRequestReplyAsync(socket, "Unsupported API version", false); return; } @@ -161,13 +161,13 @@ public class WebsocketAuthenticator( if (!isValid) { Log.WriteVerboseMessage(LOGTAG, "WebsocketInvalidToken", exception, $"WebSocket connection with invalid token"); - await SendRequestReply(socket, "Invalid token", false); + await SendRequestReplyAsync(socket, "Invalid token", false); return; } Log.WriteVerboseMessage(LOGTAG, "WebsocketAuthenticated", $"WebSocket connection authenticated with token"); - await SendRequestReply(socket, "Authenticated successfully", true); - await websocketAccessor.AddConnection(socket, false); + await SendRequestReplyAsync(socket, "Authenticated successfully", true); + await websocketAccessor.AddConnectionAsync(socket, false); } } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Services/CommandlineRunService.cs b/Duplicati/WebserverCore/Services/CommandlineRunService.cs index 96c322508..aed8a2535 100644 --- a/Duplicati/WebserverCore/Services/CommandlineRunService.cs +++ b/Duplicati/WebserverCore/Services/CommandlineRunService.cs @@ -106,7 +106,7 @@ public class CommandlineRunService(IQueueRunnerService queueRunnerService, ILogW { var tt = this.Task; if (tt != null) - tt.Abort(); + tt.AbortAsync().Await(); } } @@ -165,7 +165,7 @@ public class CommandlineRunService(IQueueRunnerService queueRunnerService, ILogW var code = CommandLine.Program.RunCommandLine(k.Writer, k.Writer, c => { k.Task!.SetController(c); - c.AppendSink(sink); + c.AppendSinkAsync(sink).Await(); }, args); k.Writer.WriteLine("Return code: {0}", code); } diff --git a/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs b/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs index ccb1d479a..f18a130de 100644 --- a/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs +++ b/Duplicati/WebserverCore/Services/IRemoteControllerHandler.cs @@ -33,26 +33,26 @@ public interface IRemoteControllerHandler /// /// The initial metadata /// The metadata to use - Task> OnConnect(Dictionary metadata); + Task> OnConnectAsync(Dictionary metadata); /// /// Re-keys the remote control connection. /// /// The data to re-key with /// An awaitable task - Task ReKey(ClaimedClientData data); + Task ReKeyAsync(ClaimedClientData data); /// /// Handles a control message. /// /// The control message to handle /// An awaitable task - Task OnControl(KeepRemoteConnection.ControlMessage message); + Task OnControlAsync(KeepRemoteConnection.ControlMessage message); /// /// Handles a command message. /// /// The command message to handle /// An awaitable task - Task OnMessage(KeepRemoteConnection.CommandMessage commandMessage); + Task OnMessageAsync(KeepRemoteConnection.CommandMessage commandMessage); } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Services/LoginProvider.cs b/Duplicati/WebserverCore/Services/LoginProvider.cs index 2ebbb0d1e..93c1c501c 100644 --- a/Duplicati/WebserverCore/Services/LoginProvider.cs +++ b/Duplicati/WebserverCore/Services/LoginProvider.cs @@ -30,12 +30,12 @@ public class LoginProvider(ITokenFamilyStore repo, IJWTTokenProvider tokenProvid { private static readonly string LOGTAG = Log.LogTagFromType(); - public async Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithSigninToken(string signinTokenString, bool shortLived, CancellationToken ct) + public async Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithSigninTokenAsync(string signinTokenString, bool shortLived, CancellationToken ct) { var signinToken = tokenProvider.ReadSigninToken(signinTokenString); var userId = signinToken.UserId; - var tokenFamily = await repo.CreateTokenFamily(userId, ct); + var tokenFamily = await repo.CreateTokenFamilyAsync(userId, ct); var (refreshToken, nonce) = tokenProvider.CreateRefreshToken(userId, tokenFamily.Id, tokenFamily.Counter, shortLived); return ( @@ -45,10 +45,10 @@ public class LoginProvider(ITokenFamilyStore repo, IJWTTokenProvider tokenProvid ); } - public async Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithRefreshToken(string refreshTokenString, string? nonce, CancellationToken ct) + public async Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithRefreshTokenAsync(string refreshTokenString, string? nonce, CancellationToken ct) { var refreshToken = tokenProvider.ReadRefreshToken(refreshTokenString, nonce); - var tokenFamily = await repo.GetTokenFamily(refreshToken.UserId, refreshToken.TokenFamilyId, ct) + var tokenFamily = await repo.GetTokenFamilyAsync(refreshToken.UserId, refreshToken.TokenFamilyId, ct) ?? throw new UnauthorizedException("Invalid refresh token"); // Allow slight drift to adjust for cases where the browser refreshes @@ -60,11 +60,11 @@ public class LoginProvider(ITokenFamilyStore repo, IJWTTokenProvider tokenProvid if (counterDiff < 0 || counterDiff > maxDrift) { Log.WriteWarningMessage(LOGTAG, "TokenFamilyReuse", null, $"Invalid refresh token counter: {tokenFamily.Counter} != {refreshToken.Counter}"); - await repo.InvalidateTokenFamily(tokenFamily.UserId, tokenFamily.Id, ct); + await repo.InvalidateTokenFamilyAsync(tokenFamily.UserId, tokenFamily.Id, ct); throw new UnauthorizedException("Token family re-use detected"); } - tokenFamily = await repo.IncrementTokenFamily(tokenFamily, ct); + tokenFamily = await repo.IncrementTokenFamilyAsync(tokenFamily, ct); var isShortLived = (refreshToken.Expiration - refreshToken.ValidFrom).TotalMinutes <= jwtConfig.RefreshTokenShortLivedDurationInMinutes + 1; (var newRefreshToken, var newNonce) = tokenProvider.CreateRefreshToken(refreshToken.UserId, tokenFamily.Id, tokenFamily.Counter, isShortLived); @@ -75,13 +75,13 @@ public class LoginProvider(ITokenFamilyStore repo, IJWTTokenProvider tokenProvid ); } - public async Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithPassword(string password, bool shortLived, CancellationToken ct) + public async Task<(string AccessToken, string RefreshToken, string? Nonce)> PerformLoginWithPasswordAsync(string password, bool shortLived, CancellationToken ct) { if (!connection.ApplicationSettings.VerifyWebserverPassword(password)) throw new UnauthorizedException("Invalid password"); var userId = "webserver"; - var tokenFamily = await repo.CreateTokenFamily(userId, ct); + var tokenFamily = await repo.CreateTokenFamilyAsync(userId, ct); var (refreshToken, nonce) = tokenProvider.CreateRefreshToken(userId, tokenFamily.Id, tokenFamily.Counter, shortLived); return ( @@ -91,17 +91,17 @@ public class LoginProvider(ITokenFamilyStore repo, IJWTTokenProvider tokenProvid ); } - public async Task PerformLogoutWithRefreshToken(string refreshTokenString, string? nonce, CancellationToken ct) + public async Task PerformLogoutWithRefreshTokenAsync(string refreshTokenString, string? nonce, CancellationToken ct) { var token = tokenProvider.ReadRefreshToken(refreshTokenString, nonce); - await repo.InvalidateTokenFamily(token.UserId, token.TokenFamilyId, ct); + await repo.InvalidateTokenFamilyAsync(token.UserId, token.TokenFamilyId, ct); } - public async Task PerformCompleteLogout(string userId, CancellationToken ct) + public async Task PerformCompleteLogoutAsync(string userId, CancellationToken ct) { if (string.IsNullOrWhiteSpace(userId)) - await repo.InvalidateAllTokens(ct); + await repo.InvalidateAllTokensAsync(ct); else - await repo.InvalidateAllTokenFamilies(userId, ct); + await repo.InvalidateAllTokenFamiliesAsync(userId, ct); } } diff --git a/Duplicati/WebserverCore/Services/QueueRunnerService.cs b/Duplicati/WebserverCore/Services/QueueRunnerService.cs index 4712dbe86..7d4b860d2 100644 --- a/Duplicati/WebserverCore/Services/QueueRunnerService.cs +++ b/Duplicati/WebserverCore/Services/QueueRunnerService.cs @@ -125,11 +125,11 @@ public class QueueRunnerService( var nextTask = _tasks[0]; _tasks.RemoveAt(0); - _current = (Task.Run(() => RunTask(nextTask), CancellationToken.None), nextTask); + _current = (Task.Run(() => RunTaskAsync(nextTask), CancellationToken.None), nextTask); } } - private async Task RunTask(IQueuedTask task) + private async Task RunTaskAsync(IQueuedTask task) { var completed = false; try @@ -140,7 +140,7 @@ public class QueueRunnerService( if (task.OnStarting != null) await task.OnStarting().ConfigureAwait(false); - Runner.Run(connection, eventPollNotify, notificationUpdateService, progressStateProviderService, applicationSettings, task, true); + await Runner.RunAsync(connection, eventPollNotify, notificationUpdateService, progressStateProviderService, applicationSettings, task, true).ConfigureAwait(false); // If the task is completed, don't call OnFinished again completed = true; @@ -213,8 +213,8 @@ public class QueueRunnerService( } /// - public IBasicResults? RunImmediately(IQueuedTask task) + public async Task RunImmediatelyAsync(IQueuedTask task) { - return Runner.Run(connection, eventPollNotify, notificationUpdateService, progressStateProviderService, applicationSettings, task, false); + return await Runner.RunAsync(connection, eventPollNotify, notificationUpdateService, progressStateProviderService, applicationSettings, task, false).ConfigureAwait(false); } } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs b/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs index cc0800a5a..9b9b9066d 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerHandler.cs @@ -43,14 +43,14 @@ public class RemoteControllerHandler(Connection connection, IHttpClientFactory h private static readonly string LOGTAG = Log.LogTagFromType(); /// - public Task> OnConnect(Dictionary metadata) + public Task> OnConnectAsync(Dictionary metadata) { metadata["feature:additional-report-url"] = connection.ApplicationSettings.AdditionalReportUrl; return Task.FromResult(metadata); } /// - public Task ReKey(ClaimedClientData data) + public Task ReKeyAsync(ClaimedClientData data) { var oldCerts = connection.ApplicationSettings.RemoteControlConfig == null ? null @@ -75,7 +75,7 @@ public class RemoteControllerHandler(Connection connection, IHttpClientFactory h } /// - public async Task OnControl(KeepRemoteConnection.ControlMessage message) + public async Task OnControlAsync(KeepRemoteConnection.ControlMessage message) { // Make method async await Task.CompletedTask; @@ -193,7 +193,7 @@ public class RemoteControllerHandler(Connection connection, IHttpClientFactory h /// - public async Task OnMessage(KeepRemoteConnection.CommandMessage commandMessage) + public async Task OnMessageAsync(KeepRemoteConnection.CommandMessage commandMessage) { if (connection.ApplicationSettings.DisableConsoleControl) { @@ -211,6 +211,6 @@ public class RemoteControllerHandler(Connection connection, IHttpClientFactory h if (!string.IsNullOrWhiteSpace(psk)) httpClient.DefaultRequestHeaders.Add(Middlewares.PreSharedKeyFilter.HeaderName, psk); - await commandMessage.Handle(httpClient); + await commandMessage.HandleAsync(httpClient); } } diff --git a/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs b/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs index 6df18f1b2..b37110594 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerRegistrationService.cs @@ -96,7 +96,7 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli public string? RegistrationUrl { get; private set; } /// - public Task RegisterMachine(string registrationUrl) + public Task RegisterMachineAsync(string registrationUrl) { if (_registerForRemote != null) throw new InvalidOperationException("Already registering"); @@ -111,7 +111,7 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli try { _registerForRemote = new RegisterForRemote(registrationUrl, httpClientFactory.CreateClient(), _operationCancellation.Token); - var data = await _registerForRemote.Register(maxRetries: 3, retryInterval: TimeSpan.FromSeconds(5)); + var data = await _registerForRemote.RegisterAsync(maxRetries: 3, retryInterval: TimeSpan.FromSeconds(5)); // Make the link visible to outside if (data.RegistrationData != null) @@ -120,7 +120,7 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli eventPollNotify.SignalRemoteControlUpdate(); // Grab the claim data once it is returned - var claimData = await _registerForRemote.Claim(); + var claimData = await _registerForRemote.ClaimAsync(); connection.ApplicationSettings.RemoteControlConfig = JsonSerializer.Serialize(new RemoteControlConfig { Token = claimData.JWT, @@ -131,7 +131,7 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli // If settings are present in the claim data, apply them via the control handler if (claimData.Settings != null && claimData.Settings.Count > 0) - await controllerHandler.OnControl(KeepRemoteConnection.ControlMessage.CreateSettingsControlMessage(claimData.Settings)); + await controllerHandler.OnControlAsync(KeepRemoteConnection.ControlMessage.CreateSettingsControlMessage(claimData.Settings)); // Automatically connect after we are registered if (remoteController.CanEnable) @@ -145,7 +145,7 @@ public class RemoteControllerRegistrationService(Connection connection, IHttpCli } /// - public Task WaitForRegistration() + public Task WaitForRegistrationAsync() { if (_registrationTask == null) throw new InvalidOperationException("Not registering"); diff --git a/Duplicati/WebserverCore/Services/RemoteControllerService.cs b/Duplicati/WebserverCore/Services/RemoteControllerService.cs index dff3a181b..1750f3b0d 100644 --- a/Duplicati/WebserverCore/Services/RemoteControllerService.cs +++ b/Duplicati/WebserverCore/Services/RemoteControllerService.cs @@ -84,10 +84,10 @@ public class RemoteControllerService(Connection connection, IRemoteControllerHan config.RefreshSettingsBy, forceConnect, CancellationToken.None, - controllerHandler.OnConnect, - controllerHandler.ReKey, - controllerHandler.OnControl, - controllerHandler.OnMessage + controllerHandler.OnConnectAsync, + controllerHandler.ReKeyAsync, + controllerHandler.OnControlAsync, + controllerHandler.OnMessageAsync ); _keepRemoteConnection.StateChanged += (_, _) => eventPollNotify.SignalRemoteControlUpdate(); diff --git a/Duplicati/WebserverCore/Services/SystemInfoProvider.cs b/Duplicati/WebserverCore/Services/SystemInfoProvider.cs index 595f5f092..3412d58b2 100644 --- a/Duplicati/WebserverCore/Services/SystemInfoProvider.cs +++ b/Duplicati/WebserverCore/Services/SystemInfoProvider.cs @@ -59,6 +59,7 @@ public class SystemInfoProvider(IApplicationSettings applicationSettings, Connec "v2:system:temp-disk-space", "v1:subscribe:remotecontrol", + // "v1:ipc:controller", // "v1:subscribe:scheduler", ]; diff --git a/Duplicati/WebserverCore/Services/TaskQueueService.cs b/Duplicati/WebserverCore/Services/TaskQueueService.cs index 86e92ea69..3616ea2a2 100644 --- a/Duplicati/WebserverCore/Services/TaskQueueService.cs +++ b/Duplicati/WebserverCore/Services/TaskQueueService.cs @@ -84,19 +84,19 @@ public class TaskQueueService(IQueueRunnerService queueRunnerService) : ITaskQue } /// - public void StopTask(long taskid) - => StopOrAbortTask(taskid, false); + public Task StopTaskAsync(long taskid) + => StopOrAbortTaskAsync(taskid, false); /// - public void AbortTask(long taskid) - => StopOrAbortTask(taskid, true); + public Task AbortTaskAsync(long taskid) + => StopOrAbortTaskAsync(taskid, true); /// /// Stops or aborts a task based on the provided task ID. /// /// The ID of the task to stop or abort. /// If true, the task will be aborted; otherwise, it will be stopped gracefully. - private void StopOrAbortTask(long taskid, bool abort) + private async Task StopOrAbortTaskAsync(long taskid, bool abort) { var task = queueRunnerService.GetCurrentTask(); var tasks = queueRunnerService.GetCurrentTasks(); @@ -109,8 +109,8 @@ public class TaskQueueService(IQueueRunnerService queueRunnerService) : ITaskQue throw new NotFoundException("No such task found"); if (abort) - task.Abort(); + await task.AbortAsync().ConfigureAwait(false); else - task.Stop(); + await task.StopAsync().ConfigureAwait(false); } } \ No newline at end of file diff --git a/Duplicati/WebserverCore/Services/TokenFamilyStore.cs b/Duplicati/WebserverCore/Services/TokenFamilyStore.cs index ab241e8e1..f3112acd8 100644 --- a/Duplicati/WebserverCore/Services/TokenFamilyStore.cs +++ b/Duplicati/WebserverCore/Services/TokenFamilyStore.cs @@ -27,7 +27,7 @@ namespace Duplicati.WebserverCore.Services; public class TokenFamilyStore(Connection connection) : ITokenFamilyStore { // Use Dapper? - public Task CreateTokenFamily(string userId, CancellationToken ct) + public Task CreateTokenFamilyAsync(string userId, CancellationToken ct) { var familyId = System.Security.Cryptography.RandomNumberGenerator.GetHexString(16); var counter = System.Security.Cryptography.RandomNumberGenerator.GetInt32(1024) % 1024; @@ -45,7 +45,7 @@ public class TokenFamilyStore(Connection connection) : ITokenFamilyStore return Task.FromResult(new ITokenFamilyStore.TokenFamily(familyId, userId, counter, lastUpdated)); } - public Task GetTokenFamily(string userId, string familyId, CancellationToken ct) + public Task GetTokenFamilyAsync(string userId, string familyId, CancellationToken ct) { ITokenFamilyStore.TokenFamily? family = null; connection.ExecuteWithCommand(cmd => @@ -68,7 +68,7 @@ public class TokenFamilyStore(Connection connection) : ITokenFamilyStore return Task.FromResult(family ?? throw new Exceptions.UnauthorizedException("Token family not found")); } - public Task IncrementTokenFamily(ITokenFamilyStore.TokenFamily tokenFamily, CancellationToken ct) + public Task IncrementTokenFamilyAsync(ITokenFamilyStore.TokenFamily tokenFamily, CancellationToken ct) { var nextCounter = tokenFamily.Counter + 1; var lastUpdated = DateTime.UtcNow; @@ -87,7 +87,7 @@ public class TokenFamilyStore(Connection connection) : ITokenFamilyStore return Task.FromResult(new ITokenFamilyStore.TokenFamily(tokenFamily.Id, tokenFamily.UserId, nextCounter, lastUpdated)); } - public Task InvalidateTokenFamily(string userId, string familyId, CancellationToken ct) + public Task InvalidateTokenFamilyAsync(string userId, string familyId, CancellationToken ct) { connection.ExecuteWithCommand(cmd => { @@ -102,7 +102,7 @@ public class TokenFamilyStore(Connection connection) : ITokenFamilyStore return Task.CompletedTask; } - public Task InvalidateAllTokenFamilies(string userId, CancellationToken ct) + public Task InvalidateAllTokenFamiliesAsync(string userId, CancellationToken ct) { connection.ExecuteWithCommand(cmd => { @@ -114,7 +114,7 @@ public class TokenFamilyStore(Connection connection) : ITokenFamilyStore return Task.CompletedTask; } - public Task InvalidateAllTokens(CancellationToken ct) + public Task InvalidateAllTokensAsync(CancellationToken ct) { connection.ExecuteWithCommand(cmd => cmd.ExecuteNonQuery(@"DELETE FROM ""TokenFamily""") diff --git a/Executables/Duplicati.Agent/Program.cs b/Executables/Duplicati.Agent/Program.cs index 0c83f365d..e73b84898 100644 --- a/Executables/Duplicati.Agent/Program.cs +++ b/Executables/Duplicati.Agent/Program.cs @@ -27,6 +27,6 @@ namespace Duplicati.Agent.Net10 public static class Program { public static Task Main(string[] args) - => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Agent.Program.Main(args)); + => CrashlogHelper.WrapWithCrashLog(() => Duplicati.Agent.Program.MainAsync(args)); } } \ No newline at end of file diff --git a/Executables/Duplicati.CommandLine.DatabaseTool/Program.cs b/Executables/Duplicati.CommandLine.DatabaseTool/Program.cs index f67ef668b..c6c79dd0c 100644 --- a/Executables/Duplicati.CommandLine.DatabaseTool/Program.cs +++ b/Executables/Duplicati.CommandLine.DatabaseTool/Program.cs @@ -18,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Threading.Tasks; using Duplicati.Library.Crashlog; namespace Duplicati.CommandLine.DatabaseTool.Net10 @@ -25,7 +26,7 @@ namespace Duplicati.CommandLine.DatabaseTool.Net10 // Wrapper class to keep code independent public static class Program { - public static int Main(string[] args) - => CrashlogHelper.WrapWithCrashLog(() => DatabaseTool.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult()); + public static Task Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => DatabaseTool.Program.MainAsync(args)); } } \ No newline at end of file diff --git a/Executables/Duplicati.CommandLine.SourceTool/Program.cs b/Executables/Duplicati.CommandLine.SourceTool/Program.cs index 47c23243b..8516748fa 100644 --- a/Executables/Duplicati.CommandLine.SourceTool/Program.cs +++ b/Executables/Duplicati.CommandLine.SourceTool/Program.cs @@ -18,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Threading.Tasks; using Duplicati.Library.Crashlog; namespace Duplicati.CommandLine.SourceTool.Net10 @@ -25,7 +26,7 @@ namespace Duplicati.CommandLine.SourceTool.Net10 // Wrapper class to keep code independent public static class Program { - public static int Main(string[] args) - => CrashlogHelper.WrapWithCrashLog(() => SourceTool.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult()); + public static Task Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => SourceTool.Program.Main(args)); } } \ No newline at end of file diff --git a/Executables/Duplicati.CommandLine.SyncTool/Program.cs b/Executables/Duplicati.CommandLine.SyncTool/Program.cs index c13843c16..db2b1a631 100644 --- a/Executables/Duplicati.CommandLine.SyncTool/Program.cs +++ b/Executables/Duplicati.CommandLine.SyncTool/Program.cs @@ -18,6 +18,7 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System.Threading.Tasks; using Duplicati.Library.Crashlog; namespace Duplicati.CommandLine.SyncTool.Net10 @@ -25,7 +26,7 @@ namespace Duplicati.CommandLine.SyncTool.Net10 // Wrapper class to keep code independent public static class Program { - public static int Main(string[] args) - => CrashlogHelper.WrapWithCrashLog(() => RemoteSynchronization.Program.Main(args).ConfigureAwait(false).GetAwaiter().GetResult()); + public static Task Main(string[] args) + => CrashlogHelper.WrapWithCrashLog(() => RemoteSynchronization.Program.MainAsync(args)); } } \ No newline at end of file diff --git a/Tools/RemoteSynchronization/Program.cs b/Tools/RemoteSynchronization/Program.cs index a6292fcc6..1dfb878d9 100644 --- a/Tools/RemoteSynchronization/Program.cs +++ b/Tools/RemoteSynchronization/Program.cs @@ -34,7 +34,7 @@ namespace RemoteSynchronization /// /// The commandline arguments. /// 0 on success, -1 on abort, and the number of errors encountered otherwise. - public static Task Main(string[] args) + public static Task MainAsync(string[] args) => RemoteSynchronizationRunner.RunAsync(args); } } diff --git a/WebserverCore.Client.UsageExample/Program.cs b/WebserverCore.Client.UsageExample/Program.cs index 1ee970ae4..d1fb947bf 100644 --- a/WebserverCore.Client.UsageExample/Program.cs +++ b/WebserverCore.Client.UsageExample/Program.cs @@ -37,22 +37,22 @@ class Program try { // Authenticate explicitly (optional - happens automatically on first API call) - await client.Authenticate(); + await client.AuthenticateAsync(); Console.WriteLine("✓ Authentication successful"); // Demonstrate all client methods - await DemonstrateAuthenticationMethods(client); - await DemonstrateBackupManagement(client); - await DemonstrateBackupOperations(client); - await DemonstrateBackupDataAccess(client); - await DemonstrateDatabaseManagement(client); - await DemonstrateExportOperations(client); - await DemonstrateServerManagement(client); - await DemonstrateTaskManagement(client); - await DemonstrateSystemInformation(client); - await DemonstrateSettingsManagement(client); - await DemonstrateFilesystemOperations(client); - await DemonstrateV2ApiMethods(client); + await DemonstrateAuthenticationMethodsAsync(client); + await DemonstrateBackupManagementAsync(client); + await DemonstrateBackupOperationsAsync(client); + await DemonstrateBackupDataAccessAsync(client); + await DemonstrateDatabaseManagementAsync(client); + await DemonstrateExportOperationsAsync(client); + await DemonstrateServerManagementAsync(client); + await DemonstrateTaskManagementAsync(client); + await DemonstrateSystemInformationAsync(client); + await DemonstrateSettingsManagementAsync(client); + await DemonstrateFilesystemOperationsAsync(client); + await DemonstrateV2ApiMethodsAsync(client); } catch (Exception ex) { @@ -63,7 +63,7 @@ class Program /// /// Demonstrates authentication-related methods /// - static async Task DemonstrateAuthenticationMethods(DuplicatiServerClient client) + static async Task DemonstrateAuthenticationMethodsAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Authentication Methods ==="); @@ -78,7 +78,7 @@ class Program var operationToken = await client.IssueTokenV1Async("export", CancellationToken.None); Console.WriteLine($"✓ Operation token issued: {operationToken.Token[..10]}..."); - await client.Authenticate(); + await client.AuthenticateAsync(); // Issue a forever token try { @@ -110,7 +110,7 @@ class Program /// /// Demonstrates backup management methods /// - static async Task DemonstrateBackupManagement(DuplicatiServerClient client) + static async Task DemonstrateBackupManagementAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Backup Management ==="); @@ -181,7 +181,7 @@ class Program /// /// Demonstrates backup operations /// - static async Task DemonstrateBackupOperations(DuplicatiServerClient client) + static async Task DemonstrateBackupOperationsAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Backup Operations ==="); @@ -248,7 +248,7 @@ class Program /// /// Demonstrates backup data access methods /// - static async Task DemonstrateBackupDataAccess(DuplicatiServerClient client) + static async Task DemonstrateBackupDataAccessAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Backup Data Access ==="); @@ -289,7 +289,7 @@ class Program /// /// Demonstrates database management methods /// - static async Task DemonstrateDatabaseManagement(DuplicatiServerClient client) + static async Task DemonstrateDatabaseManagementAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Database Management ==="); @@ -333,7 +333,7 @@ class Program /// /// Demonstrates export operations /// - static async Task DemonstrateExportOperations(DuplicatiServerClient client) + static async Task DemonstrateExportOperationsAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Export Operations ==="); @@ -371,7 +371,7 @@ class Program /// /// Demonstrates server management methods /// - static async Task DemonstrateServerManagement(DuplicatiServerClient client) + static async Task DemonstrateServerManagementAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Server Management ==="); @@ -405,7 +405,7 @@ class Program /// /// Demonstrates task management methods /// - static async Task DemonstrateTaskManagement(DuplicatiServerClient client) + static async Task DemonstrateTaskManagementAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Task Management ==="); @@ -451,7 +451,7 @@ class Program /// /// Demonstrates system information methods /// - static async Task DemonstrateSystemInformation(DuplicatiServerClient client) + static async Task DemonstrateSystemInformationAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== System Information ==="); @@ -488,7 +488,7 @@ class Program /// /// Demonstrates settings management methods /// - static async Task DemonstrateSettingsManagement(DuplicatiServerClient client) + static async Task DemonstrateSettingsManagementAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Settings Management ==="); @@ -525,7 +525,7 @@ class Program /// /// Demonstrates filesystem operations /// - static async Task DemonstrateFilesystemOperations(DuplicatiServerClient client) + static async Task DemonstrateFilesystemOperationsAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== Filesystem Operations ==="); @@ -559,7 +559,7 @@ class Program /// /// Demonstrates V2 API methods /// - static async Task DemonstrateV2ApiMethods(DuplicatiServerClient client) + static async Task DemonstrateV2ApiMethodsAsync(DuplicatiServerClient client) { Console.WriteLine("\n=== V2 API Methods ===");