diff --git a/Duplicati/Library/RestAPI/FIXMEGlobal.cs b/Duplicati/Library/RestAPI/FIXMEGlobal.cs
index 5a977f0c2..6b2491e70 100644
--- a/Duplicati/Library/RestAPI/FIXMEGlobal.cs
+++ b/Duplicati/Library/RestAPI/FIXMEGlobal.cs
@@ -48,12 +48,7 @@ namespace Duplicati.Library.RestAPI
///
/// A delegate method for creating a copy of the current progress state
///
- public static Func GenerateProgressState;
-
- ///
- /// The status event signaler, used to control long polling of status updates
- ///
- public static EventPollNotify StatusEventNotifyer => Provider.GetRequiredService();
+ public static Func GenerateProgressState;
///
/// For keeping and incrementing last last events Ids of db save and last notification
@@ -71,28 +66,16 @@ namespace Duplicati.Library.RestAPI
///
public static string DataFolder;
- ///
- /// This is the scheduling thread
- ///
- public static ISchedulerService Scheduler => Provider.GetRequiredService();
-
///
/// The log redirect handler
///
public static readonly LogWriteHandler LogHandler = new LogWriteHandler();
- ///
- /// The update poll thread.
- ///
- public static UpdatePollThread UpdatePoller => Provider.GetRequiredService();
-
-
///
/// Used to check the origin of the web server (e.g. Tray icon or a stand alone Server)
///
public static string Origin = "Server";
-
///
/// The application exit event
///
diff --git a/Duplicati/Library/RestAPI/Runner.cs b/Duplicati/Library/RestAPI/Runner.cs
index 3480d1795..841454dcb 100644
--- a/Duplicati/Library/RestAPI/Runner.cs
+++ b/Duplicati/Library/RestAPI/Runner.cs
@@ -29,6 +29,8 @@ using Duplicati.Server.Serialization;
using Duplicati.Library.RestAPI;
using Duplicati.Library.Utility;
using System.Threading.Tasks;
+using Duplicati.Server.Database;
+using Duplicati.Server.Serialization.Interface;
namespace Duplicati.Server
{
@@ -52,6 +54,12 @@ namespace Duplicati.Server
public Func? OnStarting { get; set; }
public Func? OnFinished { get; set; }
+ ///
+ /// Callback to be executed when the task is finished.
+ /// Only supported for the delete operation for now.
+ ///
+ internal Action? AfterTaskFinished { get; set; }
+
public DuplicatiOperation Operation { get; internal set; }
public Serialization.Interface.IBackup? Backup { get; internal set; }
public IDictionary? ExtraOptions { get; internal set; }
@@ -141,9 +149,6 @@ namespace Duplicati.Server
{
m_taskID = System.Threading.Interlocked.Increment(ref RunnerTaskID);
}
-
- public Task Execute()
- => Task.Run(() => Runner.Run(this, true));
}
private class CustomRunnerTask : RunnerData
@@ -166,7 +171,7 @@ namespace Duplicati.Server
return new CustomRunnerTask(runner);
}
- public static IRunnerData CreateTask(DuplicatiOperation operation, Serialization.Interface.IBackup backup, IDictionary? extraOptions = null, string[]? filterStrings = null, string[]? extraArguments = null, int pageSize = 0, int pageOffset = 0)
+ public static IRunnerData CreateTask(DuplicatiOperation operation, IBackup backup, IDictionary? extraOptions = null, string[]? filterStrings = null, string[]? extraArguments = null, int pageSize = 0, int pageOffset = 0)
{
return new RunnerData()
{
@@ -180,7 +185,18 @@ namespace Duplicati.Server
};
}
- public static IRunnerData CreateListTask(Serialization.Interface.IBackup backup, string[]? filters, bool onlyPrefix, bool allVersions, bool folderContents, DateTime time)
+ public static IRunnerData CreateDeleteTask(IBackup backup, IDictionary extraOptions, Action? afterTaskFinished = null)
+ {
+ return new RunnerData()
+ {
+ Operation = DuplicatiOperation.Delete,
+ Backup = backup,
+ ExtraOptions = extraOptions,
+ AfterTaskFinished = afterTaskFinished
+ };
+ }
+
+ public static IRunnerData CreateListTask(IBackup backup, string[]? filters, bool onlyPrefix, bool allVersions, bool folderContents, DateTime time)
{
var dict = new Dictionary();
if (onlyPrefix)
@@ -199,7 +215,7 @@ namespace Duplicati.Server
filters);
}
- public static IRunnerData CreateListFilesetsTask(Serialization.Interface.IBackup backup, Dictionary? extraOptions = null)
+ public static IRunnerData CreateListFilesetsTask(IBackup backup, Dictionary? extraOptions = null)
{
return CreateTask(
DuplicatiOperation.ListFilesets,
@@ -207,7 +223,7 @@ namespace Duplicati.Server
extraOptions ?? new Dictionary());
}
- public static IRunnerData CreateListFolderContents(Serialization.Interface.IBackup backup, string[] folders, DateTime time, int pageSize, int pageOffset)
+ public static IRunnerData CreateListFolderContents(IBackup backup, string[]? folders, DateTime time, int pageSize, int pageOffset)
{
var dict = new Dictionary();
if (time.Ticks > 0)
@@ -222,7 +238,7 @@ namespace Duplicati.Server
pageOffset: pageOffset);
}
- public static IRunnerData ListFileVersionsTask(Serialization.Interface.IBackup backup, string[] filepaths, int pageSize, int pageOffset)
+ public static IRunnerData ListFileVersionsTask(IBackup backup, string[]? filepaths, int pageSize, int pageOffset)
{
var dict = new Dictionary();
return CreateTask(
@@ -234,7 +250,7 @@ namespace Duplicati.Server
pageOffset: pageOffset);
}
- public static IRunnerData CreateSearchEntriesTask(Serialization.Interface.IBackup backup, string[] filters, string[] folders, DateTime time, int pageSize, int pageOffset)
+ public static IRunnerData CreateSearchEntriesTask(IBackup backup, string[]? filters, string[]? folders, DateTime time, int pageSize, int pageOffset)
{
var dict = new Dictionary();
if (time.Ticks > 0)
@@ -251,7 +267,7 @@ namespace Duplicati.Server
}
- public static IRunnerData CreateRestoreTask(Serialization.Interface.IBackup backup, string[]? filters,
+ public static IRunnerData CreateRestoreTask(IBackup backup, string[]? filters,
DateTime time, string? restoreTarget, bool overwrite, bool restore_permissions,
bool skip_metadata, string? passphrase)
{
@@ -276,7 +292,7 @@ namespace Duplicati.Server
}
private class MessageSink : Library.Main.IMessageSink
{
- private class ProgressState : Serialization.Interface.IProgressEventData
+ private class ProgressState : IProgressEventData
{
private readonly string? m_backupID;
private readonly long m_taskID;
@@ -394,18 +410,18 @@ namespace Duplicati.Server
#endregion
}
- public static string GetCommandLine(IRunnerData data)
+ public static string GetCommandLine(Connection databaseConnection, IRunnerData data)
{
var backup = data.Backup;
if (backup == null)
throw new ArgumentNullException(nameof(backup));
- var options = ApplyOptions(backup, GetCommonOptions());
+ var options = ApplyOptions(databaseConnection, backup, GetCommonOptions(databaseConnection));
if (data.ExtraOptions != null)
foreach (var k in data.ExtraOptions)
options[k.Key] = k.Value;
- var cf = FIXMEGlobal.DataConnection.Filters;
+ var cf = databaseConnection.Filters;
var bf = backup.Filters;
var sources =
@@ -440,18 +456,18 @@ namespace Duplicati.Server
return cmd.ToString();
}
- public static string[] GetCommandLineParts(IRunnerData data)
+ public static string[] GetCommandLineParts(Connection databaseConnection, IRunnerData data)
{
var backup = data.Backup;
if (backup == null)
throw new ArgumentNullException(nameof(backup));
- var options = ApplyOptions(backup, GetCommonOptions());
+ var options = ApplyOptions(databaseConnection, backup, GetCommonOptions(databaseConnection));
if (data.ExtraOptions != null)
foreach (var k in data.ExtraOptions)
options[k.Key] = k.Value;
- var cf = FIXMEGlobal.DataConnection.Filters;
+ var cf = databaseConnection.Filters;
var bf = backup.Filters;
var sources =
@@ -480,7 +496,15 @@ namespace Duplicati.Server
return parts.ToArray();
}
- public static IBasicResults? Run(IRunnerData data, bool fromQueue)
+ public static IBasicResults? Run(Connection databaseConnection, EventPollNotify eventPollNotify, IQueuedTask data, bool fromQueue)
+ {
+ if (data is IRunnerData runnerData)
+ return Run(databaseConnection, eventPollNotify, runnerData, fromQueue);
+
+ throw new ArgumentException("Invalid task type", nameof(data));
+ }
+
+ private static IBasicResults? Run(Connection databaseConnection, EventPollNotify eventPollNotify, IRunnerData data, bool fromQueue)
{
data.TaskStarted = DateTime.Now;
if (data is CustomRunnerTask task)
@@ -489,13 +513,13 @@ namespace Duplicati.Server
{
var sink = new MessageSink(task.TaskID, null);
FIXMEGlobal.GenerateProgressState = sink.Copy;
- FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
+ eventPollNotify.SignalNewEvent();
task.Run(sink);
}
catch (Exception ex)
{
- FIXMEGlobal.DataConnection.LogError(string.Empty, "Failed while executing custom task", ex);
+ databaseConnection.LogError(string.Empty, "Failed while executing custom task", ex);
}
finally
{
@@ -518,10 +542,10 @@ namespace Duplicati.Server
if (fromQueue)
{
FIXMEGlobal.GenerateProgressState = () => sink.Copy();
- FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
+ eventPollNotify.SignalNewEvent();
}
- var options = ApplyOptions(backup, GetCommonOptions());
+ var options = ApplyOptions(databaseConnection, backup, GetCommonOptions(databaseConnection));
if (data.ExtraOptions != null)
foreach (var k in data.ExtraOptions)
options[k.Key] = k.Value;
@@ -529,7 +553,7 @@ namespace Duplicati.Server
// Pack in the system or task config for easy restore
if (data.Operation == DuplicatiOperation.Backup && options.ContainsKey("store-task-config"))
{
- tempfolder = StoreTaskConfigAndGetTempFolder(data, options);
+ tempfolder = StoreTaskConfigAndGetTempFolder(databaseConnection, data, options);
}
// Attach a log scope that tags all messages to relay the TaskID and BackupID
@@ -557,7 +581,7 @@ namespace Duplicati.Server
catch { }
((RunnerData)data).Controller = controller;
- var appSettings = FIXMEGlobal.DataConnection.ApplicationSettings;
+ var appSettings = databaseConnection.ApplicationSettings;
data.UpdateThrottleSpeeds(appSettings.UploadSpeedLimit, appSettings.DownloadSpeedLimit);
// Pass on the provider, will be replaced if configured in the backup
@@ -573,7 +597,7 @@ namespace Duplicati.Server
{
case DuplicatiOperation.Backup:
{
- var filter = ApplyFilter(backup, GetCommonFilter());
+ var filter = ApplyFilter(backup, GetCommonFilter(databaseConnection));
var sources =
(from n in backup.Sources
let p = SpecialFolders.ExpandEnvironmentVariables(n)
@@ -581,62 +605,62 @@ namespace Duplicati.Server
select p).ToArray();
var r = controller.Backup(sources, filter);
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.List:
{
var r = controller.List(data.FilterStrings, null);
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.Repair:
{
var r = controller.Repair(data.FilterStrings == null ? null : new Library.Utility.FilterExpression(data.FilterStrings));
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.RepairUpdate:
{
var r = controller.UpdateDatabaseWithVersions();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.Remove:
{
var r = controller.Delete();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.Restore:
{
var r = controller.Restore(data.FilterStrings);
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.Verify:
{
var r = controller.Test();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.Compact:
{
var r = controller.Compact();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.CreateReport:
{
- using (var tf = new TempFile())
+ using (var tf = new Library.Utility.TempFile())
{
var r = controller.CreateLogDatabase(tf);
- var tempid = FIXMEGlobal.DataConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3));
+ var tempid = databaseConnection.RegisterTempFile("create-bug-report", r.TargetPath, DateTime.Now.AddDays(3));
if (string.Equals(tf, r.TargetPath, Utility.ClientFilenameStringComparison))
tf.Protected = true;
- FIXMEGlobal.DataConnection.RegisterNotification(
+ databaseConnection.RegisterNotification(
NotificationType.Information,
"Bugreport ready",
"Bugreport is ready for download",
@@ -656,7 +680,7 @@ namespace Duplicati.Server
case DuplicatiOperation.ListRemote:
{
var r = controller.ListRemote();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
@@ -675,21 +699,21 @@ namespace Duplicati.Server
System.IO.File.Delete(dbpath);
}
}
- FIXMEGlobal.DataConnection.DeleteBackup(backup);
- FIXMEGlobal.Scheduler.Reschedule();
+ databaseConnection.DeleteBackup(backup);
+ (data as RunnerData)?.AfterTaskFinished?.Invoke(data);
return null;
}
case DuplicatiOperation.Vacuum:
{
var r = controller.Vacuum();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.ListFilesets:
{
var r = controller.ListFilesets();
- UpdateMetadata(backup, r);
+ UpdateMetadataBase(databaseConnection, eventPollNotify, backup, r);
return r;
}
case DuplicatiOperation.ListFolderContents:
@@ -714,9 +738,9 @@ namespace Duplicati.Server
}
catch (Exception ex)
{
- FIXMEGlobal.DataConnection.LogError(data.Backup?.ID, string.Format("Failed while executing {0} \"{1}\" (id: {2})", data.Operation, data.Backup?.Name, data.Backup?.ID), ex);
+ databaseConnection.LogError(data.Backup?.ID, string.Format("Failed while executing {0} \"{1}\" (id: {2})", data.Operation, data.Backup?.Name, data.Backup?.ID), ex);
if (data.Backup != null)
- UpdateMetadataError(data.Backup, ex);
+ UpdateMetadataError(databaseConnection, data.Backup, ex);
Library.UsageReporter.Reporter.Report(ex);
if (!fromQueue)
@@ -731,7 +755,7 @@ namespace Duplicati.Server
}
}
- private static TempFolder? StoreTaskConfigAndGetTempFolder(IRunnerData data, Dictionary options)
+ private static TempFolder? StoreTaskConfigAndGetTempFolder(Connection databaseConnection, IRunnerData data, Dictionary options)
{
if (data.Backup == null)
throw new ArgumentNullException(nameof(data.Backup));
@@ -746,13 +770,13 @@ namespace Duplicati.Server
{
tempfolder = new TempFolder();
var temppath = System.IO.Path.Combine(tempfolder, "task-setup.json");
- using (var tempfile = TempFile.WrapExistingFile(temppath))
+ using (var tempfile = Library.Utility.TempFile.WrapExistingFile(temppath))
{
object? taskdata = null;
if (all_tasks)
- taskdata = FIXMEGlobal.DataConnection.Backups.Where(x => !x.IsTemporary).Select(x => FIXMEGlobal.DataConnection.PrepareBackupForExport(FIXMEGlobal.DataConnection.GetBackup(x.ID)!));
+ taskdata = databaseConnection.Backups.Where(x => !x.IsTemporary).Select(x => databaseConnection.PrepareBackupForExport(databaseConnection.GetBackup(x.ID)!));
else
- taskdata = new[] { FIXMEGlobal.DataConnection.PrepareBackupForExport(data.Backup) };
+ taskdata = new[] { databaseConnection.PrepareBackupForExport(data.Backup) };
using (var fs = System.IO.File.OpenWrite(tempfile))
using (var sw = new System.IO.StreamWriter(fs, System.Text.Encoding.UTF8))
@@ -773,20 +797,20 @@ namespace Duplicati.Server
return tempfolder;
}
- private static void UpdateMetadataError(Duplicati.Server.Serialization.Interface.IBackup backup, Exception ex)
+ private static void UpdateMetadataError(Connection databaseConnection, Serialization.Interface.IBackup backup, Exception ex)
{
- backup.Metadata["LastErrorDate"] = Library.Utility.Utility.SerializeDateTime(DateTime.UtcNow);
+ backup.Metadata["LastErrorDate"] = Utility.SerializeDateTime(DateTime.UtcNow);
backup.Metadata["LastErrorMessage"] = ex.Message;
if (!backup.IsTemporary)
- FIXMEGlobal.DataConnection.SetMetadata(backup.Metadata, long.Parse(backup.ID), null);
+ databaseConnection.SetMetadata(backup.Metadata, long.Parse(backup.ID), null);
string? messageid = null;
if (ex is UserInformationException exception)
messageid = exception.HelpID;
FIXMEGlobal.NotificationUpdateService.IncrementLastDataUpdateId();
- FIXMEGlobal.DataConnection.RegisterNotification(
+ databaseConnection.RegisterNotification(
NotificationType.Error,
backup.IsTemporary ?
"Error" : string.Format("Error while running {0}", backup.Name),
@@ -804,31 +828,31 @@ namespace Duplicati.Server
);
}
- private static void UpdateMetadataLastCompact(Duplicati.Server.Serialization.Interface.IBackup backup, Duplicati.Library.Interface.ICompactResults r)
+ private static void UpdateMetadataLastCompact(IBackup backup, ICompactResults r)
{
if (r != null)
{
backup.Metadata["LastCompactDuration"] = r.Duration.ToString();
- backup.Metadata["LastCompactStarted"] = Library.Utility.Utility.SerializeDateTime(r.BeginTime.ToUniversalTime());
- backup.Metadata["LastCompactFinished"] = Library.Utility.Utility.SerializeDateTime(r.EndTime.ToUniversalTime());
+ backup.Metadata["LastCompactStarted"] = Utility.SerializeDateTime(r.BeginTime.ToUniversalTime());
+ backup.Metadata["LastCompactFinished"] = Utility.SerializeDateTime(r.EndTime.ToUniversalTime());
}
}
- private static void UpdateMetadataLastVacuum(Duplicati.Server.Serialization.Interface.IBackup backup, Duplicati.Library.Interface.IVacuumResults r)
+ private static void UpdateMetadataLastVacuum(IBackup backup, IVacuumResults r)
{
if (r != null)
{
backup.Metadata["LastVacuumDuration"] = r.Duration.ToString();
- backup.Metadata["LastVacuumStarted"] = Library.Utility.Utility.SerializeDateTime(r.BeginTime.ToUniversalTime());
- backup.Metadata["LastVacuumFinished"] = Library.Utility.Utility.SerializeDateTime(r.EndTime.ToUniversalTime());
+ backup.Metadata["LastVacuumStarted"] = Utility.SerializeDateTime(r.BeginTime.ToUniversalTime());
+ backup.Metadata["LastVacuumFinished"] = Utility.SerializeDateTime(r.EndTime.ToUniversalTime());
}
}
- private static void UpdateMetadata(Duplicati.Server.Serialization.Interface.IBackup backup, Duplicati.Library.Interface.IParsedBackendStatistics r)
+ private static void UpdateMetadataStatistics(IBackup backup, IParsedBackendStatistics r)
{
if (r != null)
{
- backup.Metadata["LastBackupDate"] = Library.Utility.Utility.SerializeDateTime(r.LastBackupDate.ToUniversalTime());
+ backup.Metadata["LastBackupDate"] = Utility.SerializeDateTime(r.LastBackupDate.ToUniversalTime());
backup.Metadata["BackupListCount"] = r.BackupListCount.ToString();
backup.Metadata["TotalQuotaSpace"] = r.TotalQuotaSpace.ToString();
backup.Metadata["FreeQuotaSpace"] = r.FreeQuotaSpace.ToString();
@@ -837,28 +861,28 @@ namespace Duplicati.Server
backup.Metadata["TargetFilesSize"] = r.KnownFileSize.ToString();
backup.Metadata["TargetFilesCount"] = r.KnownFileCount.ToString();
backup.Metadata["TargetFilesetsCount"] = r.KnownFilesets.ToString();
- backup.Metadata["TargetSizeString"] = Duplicati.Library.Utility.Utility.FormatSizeString(r.KnownFileSize);
+ backup.Metadata["TargetSizeString"] = Utility.FormatSizeString(r.KnownFileSize);
}
}
- private static void UpdateMetadata(Duplicati.Server.Serialization.Interface.IBackup backup, Duplicati.Library.Interface.IBasicResults result)
+ private static void UpdateMetadataBase(Connection databaseConnection, EventPollNotify eventPollNotify, Serialization.Interface.IBackup backup, IBasicResults result)
{
if (result is IRestoreResults r1)
{
backup.Metadata["LastRestoreDuration"] = r1.Duration.ToString();
- backup.Metadata["LastRestoreStarted"] = Library.Utility.Utility.SerializeDateTime(result.BeginTime.ToUniversalTime());
- backup.Metadata["LastRestoreFinished"] = Library.Utility.Utility.SerializeDateTime(result.EndTime.ToUniversalTime());
+ backup.Metadata["LastRestoreStarted"] = Utility.SerializeDateTime(result.BeginTime.ToUniversalTime());
+ backup.Metadata["LastRestoreFinished"] = Utility.SerializeDateTime(result.EndTime.ToUniversalTime());
}
if (result is IParsedBackendStatistics r2 && !result.Interrupted)
{
- UpdateMetadata(backup, r2);
+ UpdateMetadataStatistics(backup, r2);
}
if (result is IBackendStatsticsReporter r3 && !result.Interrupted)
{
if (r3.BackendStatistics is IParsedBackendStatistics statistics)
- UpdateMetadata(backup, statistics);
+ UpdateMetadataStatistics(backup, statistics);
}
if (result is ICompactResults r4 && !result.Interrupted)
@@ -881,8 +905,8 @@ namespace Duplicati.Server
backup.Metadata["SourceFilesSize"] = r.SizeOfExaminedFiles.ToString();
backup.Metadata["SourceFilesCount"] = r.ExaminedFiles.ToString();
backup.Metadata["SourceSizeString"] = Duplicati.Library.Utility.Utility.FormatSizeString(r.SizeOfExaminedFiles);
- backup.Metadata["LastBackupStarted"] = Library.Utility.Utility.SerializeDateTime(r.BeginTime.ToUniversalTime());
- backup.Metadata["LastBackupFinished"] = Library.Utility.Utility.SerializeDateTime(r.EndTime.ToUniversalTime());
+ backup.Metadata["LastBackupStarted"] = Utility.SerializeDateTime(r.BeginTime.ToUniversalTime());
+ backup.Metadata["LastBackupFinished"] = Utility.SerializeDateTime(r.EndTime.ToUniversalTime());
backup.Metadata["LastBackupDuration"] = r.Duration.ToString();
if (r.CompactResults != null)
@@ -912,7 +936,7 @@ namespace Duplicati.Server
titleType = "Warning";
}
- FIXMEGlobal.DataConnection.RegisterNotification(
+ databaseConnection.RegisterNotification(
r.FilesWithError == 0 && !r.Errors.Any() ? NotificationType.Warning : NotificationType.Error,
backup.IsTemporary ? "Warning" : $"{titleType} while running {backup.Name}",
message,
@@ -958,7 +982,7 @@ namespace Duplicati.Server
else if (result.ParsedResult == ParsedResultType.Error && result.Errors.Count() == 1)
message = $"Error: {result.Errors.Single()}";
- FIXMEGlobal.DataConnection.RegisterNotification(
+ databaseConnection.RegisterNotification(
type,
title,
message,
@@ -973,10 +997,10 @@ namespace Duplicati.Server
}
if (!backup.IsTemporary)
- FIXMEGlobal.DataConnection.SetMetadata(backup.Metadata, long.Parse(backup.ID), null);
+ databaseConnection.SetMetadata(backup.Metadata, long.Parse(backup.ID), null);
FIXMEGlobal.NotificationUpdateService.IncrementLastDataUpdateId();
- FIXMEGlobal.StatusEventNotifyer.SignalNewEvent();
+ eventPollNotify.SignalNewEvent();
}
private static bool TestIfOptionApplies()
@@ -998,7 +1022,7 @@ namespace Duplicati.Server
options["disable-module"] = string.Join(",", mods.Union(new string[] { module }).Distinct(StringComparer.OrdinalIgnoreCase));
}
- internal static Dictionary ApplyOptions(Serialization.Interface.IBackup backup, Dictionary options)
+ internal static Dictionary ApplyOptions(Connection databaseConnection, Serialization.Interface.IBackup backup, Dictionary options)
{
options["backup-name"] = backup.Name;
options["dbpath"] = backup.DBPath;
@@ -1018,7 +1042,7 @@ namespace Duplicati.Server
DisableModule("console-password-input", options);
// Patch in additional report urls
- var additionalReportUrl = FIXMEGlobal.DataConnection.ApplicationSettings.AdditionalReportUrl;
+ var additionalReportUrl = databaseConnection.ApplicationSettings.AdditionalReportUrl;
if (!string.IsNullOrWhiteSpace(additionalReportUrl))
{
options["send-http-json-urls"] = string.Join(";",
@@ -1032,7 +1056,7 @@ namespace Duplicati.Server
return options;
}
- private static IFilter? ApplyFilter(Serialization.Interface.IBackup backup, IFilter? filter)
+ private static Library.Utility.IFilter? ApplyFilter(IBackup backup, Library.Utility.IFilter? filter)
{
var f2 = backup.Filters;
if (f2 != null && f2.Length > 0)
@@ -1044,7 +1068,7 @@ namespace Duplicati.Server
? SpecialFolders.ExpandEnvironmentVariablesRegexp(n.Expression)
: SpecialFolders.ExpandEnvironmentVariables(n.Expression)
orderby n.Order
- select (IFilter)new FilterExpression(exp, n.Include))
+ select (Library.Utility.IFilter)new FilterExpression(exp, n.Include))
.Aggregate((a, b) => FilterExpression.Combine(a, b));
filter = FilterExpression.Combine(filter, nf);
@@ -1053,17 +1077,17 @@ namespace Duplicati.Server
return filter;
}
- public static Dictionary GetCommonOptions()
+ public static Dictionary GetCommonOptions(Connection databaseConnection)
{
return
- (from n in FIXMEGlobal.DataConnection.Settings
+ (from n in databaseConnection.Settings
where TestIfOptionApplies()
select n).ToDictionary(k => k.Name.StartsWith("--", StringComparison.Ordinal) ? k.Name.Substring(2) : k.Name, k => (string?)k.Value);
}
- private static IFilter? GetCommonFilter()
+ private static Library.Utility.IFilter? GetCommonFilter(Connection databaseConnection)
{
- var filters = FIXMEGlobal.DataConnection.Filters;
+ var filters = databaseConnection.Filters;
if (filters == null || filters.Length == 0)
return null;
@@ -1071,7 +1095,7 @@ namespace Duplicati.Server
(from n in filters
orderby n.Order
let exp = Environment.ExpandEnvironmentVariables(n.Expression)
- select (IFilter)new FilterExpression(exp, n.Include))
+ select (Library.Utility.IFilter)new FilterExpression(exp, n.Include))
.Aggregate((a, b) => FilterExpression.Combine(a, b));
}
}
diff --git a/Duplicati/Library/RestAPI/Scheduler.cs b/Duplicati/Library/RestAPI/Scheduler.cs
index 388a850b7..bc1f175cc 100644
--- a/Duplicati/Library/RestAPI/Scheduler.cs
+++ b/Duplicati/Library/RestAPI/Scheduler.cs
@@ -29,9 +29,9 @@ using System.Text;
using System.Linq;
using System.Threading;
using Duplicati.Library.Utility;
-using Duplicati.Library.RestAPI;
using System.Threading.Tasks;
using Duplicati.WebserverCore.Abstractions;
+using Duplicati.Server.Database;
// TODO: Rewrite this class.
// It should just signal what new backups to run, and not mix with the worker thread.
@@ -69,6 +69,10 @@ namespace Duplicati.Server
/// The queue runner service
///
private readonly IQueueRunnerService m_queueRunnerService;
+ ///
+ /// The data connection
+ ///
+ private readonly Connection m_dataConnection;
///
/// The currently scheduled items
@@ -83,8 +87,11 @@ namespace Duplicati.Server
///
/// Constructs a new scheduler
///
- public Scheduler(IQueueRunnerService queueRunnerService)
+ /// The data connection
+ /// The queue runner service
+ public Scheduler(Connection connection, IQueueRunnerService queueRunnerService)
{
+ m_dataConnection = connection;
m_queueRunnerService = queueRunnerService;
m_thread = new Thread(new ThreadStart(Runner));
m_schedule = [];
@@ -99,7 +106,7 @@ namespace Duplicati.Server
public IList> GetProposedSchedule()
{
return (
- from n in FIXMEGlobal.Scheduler.Schedule
+ from n in this.Schedule
let backupid = (from t in n.Value.Tags
where t != null && t.StartsWith("ID=", StringComparison.Ordinal)
select t.Substring("ID=".Length)).FirstOrDefault()
@@ -220,7 +227,7 @@ namespace Duplicati.Server
{
t.Item1.Time = t.Item2;
t.Item1.LastRun = t.Item3;
- FIXMEGlobal.DataConnection.AddOrUpdateSchedule(t.Item1);
+ m_dataConnection.AddOrUpdateSchedule(t.Item1);
}
return Task.CompletedTask;
@@ -256,8 +263,8 @@ namespace Duplicati.Server
// to avoid frequent db lookups
//Determine schedule list
- var timeZoneInfo = FIXMEGlobal.DataConnection.ApplicationSettings.Timezone;
- var lst = FIXMEGlobal.DataConnection.Schedules;
+ var timeZoneInfo = m_dataConnection.ApplicationSettings.Timezone;
+ var lst = m_dataConnection.Schedules;
foreach (var sc in lst)
{
if (!string.IsNullOrEmpty(sc.Repeat))
@@ -291,7 +298,7 @@ namespace Duplicati.Server
}
catch (Exception ex)
{
- FIXMEGlobal.DataConnection.LogError(sc.ID.ToString(), "Scheduler failed to find next date",
+ m_dataConnection.LogError(sc.ID.ToString(), "Scheduler failed to find next date",
ex);
}
@@ -300,7 +307,7 @@ namespace Duplicati.Server
{
var jobsToRun = new List();
//TODO: Cache this to avoid frequent lookups
- foreach (var id in FIXMEGlobal.DataConnection.GetBackupIDsForTags(sc.Tags).Distinct()
+ foreach (var id in m_dataConnection.GetBackupIDsForTags(sc.Tags).Distinct()
.Select(x => x.ToString()))
{
//See if it is already queued
@@ -315,11 +322,11 @@ namespace Duplicati.Server
//If it is not already in queue, put it there
if (!tmplst.Any(x => x == id))
{
- var entry = FIXMEGlobal.DataConnection.GetBackup(id);
+ var entry = m_dataConnection.GetBackup(id);
if (entry != null)
{
- var options = Server.Runner.GetCommonOptions();
- Server.Runner.ApplyOptions(entry, options);
+ var options = Server.Runner.GetCommonOptions(m_dataConnection);
+ Server.Runner.ApplyOptions(m_dataConnection, entry, options);
if (new Library.Main.Options(options).DisableOnBattery &&
(Library.Utility.Power.PowerSupply.GetSource() ==
Library.Utility.Power.PowerSupply.Source.Battery))
@@ -364,7 +371,7 @@ namespace Duplicati.Server
}
catch (Exception ex)
{
- FIXMEGlobal.DataConnection.LogError(sc.ID.ToString(),
+ m_dataConnection.LogError(sc.ID.ToString(),
"Scheduler failed to find next date", ex);
continue;
}
diff --git a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs
index cb95b681a..ccaa14a35 100644
--- a/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs
+++ b/Duplicati/Server/Duplicati.Server.Serialization/Interface/IQueuedTask.cs
@@ -53,10 +53,6 @@ public interface IQueuedTask
///
Func? OnFinished { get; set; }
- ///
- /// That action that performs the task.
- ///
- Task Execute();
///
/// Updates the throttle speeds for the task.
///
diff --git a/Duplicati/Server/Program.cs b/Duplicati/Server/Program.cs
index 933cca135..794ceb955 100644
--- a/Duplicati/Server/Program.cs
+++ b/Duplicati/Server/Program.cs
@@ -99,11 +99,6 @@ namespace Duplicati.Server
///
public static Database.Connection DataConnection { get => FIXMEGlobal.DataConnection; set => FIXMEGlobal.DataConnection = value; }
- ///
- /// This is the scheduling thread
- ///
- public static ISchedulerService Scheduler { get => FIXMEGlobal.Scheduler; }
-
///
/// The thread running the ping-pong handler
///
@@ -137,20 +132,10 @@ namespace Duplicati.Server
DuplicatiWebserver.Stop().GetAwaiter().GetResult();
}
- ///
- /// The update poll thread.
- ///
- public static UpdatePollThread UpdatePoller => FIXMEGlobal.UpdatePoller;
-
///
/// An event that is set once the server is ready to respond to requests
///
- public static readonly System.Threading.ManualResetEvent ServerStartedEvent = new System.Threading.ManualResetEvent(false);
-
- ///
- /// The status event signaler, used to control long polling of status updates
- ///
- public static EventPollNotify StatusEventNotifyer => FIXMEGlobal.Provider.GetRequiredService();
+ public static readonly ManualResetEvent ServerStartedEvent = new ManualResetEvent(false);
///
/// A delegate method for creating a copy of the current progress state
@@ -244,6 +229,9 @@ namespace Duplicati.Server
var crashed = false;
var terminated = false;
IQueueRunnerService queueRunner = null;
+ UpdatePollThread updatePollThread = null;
+ EventPollNotify eventPollNotify = null;
+ ISchedulerService scheduler = null;
try
{
DataConnection = GetDatabaseConnection(commandlineOptions, silentConsole);
@@ -267,14 +255,17 @@ namespace Duplicati.Server
DuplicatiWebserver = StartWebServer(commandlineOptions, DataConnection).Await();
- queueRunner = DuplicatiWebserver.Provider.GetRequiredService();
DataConnection.SetServiceProvider(DuplicatiWebserver.Provider);
+ queueRunner = DuplicatiWebserver.Provider.GetRequiredService();
+ updatePollThread = DuplicatiWebserver.Provider.GetRequiredService();
+ eventPollNotify = DuplicatiWebserver.Provider.GetRequiredService();
+ scheduler = DuplicatiWebserver.Provider.GetRequiredService();
- UpdatePoller.Init(Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_UPDATE_CHECK_OPTION));
+ updatePollThread.Init(Library.Utility.Utility.ParseBoolOption(commandlineOptions, DISABLE_UPDATE_CHECK_OPTION));
SetPurgeTempFilesTimer(commandlineOptions);
- LiveControl.StateChanged = (e) => { LiveControl_StateChanged(queueRunner, DataConnection, StatusEventNotifyer, e); };
+ LiveControl.StateChanged = (e) => { LiveControl_StateChanged(queueRunner, DataConnection, eventPollNotify, e); };
if (Library.Utility.Utility.ParseBoolOption(commandlineOptions, PING_PONG_KEEPALIVE_OPTION))
{
@@ -361,10 +352,10 @@ namespace Duplicati.Server
Log.WriteInformationMessage(LOGTAG, "ServerStopping", Strings.Program.ServerStopping);
var steps = new Action[] {
- () => StatusEventNotifyer.SignalNewEvent(),
- () => { if (ShutdownModernWebserver != null) ShutdownModernWebserver(); },
- () => UpdatePoller?.Terminate(),
- () => Scheduler?.Terminate(true),
+ () => eventPollNotify?.SignalNewEvent(),
+ () => ShutdownModernWebserver(),
+ () => updatePollThread?.Terminate(),
+ () => scheduler?.Terminate(true),
() => queueRunner?.Terminate(true),
() => ApplicationInstance?.Dispose(),
() => PurgeTempFilesTimer?.Dispose(),
@@ -844,11 +835,6 @@ namespace Duplicati.Server
Library.UsageReporter.Reporter.SetReportLevel(reportLevel, disableUsageReporter);
}
- private static void SignalNewEvent(object sender, EventArgs e)
- {
- StatusEventNotifyer.SignalNewEvent();
- }
-
///
/// This event handler updates the trayicon menu with the current state of the runner.
///
diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs
index acad0cfac..da2e33802 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupGet.cs
@@ -41,8 +41,8 @@ public class BackupGet : IEndpointV1
=> ExecuteGet(connection, GetBackup(connection, id)))
.RequireAuthorization();
- group.MapGet("/backup/{id}/files", ([FromServices] Connection connection, [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(GetBackup(connection, id), filter, time, allVersions ?? false, prefixOnly ?? false, folderContents ?? false, new Dictionary()))
+ group.MapGet("/backup/{id}/files", ([FromServices] Connection connection, [FromServices] EventPollNotify eventPollNotify, [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(connection, eventPollNotify, 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)
@@ -53,16 +53,16 @@ public class BackupGet : IEndpointV1
=> ExecuteGetRemotelog(connection, GetBackup(connection, id), offset, pagesize ?? 100))
.RequireAuthorization();
- group.MapGet("/backup/{id}/filesets", ([FromServices] Connection connection, [FromRoute] string id, [FromQuery(Name = "include-metadata")] bool? includeMetadata, [FromQuery(Name = "from-remote-only")] bool? fromRemoteOnly)
- => ExecuteGetFilesets(GetBackup(connection, id), includeMetadata ?? false, fromRemoteOnly ?? false))
+ group.MapGet("/backup/{id}/filesets", ([FromServices] Connection connection, [FromServices] EventPollNotify eventPollNotify, [FromRoute] string id, [FromQuery(Name = "include-metadata")] bool? includeMetadata, [FromQuery(Name = "from-remote-only")] bool? fromRemoteOnly)
+ => ExecuteGetFilesets(connection, eventPollNotify, 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)
- => ExecuteGetExportArgsOnly(GetBackup(connection, id), exportPasswords ?? false))
+ => ExecuteGetExportArgsOnly(connection, GetBackup(connection, id), exportPasswords ?? false))
.RequireAuthorization();
group.MapGet("/backup/{id}/export-cmdline", ([FromServices] Connection connection, [FromRoute] string id, [FromQuery(Name = "export-passwords")] bool? exportPasswords, [FromQuery] string? passphrase)
- => ExecuteGetExportCmdline(GetBackup(connection, id), exportPasswords ?? false))
+ => 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) =>
@@ -142,7 +142,7 @@ public class BackupGet : IEndpointV1
);
}
- private static Dictionary SearchFiles(IBackup backup, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues)
+ private static Dictionary SearchFiles(Connection connection, EventPollNotify eventPollNotify, 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");
@@ -151,7 +151,7 @@ public class BackupGet : IEndpointV1
if (!allVersions)
time = Library.Utility.Timeparser.ParseTimeInterval(timestring, DateTime.Now);
- var r = Runner.Run(Runner.CreateListTask(backup, filter == null ? null : [filter], prefixOnly, allVersions, folderContents, time), false) as Duplicati.Library.Interface.IListResults;
+ var r = Runner.Run(connection, eventPollNotify, Runner.CreateListTask(backup, filter == null ? null : [filter], prefixOnly, allVersions, folderContents, time), false) as IListResults;
if (r == null)
throw new ServerErrorException("No result from list operation");
@@ -170,8 +170,8 @@ public class BackupGet : IEndpointV1
return result;
}
- private static Dictionary ExecuteGetFiles(IBackup bk, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues)
- => SearchFiles(bk, filter, timestring, allVersions, prefixOnly, folderContents, extraValues);
+ private static Dictionary ExecuteGetFiles(Connection connection, EventPollNotify eventPollNotify, IBackup bk, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary extraValues)
+ => SearchFiles(connection, eventPollNotify, bk, filter, timestring, allVersions, prefixOnly, folderContents, extraValues);
private static List> ExecuteGetLog(Connection connection, IBackup bk, long? offset, long pagesize)
{
@@ -202,7 +202,7 @@ public class BackupGet : IEndpointV1
}
}
- private static IEnumerable ExecuteGetFilesets(IBackup bk, bool includeMetadata, bool fromRemoteOnly)
+ private static IEnumerable ExecuteGetFilesets(Connection connection, EventPollNotify eventPollNotify, IBackup bk, bool includeMetadata, bool fromRemoteOnly)
{
var extra = new Dictionary
{
@@ -218,7 +218,7 @@ public class BackupGet : IEndpointV1
try
{
- var r = Runner.Run(Runner.CreateTask(DuplicatiOperation.List, bk, extra), false) as IListResults;
+ var r = Runner.Run(connection, eventPollNotify, Runner.CreateTask(DuplicatiOperation.List, bk, extra), false) as IListResults;
if (r == null)
throw new ServerErrorException("No result from list operation");
@@ -239,20 +239,20 @@ public class BackupGet : IEndpointV1
backup.SanitizeTargetUrl();
}
- private static Dto.ExportCommandlineDto ExecuteGetExportCmdline(IBackup backup, bool exportPasswords)
+ private static Dto.ExportCommandlineDto ExecuteGetExportCmdline(Connection connection, IBackup backup, bool exportPasswords)
{
if (!exportPasswords)
RemovePasswords(backup);
- return new Dto.ExportCommandlineDto(Runner.GetCommandLine(Runner.CreateTask(DuplicatiOperation.Backup, backup)));
+ return new Dto.ExportCommandlineDto(Runner.GetCommandLine(connection, Runner.CreateTask(DuplicatiOperation.Backup, backup)));
}
- private static Dto.ExportArgsOnlyDto ExecuteGetExportArgsOnly(IBackup backup, bool exportPasswords)
+ private static Dto.ExportArgsOnlyDto ExecuteGetExportArgsOnly(Connection connection, IBackup backup, bool exportPasswords)
{
if (!exportPasswords)
RemovePasswords(backup);
- var parts = Runner.GetCommandLineParts(Runner.CreateTask(DuplicatiOperation.Backup, backup));
+ var parts = Runner.GetCommandLineParts(connection, Runner.CreateTask(DuplicatiOperation.Backup, backup));
return new Dto.ExportArgsOnlyDto(
parts.First(),
parts.Skip(1).Where(x => !x.StartsWith("--", StringComparison.Ordinal)),
diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs
index 754cab235..77e763e42 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/Backup/BackupPutDelete.cs
@@ -36,9 +36,9 @@ public class BackupPutDelete : IEndpointV1
=> ExecutePut(GetBackup(connection, id), connection, input))
.RequireAuthorization();
- group.MapDelete("/backup/{id}", ([FromServices] Connection connection, [FromServices] IQueueRunnerService queueRunnerService, [FromServices] LiveControls liveControls, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string id, [FromQuery(Name = "delete-remote-files")] bool? delete_remote_files, [FromQuery(Name = "delete-local-db")] bool? delete_local_db, [FromQuery] bool? force) =>
+ group.MapDelete("/backup/{id}", ([FromServices] Connection connection, [FromServices] ISchedulerService schedulerService, [FromServices] IQueueRunnerService queueRunnerService, [FromServices] LiveControls liveControls, [FromServices] IHttpContextAccessor httpContextAccessor, [FromRoute] string id, [FromQuery(Name = "delete-remote-files")] bool? delete_remote_files, [FromQuery(Name = "delete-local-db")] bool? delete_local_db, [FromQuery] bool? force) =>
{
- var res = ExecuteDelete(GetBackup(connection, id), queueRunnerService, liveControls, delete_remote_files ?? false, delete_local_db, force ?? false);
+ var res = ExecuteDelete(GetBackup(connection, id), schedulerService, queueRunnerService, liveControls, delete_remote_files ?? false, delete_local_db, force ?? false);
if (res.Status != "OK" && httpContextAccessor.HttpContext != null)
httpContextAccessor.HttpContext.Response.StatusCode = 500;
return res;
@@ -130,7 +130,7 @@ public class BackupPutDelete : IEndpointV1
}
}
- private static Dto.DeleteBackupOutputDto ExecuteDelete(IBackup backup, IQueueRunnerService queueRunnerService, LiveControls liveControls, bool delete_remote_files, bool? delete_local_db, bool force)
+ private static Dto.DeleteBackupOutputDto ExecuteDelete(IBackup backup, ISchedulerService schedulerService, IQueueRunnerService queueRunnerService, LiveControls liveControls, bool delete_remote_files, bool? delete_local_db, bool force)
{
try
{
@@ -179,7 +179,7 @@ public class BackupPutDelete : IEndpointV1
if (delete_remote_files)
extra["delete-remote-files"] = "true";
- return new Dto.DeleteBackupOutputDto("OK", null, queueRunnerService.AddTask(Runner.CreateTask(DuplicatiOperation.Delete, backup, extra)));
+ return new Dto.DeleteBackupOutputDto("OK", null, queueRunnerService.AddTask(Runner.CreateDeleteTask(backup, extra, (_) => schedulerService.Reschedule())));
}
}
diff --git a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs
index 8771de22d..3abd2d4ef 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/RemoteOperation.cs
@@ -22,6 +22,7 @@ using Duplicati.Library.Interface;
using Duplicati.Library.Main;
using Duplicati.Library.RestAPI;
using Duplicati.Server;
+using Duplicati.Server.Database;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Exceptions;
using Microsoft.AspNetCore.Mvc;
@@ -38,8 +39,8 @@ namespace Duplicati.WebserverCore.Endpoints.V1
=> ExecuteDbPath(input.path))
.RequireAuthorization();
- group.MapPost("/remoteoperation/test", ([FromQuery] bool? autocreate, [FromBody] RemoteOperationInput input, CancellationToken cancelToken)
- => ExecuteTest(input.path, autocreate ?? false, cancelToken))
+ group.MapPost("/remoteoperation/test", ([FromServices] Connection connection, [FromQuery] bool? autocreate, [FromBody] RemoteOperationInput input, CancellationToken cancelToken)
+ => ExecuteTest(connection, input.path, autocreate ?? false, cancelToken))
.RequireAuthorization();
group.MapPost("/remoteoperation/create", ([FromBody] RemoteOperationInput input, CancellationToken cancelToken)
@@ -53,11 +54,11 @@ namespace Duplicati.WebserverCore.Endpoints.V1
return new Dto.GetDbPathDto(!string.IsNullOrWhiteSpace(path), path);
}
- private static Dictionary ParseUrlOptions(Library.Utility.Uri uri)
+ private static Dictionary ParseUrlOptions(Connection connection, Library.Utility.Uri uri)
{
var qp = uri.QueryParameters;
- var opts = Runner.GetCommonOptions();
+ var opts = Runner.GetCommonOptions(connection);
foreach (var k in qp.Keys.Cast())
opts[k] = qp[k];
@@ -87,10 +88,10 @@ namespace Duplicati.WebserverCore.Endpoints.V1
}
}
- private static async Task GetBackend(string url, CancellationToken cancelToken)
+ private static async Task GetBackend(Connection connection, string url, CancellationToken cancelToken)
{
var uri = new Library.Utility.Uri(url);
- var opts = ParseUrlOptions(uri);
+ var opts = ParseUrlOptions(connection, uri);
var tmp = new[] { uri };
await SecretProviderHelper.ApplySecretProviderAsync([], tmp, opts, Library.Utility.TempFolder.SystemTempPath, FIXMEGlobal.SecretProvider, cancelToken);
@@ -101,13 +102,13 @@ namespace Duplicati.WebserverCore.Endpoints.V1
return new TupleDisposeWrapper(backend, modules);
}
- private static async Task ExecuteTest(string url, bool autoCreate, CancellationToken cancelToken)
+ private static async Task ExecuteTest(Connection connection, string url, bool autoCreate, CancellationToken cancelToken)
{
TupleDisposeWrapper? wrapper = null;
try
{
- wrapper = await GetBackend(url, cancelToken);
+ wrapper = await GetBackend(connection, url, cancelToken);
using (var b = wrapper.Backend)
{
diff --git a/Duplicati/WebserverCore/Endpoints/V1/Updates.cs b/Duplicati/WebserverCore/Endpoints/V1/Updates.cs
index 86e252dbf..029f60750 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/Updates.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/Updates.cs
@@ -18,8 +18,9 @@
// 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.RestAPI;
+using Duplicati.Server;
using Duplicati.WebserverCore.Abstractions;
+using Microsoft.AspNetCore.Mvc;
namespace Duplicati.WebserverCore.Endpoints.V1;
@@ -27,9 +28,11 @@ public class Updates : IEndpointV1
{
public static void Map(RouteGroupBuilder group)
{
- group.MapPost("/updates/check", Execute).RequireAuthorization();
+ group.MapPost("/updates/check",
+ ([FromServices] UpdatePollThread updatePollThread) => Execute(updatePollThread))
+ .RequireAuthorization();
}
- private static void Execute()
- => FIXMEGlobal.UpdatePoller.CheckNow();
+ private static void Execute(UpdatePollThread updatePollThread)
+ => updatePollThread.CheckNow();
}
\ No newline at end of file
diff --git a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs
index 7c35c171f..20421f853 100644
--- a/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs
+++ b/Duplicati/WebserverCore/Endpoints/V1/WebModules.cs
@@ -22,6 +22,7 @@ using Duplicati.Library.Interface;
using Duplicati.Library.Main;
using Duplicati.Library.RestAPI;
using Duplicati.Server;
+using Duplicati.Server.Database;
using Duplicati.WebserverCore.Abstractions;
using Duplicati.WebserverCore.Exceptions;
using Microsoft.AspNetCore.Mvc;
@@ -33,18 +34,20 @@ public record WebModules : IEndpointV1
public static void Map(RouteGroupBuilder group)
{
group.MapGet("/webmodules", ExecuteGet).RequireAuthorization();
- group.MapPost("/webmodule/{modulekey}", ([FromRoute] string modulekey, [FromBody] Dictionary options, CancellationToken cancellationToken) => ExecutePost(modulekey, options, cancellationToken)).RequireAuthorization();
+ group.MapPost("/webmodule/{modulekey}", ([FromServices] Connection connection, [FromRoute] string modulekey, [FromBody] Dictionary options, CancellationToken cancellationToken)
+ => ExecutePost(connection, modulekey, options, cancellationToken))
+ .RequireAuthorization();
}
private static IEnumerable ExecuteGet()
=> Library.DynamicLoader.WebLoader.Modules;
- private static async Task ExecutePost(string modulekey, Dictionary inputOptions, CancellationToken cancellationToken)
+ private static async Task ExecutePost(Connection connection, 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");
- var options = Runner.GetCommonOptions();
+ var options = Runner.GetCommonOptions(connection);
foreach (var k in inputOptions.Keys)
options[k] = inputOptions[k];
diff --git a/Duplicati/WebserverCore/Endpoints/V2/BackupListing.cs b/Duplicati/WebserverCore/Endpoints/V2/BackupListing.cs
index d013f73d5..5e301489e 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, [FromBody] Dto.V2.ListFilesetsRequestDto input)
- => ExecuteGetFilesets(GetBackup(connection, input.BackupId)))
+ group.MapPost("/backup/list-filesets", ([FromServices] Connection connection, [FromServices] EventPollNotify eventPollNotify, [FromBody] Dto.V2.ListFilesetsRequestDto input)
+ => ExecuteGetFilesets(connection, eventPollNotify, GetBackup(connection, input.BackupId)))
.RequireAuthorization();
- group.MapPost("/backup/list-folder", ([FromServices] Connection connection, [FromBody] Dto.V2.ListFolderContentRequestDto input)
- => ExecuteListFolder(GetBackup(connection, input.BackupId), input))
+ group.MapPost("/backup/list-folder", ([FromServices] Connection connection, [FromServices] EventPollNotify eventPollNotify, [FromBody] Dto.V2.ListFolderContentRequestDto input)
+ => ExecuteListFolder(connection, eventPollNotify, GetBackup(connection, input.BackupId), input))
.RequireAuthorization();
- group.MapPost("/backup/list-versions", ([FromServices] Connection connection, [FromBody] Dto.V2.ListFileVersionsRequestDto input)
- => ExecuteListVersions(GetBackup(connection, input.BackupId), input))
+ group.MapPost("/backup/list-versions", ([FromServices] Connection connection, [FromServices] EventPollNotify eventPollNotify, [FromBody] Dto.V2.ListFileVersionsRequestDto input)
+ => ExecuteListVersions(connection, eventPollNotify, GetBackup(connection, input.BackupId), input))
.RequireAuthorization();
- group.MapPost("/backup/search", ([FromServices] Connection connection, [FromBody] Dto.V2.SearchEntriesRequestDto input)
- => ExecuteSearch(GetBackup(connection, input.BackupId), input))
+ group.MapPost("/backup/search", ([FromServices] Connection connection, [FromServices] EventPollNotify eventPollNotify, [FromBody] Dto.V2.SearchEntriesRequestDto input)
+ => ExecuteSearch(connection, eventPollNotify, GetBackup(connection, input.BackupId), input))
.RequireAuthorization();
}
private static IBackup GetBackup(Connection connection, string id)
=> connection.GetBackup(id) ?? throw new NotFoundException("Backup not found");
- private static Dto.V2.ListFolderContentResponseDto ExecuteListFolder(IBackup bk, Dto.V2.ListFolderContentRequestDto input)
+ private static Dto.V2.ListFolderContentResponseDto ExecuteListFolder(Connection connection, EventPollNotify eventPollNotify, 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 = Runner.Run(Runner.CreateListFolderContents(bk, input.Paths, time, input.PageSize ?? 1000, input.Page ?? 0), false) as IListFolderResults;
+ var r = Runner.Run(connection, eventPollNotify, Runner.CreateListFolderContents(bk, input.Paths, time, input.PageSize ?? 1000, input.Page ?? 0), false) as IListFolderResults;
if (r == null)
throw new ServerErrorException("No result from list operation");
@@ -78,16 +78,16 @@ public class BackupListing : IEndpointV2
r.Entries.TotalCount);
}
- private static Dto.V2.ListFilesetsResponseDto ExecuteGetFilesets(IBackup bk)
+ private static Dto.V2.ListFilesetsResponseDto ExecuteGetFilesets(Connection connection, EventPollNotify eventPollNotify, IBackup bk)
{
- var extra = new Dictionary();
+ var extra = new Dictionary();
// Retries will hang the http request
extra["number-of-retries"] = "0";
try
{
- var r = Runner.Run(Runner.CreateListFilesetsTask(bk, extra), false) as IListFilesetResults;
+ var r = Runner.Run(connection, eventPollNotify, Runner.CreateListFilesetsTask(bk, extra), false) as IListFilesetResults;
if (r == null)
throw new ServerErrorException("No result from list operation");
@@ -112,9 +112,9 @@ public class BackupListing : IEndpointV2
}
}
- private static Dto.V2.ListFileVersionsOutputDto ExecuteListVersions(IBackup bk, Dto.V2.ListFileVersionsRequestDto input)
+ private static Dto.V2.ListFileVersionsOutputDto ExecuteListVersions(Connection connection, EventPollNotify eventPollNotify, IBackup bk, Dto.V2.ListFileVersionsRequestDto input)
{
- var r = Runner.Run(Runner.ListFileVersionsTask(bk, input.Paths, input.PageSize ?? 1000, input.Page ?? 0), false) as IListFileVersionsResults;
+ var r = Runner.Run(connection, eventPollNotify, Runner.ListFileVersionsTask(bk, input.Paths, input.PageSize ?? 1000, input.Page ?? 0), false) as IListFileVersionsResults;
if (r == null)
throw new ServerErrorException("No result from list operation");
@@ -135,13 +135,13 @@ public class BackupListing : IEndpointV2
r.FileVersions.TotalCount);
}
- private static Dto.V2.SearchEntriesResponseDto ExecuteSearch(IBackup bk, Dto.V2.SearchEntriesRequestDto input)
+ private static Dto.V2.SearchEntriesResponseDto ExecuteSearch(Connection connection, EventPollNotify eventPollNotify, 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 = Runner.Run(Runner.CreateSearchEntriesTask(bk, input.Filters, input.Paths, time, input.PageSize ?? 1000, input.Page ?? 0), false) as ISearchFilesResults;
+ var r = Runner.Run(connection, eventPollNotify, Runner.CreateSearchEntriesTask(bk, input.Filters, input.Paths, time, input.PageSize ?? 1000, input.Page ?? 0), 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 6e63db05b..c44725bf7 100644
--- a/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs
+++ b/Duplicati/WebserverCore/Endpoints/V2/DestinationVerify.cs
@@ -36,7 +36,7 @@ public class DestinationVerify : IEndpointV2
public static void Map(RouteGroupBuilder group)
{
group.MapPost("/destination/test", ([FromServices] Connection connection, [FromBody] Dto.V2.DestinationTestRequestDto input, CancellationToken cancelToken)
- => ExecuteTest(input, cancelToken))
+ => ExecuteTest(connection, input, cancelToken))
.RequireAuthorization();
}
@@ -51,11 +51,11 @@ public class DestinationVerify : IEndpointV2
}
}
- private static Dictionary ParseUrlOptions(Library.Utility.Uri uri)
+ private static Dictionary ParseUrlOptions(Connection connection, Library.Utility.Uri uri)
{
var qp = uri.QueryParameters;
- var opts = Runner.GetCommonOptions();
+ var opts = Runner.GetCommonOptions(connection);
foreach (var k in qp.Keys.Cast())
opts[k] = qp[k];
@@ -75,10 +75,10 @@ public class DestinationVerify : IEndpointV2
return modules;
}
- private static async Task GetBackend(string url, CancellationToken cancelToken)
+ private static async Task GetBackend(Connection connection, string url, CancellationToken cancelToken)
{
var uri = new Library.Utility.Uri(url);
- var opts = ParseUrlOptions(uri);
+ var opts = ParseUrlOptions(connection, uri);
var tmp = new[] { uri };
await SecretProviderHelper.ApplySecretProviderAsync([], tmp, opts, Library.Utility.TempFolder.SystemTempPath, FIXMEGlobal.SecretProvider, cancelToken);
@@ -90,13 +90,13 @@ public class DestinationVerify : IEndpointV2
}
- private static async Task ExecuteTest(DestinationTestRequestDto input, CancellationToken cancelToken)
+ private static async Task ExecuteTest(Connection connection, DestinationTestRequestDto input, CancellationToken cancelToken)
{
TupleDisposeWrapper? wrapper = null;
try
{
- wrapper = await GetBackend(input.DestinationUrl, cancelToken);
+ wrapper = await GetBackend(connection, input.DestinationUrl, cancelToken);
using (var b = wrapper.Backend)
{
diff --git a/Duplicati/WebserverCore/Services/QueueRunnerService.cs b/Duplicati/WebserverCore/Services/QueueRunnerService.cs
index 9335bcc93..4313c0814 100644
--- a/Duplicati/WebserverCore/Services/QueueRunnerService.cs
+++ b/Duplicati/WebserverCore/Services/QueueRunnerService.cs
@@ -136,7 +136,7 @@ public class QueueRunnerService(Connection connection, EventPollNotify eventPoll
if (task.OnStarting != null)
await task.OnStarting().ConfigureAwait(false);
- await task.Execute();
+ Runner.Run(connection, eventPollNotify, task, false);
// If the task is completed, don't call OnFinished again
completed = true;
diff --git a/Duplicati/WebserverCore/Services/SchedulerService.cs b/Duplicati/WebserverCore/Services/SchedulerService.cs
index b327c83ba..381fe38d9 100644
--- a/Duplicati/WebserverCore/Services/SchedulerService.cs
+++ b/Duplicati/WebserverCore/Services/SchedulerService.cs
@@ -21,6 +21,7 @@
using Duplicati.Library.RestAPI;
using Duplicati.Server;
+using Duplicati.Server.Database;
using Duplicati.Server.Serialization.Interface;
using Duplicati.WebserverCore.Abstractions;
@@ -29,9 +30,9 @@ namespace WebserverCore.Services;
public class SchedulerService : ISchedulerService
{
private readonly Scheduler scheduler;
- public SchedulerService(EventPollNotify eventPollNotify, INotificationUpdateService notificationUpdateService, IQueueRunnerService queueRunnerService)
+ public SchedulerService(Connection connection, EventPollNotify eventPollNotify, INotificationUpdateService notificationUpdateService, IQueueRunnerService queueRunnerService)
{
- this.scheduler = new Scheduler(queueRunnerService);
+ this.scheduler = new Scheduler(connection, queueRunnerService);
var lastScheduleId = notificationUpdateService.LastDataUpdateId;
eventPollNotify.NewEvent += (sender, e) =>
{