More FIXMEGlobal cleanup

This commit is contained in:
Kenneth Skovhede
2025-05-03 15:54:23 +02:00
parent 072f89c747
commit 16acb282c9
14 changed files with 208 additions and 204 deletions
+1 -18
View File
@@ -48,12 +48,7 @@ namespace Duplicati.Library.RestAPI
/// <summary>
/// A delegate method for creating a copy of the current progress state
/// </summary>
public static Func<Duplicati.Server.Serialization.Interface.IProgressEventData> GenerateProgressState;
/// <summary>
/// The status event signaler, used to control long polling of status updates
/// </summary>
public static EventPollNotify StatusEventNotifyer => Provider.GetRequiredService<EventPollNotify>();
public static Func<Server.Serialization.Interface.IProgressEventData> GenerateProgressState;
/// <summary>
/// For keeping and incrementing last last events Ids of db save and last notification
@@ -71,28 +66,16 @@ namespace Duplicati.Library.RestAPI
/// </summary>
public static string DataFolder;
/// <summary>
/// This is the scheduling thread
/// </summary>
public static ISchedulerService Scheduler => Provider.GetRequiredService<ISchedulerService>();
/// <summary>
/// The log redirect handler
/// </summary>
public static readonly LogWriteHandler LogHandler = new LogWriteHandler();
/// <summary>
/// The update poll thread.
/// </summary>
public static UpdatePollThread UpdatePoller => Provider.GetRequiredService<UpdatePollThread>();
/// <summary>
/// Used to check the origin of the web server (e.g. Tray icon or a stand alone Server)
/// </summary>
public static string Origin = "Server";
/// <summary>
/// The application exit event
/// </summary>
+104 -80
View File
@@ -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<Task>? OnStarting { get; set; }
public Func<Exception?, Task>? OnFinished { get; set; }
/// <summary>
/// Callback to be executed when the task is finished.
/// Only supported for the delete operation for now.
/// </summary>
internal Action<IRunnerData>? AfterTaskFinished { get; set; }
public DuplicatiOperation Operation { get; internal set; }
public Serialization.Interface.IBackup? Backup { get; internal set; }
public IDictionary<string, string?>? 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<string, string?>? extraOptions = null, string[]? filterStrings = null, string[]? extraArguments = null, int pageSize = 0, int pageOffset = 0)
public static IRunnerData CreateTask(DuplicatiOperation operation, IBackup backup, IDictionary<string, string?>? 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<string, string?> extraOptions, Action<IRunnerData>? 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<string, string?>();
if (onlyPrefix)
@@ -199,7 +215,7 @@ namespace Duplicati.Server
filters);
}
public static IRunnerData CreateListFilesetsTask(Serialization.Interface.IBackup backup, Dictionary<string, string?>? extraOptions = null)
public static IRunnerData CreateListFilesetsTask(IBackup backup, Dictionary<string, string?>? extraOptions = null)
{
return CreateTask(
DuplicatiOperation.ListFilesets,
@@ -207,7 +223,7 @@ namespace Duplicati.Server
extraOptions ?? new Dictionary<string, string?>());
}
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<string, string?>();
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<string, string?>();
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<string, string?>();
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<string, string?> options)
private static TempFolder? StoreTaskConfigAndGetTempFolder(Connection databaseConnection, IRunnerData data, Dictionary<string, string?> 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<string, string?> ApplyOptions(Serialization.Interface.IBackup backup, Dictionary<string, string?> options)
internal static Dictionary<string, string?> ApplyOptions(Connection databaseConnection, Serialization.Interface.IBackup backup, Dictionary<string, string?> 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<string, string?> GetCommonOptions()
public static Dictionary<string, string?> 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));
}
}
+19 -12
View File
@@ -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
/// </summary>
private readonly IQueueRunnerService m_queueRunnerService;
/// <summary>
/// The data connection
/// </summary>
private readonly Connection m_dataConnection;
/// <summary>
/// The currently scheduled items
@@ -83,8 +87,11 @@ namespace Duplicati.Server
/// <summary>
/// Constructs a new scheduler
/// </summary>
public Scheduler(IQueueRunnerService queueRunnerService)
/// <param name="connection">The data connection</param>
/// <param name="queueRunnerService">The queue runner service</param>
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<Tuple<string, DateTime>> 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<Server.Runner.IRunnerData>();
//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;
}
@@ -53,10 +53,6 @@ public interface IQueuedTask
/// </summary>
Func<Exception?, Task>? OnFinished { get; set; }
/// <summary>
/// That action that performs the task.
/// </summary>
Task Execute();
/// <summary>
/// Updates the throttle speeds for the task.
/// </summary>
+14 -28
View File
@@ -99,11 +99,6 @@ namespace Duplicati.Server
/// </summary>
public static Database.Connection DataConnection { get => FIXMEGlobal.DataConnection; set => FIXMEGlobal.DataConnection = value; }
/// <summary>
/// This is the scheduling thread
/// </summary>
public static ISchedulerService Scheduler { get => FIXMEGlobal.Scheduler; }
/// <summary>
/// The thread running the ping-pong handler
/// </summary>
@@ -137,20 +132,10 @@ namespace Duplicati.Server
DuplicatiWebserver.Stop().GetAwaiter().GetResult();
}
/// <summary>
/// The update poll thread.
/// </summary>
public static UpdatePollThread UpdatePoller => FIXMEGlobal.UpdatePoller;
/// <summary>
/// An event that is set once the server is ready to respond to requests
/// </summary>
public static readonly System.Threading.ManualResetEvent ServerStartedEvent = new System.Threading.ManualResetEvent(false);
/// <summary>
/// The status event signaler, used to control long polling of status updates
/// </summary>
public static EventPollNotify StatusEventNotifyer => FIXMEGlobal.Provider.GetRequiredService<EventPollNotify>();
public static readonly ManualResetEvent ServerStartedEvent = new ManualResetEvent(false);
/// <summary>
/// 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<IQueueRunnerService>();
DataConnection.SetServiceProvider(DuplicatiWebserver.Provider);
queueRunner = DuplicatiWebserver.Provider.GetRequiredService<IQueueRunnerService>();
updatePollThread = DuplicatiWebserver.Provider.GetRequiredService<UpdatePollThread>();
eventPollNotify = DuplicatiWebserver.Provider.GetRequiredService<EventPollNotify>();
scheduler = DuplicatiWebserver.Provider.GetRequiredService<ISchedulerService>();
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();
}
/// <summary>
/// This event handler updates the trayicon menu with the current state of the runner.
/// </summary>
@@ -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<string, string>()))
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<string, string>()))
.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<string, object> SearchFiles(IBackup backup, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary<string, string> extraValues)
private static Dictionary<string, object> SearchFiles(Connection connection, EventPollNotify eventPollNotify, IBackup backup, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary<string, string> 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<string, object> ExecuteGetFiles(IBackup bk, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary<string, string> extraValues)
=> SearchFiles(bk, filter, timestring, allVersions, prefixOnly, folderContents, extraValues);
private static Dictionary<string, object> ExecuteGetFiles(Connection connection, EventPollNotify eventPollNotify, IBackup bk, string? filter, string? timestring, bool allVersions, bool prefixOnly, bool folderContents, Dictionary<string, string> extraValues)
=> SearchFiles(connection, eventPollNotify, bk, filter, timestring, allVersions, prefixOnly, folderContents, extraValues);
private static List<Dictionary<string, object>> ExecuteGetLog(Connection connection, IBackup bk, long? offset, long pagesize)
{
@@ -202,7 +202,7 @@ public class BackupGet : IEndpointV1
}
}
private static IEnumerable<IListResultFileset> ExecuteGetFilesets(IBackup bk, bool includeMetadata, bool fromRemoteOnly)
private static IEnumerable<IListResultFileset> ExecuteGetFilesets(Connection connection, EventPollNotify eventPollNotify, IBackup bk, bool includeMetadata, bool fromRemoteOnly)
{
var extra = new Dictionary<string, string?>
{
@@ -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)),
@@ -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())));
}
}
@@ -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<string, string?> ParseUrlOptions(Library.Utility.Uri uri)
private static Dictionary<string, string?> 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<string>())
opts[k] = qp[k];
@@ -87,10 +88,10 @@ namespace Duplicati.WebserverCore.Endpoints.V1
}
}
private static async Task<TupleDisposeWrapper> GetBackend(string url, CancellationToken cancelToken)
private static async Task<TupleDisposeWrapper> 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)
{
@@ -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();
}
@@ -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<string, string> options, CancellationToken cancellationToken) => ExecutePost(modulekey, options, cancellationToken)).RequireAuthorization();
group.MapPost("/webmodule/{modulekey}", ([FromServices] Connection connection, [FromRoute] string modulekey, [FromBody] Dictionary<string, string> options, CancellationToken cancellationToken)
=> ExecutePost(connection, modulekey, options, cancellationToken))
.RequireAuthorization();
}
private static IEnumerable<IWebModule> ExecuteGet()
=> Library.DynamicLoader.WebLoader.Modules;
private static async Task<Dto.WebModuleOutputDto> ExecutePost(string modulekey, Dictionary<string, string> inputOptions, CancellationToken cancellationToken)
private static async Task<Dto.WebModuleOutputDto> ExecutePost(Connection connection, string modulekey, Dictionary<string, string> 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];
@@ -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<string, string>();
var extra = new Dictionary<string, string?>();
// 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");
@@ -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<string, string?> ParseUrlOptions(Library.Utility.Uri uri)
private static Dictionary<string, string?> 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<string>())
opts[k] = qp[k];
@@ -75,10 +75,10 @@ public class DestinationVerify : IEndpointV2
return modules;
}
private static async Task<TupleDisposeWrapper> GetBackend(string url, CancellationToken cancelToken)
private static async Task<TupleDisposeWrapper> 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<DestinationTestResponseDto> ExecuteTest(DestinationTestRequestDto input, CancellationToken cancelToken)
private static async Task<DestinationTestResponseDto> 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)
{
@@ -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;
@@ -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) =>
{