From 2b06faaf4fb00a534a81bee34865a1cb9c36bb86 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Wed, 26 Mar 2025 15:58:06 +0100 Subject: [PATCH] Added simple sorting support to backend --- .../RestAPI/Database/ServerSettings.cs | 16 ++++ .../WebserverCore/Endpoints/V1/Backups.cs | 74 +++++++++++++++++-- 2 files changed, 85 insertions(+), 5 deletions(-) diff --git a/Duplicati/Library/RestAPI/Database/ServerSettings.cs b/Duplicati/Library/RestAPI/Database/ServerSettings.cs index 4b002fee8..dea19e8ec 100644 --- a/Duplicati/Library/RestAPI/Database/ServerSettings.cs +++ b/Duplicati/Library/RestAPI/Database/ServerSettings.cs @@ -73,6 +73,7 @@ namespace Duplicati.Server.Database public const string PAUSED_UNTIL = "paused-until"; public const string LAST_UPDATE_CHECK_VERSION = "last-update-check-version"; public const string ADDITIONAL_REPORT_URL = "additional-report-url"; + public const string BACKUP_LIST_SORT_ORDER = "backup-list-sort-order"; } private readonly Dictionary settings; @@ -874,6 +875,21 @@ namespace Duplicati.Server.Database SaveSettings(); } } + + public string? BackupListSortOrder + { + get + { + return settings[CONST.BACKUP_LIST_SORT_ORDER]; + } + set + { + lock (databaseConnection.m_lock) + settings[CONST.BACKUP_LIST_SORT_ORDER] = value; + SaveSettings(); + } + } + } } diff --git a/Duplicati/WebserverCore/Endpoints/V1/Backups.cs b/Duplicati/WebserverCore/Endpoints/V1/Backups.cs index 84783a1f3..4a6a94765 100644 --- a/Duplicati/WebserverCore/Endpoints/V1/Backups.cs +++ b/Duplicati/WebserverCore/Endpoints/V1/Backups.cs @@ -30,8 +30,8 @@ public class Backups : IEndpointV1 { public static void Map(RouteGroupBuilder group) { - group.MapGet("/backups", ([FromServices] Connection connection) - => ExecuteGet(connection)) + group.MapGet("/backups", ([FromServices] Connection connection, [FromQuery] string? sort = null) + => ExecuteGet(connection, sort)) .RequireAuthorization(); group.MapPost("/backups", ([FromServices] Connection connection, [FromBody] Dto.BackupAndScheduleInputDto input, [FromQuery] bool? temporary, [FromQuery] bool? existingdb) @@ -48,10 +48,62 @@ public class Backups : IEndpointV1 }).RequireAuthorization(); } - private static IEnumerable ExecuteGet(Connection connection) + private static IEnumerable ExecuteGet(Connection connection, string? sort) { var schedules = connection.Schedules; - var backups = connection.Backups; + var backups = connection.Backups.AsEnumerable(); + + IEnumerable ApplySorting(IEnumerable backups, string sort) + { + var asc = true; + if (sort.StartsWith("-")) + { + asc = false; + sort = sort.Substring(1); + } + else if (sort.StartsWith("+")) + { + asc = true; + sort = sort.Substring(1); + } + + Func? selector = sort switch + { + "name" => x => x.Backup.Name, + "id" => x => x.Backup.ID, + "lastrun" => x => + { + if (x.Backup.Metadata == null) + return null; + x.Backup.Metadata.TryGetValue("LastSuccessfulRun", out var res); + return res; + } + , + "nextrun" => x => x.Schedule?.Time, + "schedule" => x => string.IsNullOrWhiteSpace(x.Schedule?.Repeat), + "destination" => x => Library.Utility.Utility.GuessScheme(x.Backup.TargetURL), + _ => null + }; + + // Ignore unknown sort fields + if (selector != null) + { + if (backups is IOrderedEnumerable backupsOrdered) + { + return asc + ? backupsOrdered.ThenBy(selector) + : backupsOrdered.ThenByDescending(selector); + } + else + { + return asc + ? backups.OrderBy(selector) + : backups.OrderByDescending(selector); + } + } + + return backups; + } var all = backups.Select(n => new { @@ -60,7 +112,7 @@ public class Backups : IEndpointV1 Schedule = schedules.FirstOrDefault(x => x.Tags != null && x.Tags.Contains("ID=" + n.ID)) }); - return all.Select(x => new Dto.BackupAndScheduleOutputDto() + var res = all.Select(x => new Dto.BackupAndScheduleOutputDto() { Backup = new Dto.BackupDto() { @@ -99,7 +151,19 @@ public class Backups : IEndpointV1 AllowedDays = x.Schedule.AllowedDays } }); + + // Use DB setting if not set + if (string.IsNullOrWhiteSpace(sort)) + sort = connection.ApplicationSettings.BackupListSortOrder; + + // Apply sorting, if any + if (!string.IsNullOrWhiteSpace(sort)) + foreach (var direction in sort.Split(",")) + res = ApplySorting(res, direction); + + return res; } + private static Dto.ImportBackupOutputDto ExecuteImport(Connection connection, bool cmdline, bool import_metadata, bool direct, string passphrase, string tempfile) { try