diff --git a/Duplicati/WebserverCore/Dto/V2/TempDiskSpaceRequestDto.cs b/Duplicati/WebserverCore/Dto/V2/TempDiskSpaceRequestDto.cs new file mode 100644 index 000000000..9d1d5e5b0 --- /dev/null +++ b/Duplicati/WebserverCore/Dto/V2/TempDiskSpaceRequestDto.cs @@ -0,0 +1,32 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.WebserverCore.Dto.V2; + +/// +/// The temporary disk space request DTO +/// +public sealed record TempDiskSpaceRequestDto +{ + /// + /// The backup ID, if known. If provided, the system will check for a temp folder override in the backup settings. + /// + public string? BackupId { get; init; } +} diff --git a/Duplicati/WebserverCore/Dto/V2/TempDiskSpaceResponseDto.cs b/Duplicati/WebserverCore/Dto/V2/TempDiskSpaceResponseDto.cs new file mode 100644 index 000000000..435350dd4 --- /dev/null +++ b/Duplicati/WebserverCore/Dto/V2/TempDiskSpaceResponseDto.cs @@ -0,0 +1,100 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +namespace Duplicati.WebserverCore.Dto.V2; + +/// +/// The temporary disk space response DTO +/// +public sealed record TempDiskSpaceResponseDto : ResponseEnvelope +{ + /// + /// Creates a success response with the temp disk space information + /// + /// The path to the temporary folder + /// The free space in bytes + /// The total space in bytes + /// The response DTO + public static TempDiskSpaceResponseDto Create(string tempPath, long? freeSpace, long? totalSpace, long dblockSize, long restoreCacheMax) + { + return new TempDiskSpaceResponseDto() + { + Success = true, + Error = null, + StatusCode = "OK", + Data = new TempDiskSpaceResult() + { + TempPath = tempPath, + FreeSpace = freeSpace, + TotalSpace = totalSpace, + DblockSize = dblockSize, + RestoreCacheMax = restoreCacheMax + } + }; + } + + /// + /// Creates a failure response + /// + /// The error message + /// The status code + /// The response DTO + public static new TempDiskSpaceResponseDto Failure(string error, string statusCode) + { + return new TempDiskSpaceResponseDto() + { + Success = false, + Error = error, + StatusCode = statusCode, + Data = null + }; + } +} + +/// +/// The temporary disk space result +/// +public sealed record TempDiskSpaceResult +{ + /// + /// The path to the temporary folder that was checked + /// + public required string TempPath { get; init; } + + /// + /// The free space available in bytes, or null if it could not be determined + /// + public required long? FreeSpace { get; init; } + + /// + /// The total space of the drive in bytes, or null if it could not be determined + /// + public required long? TotalSpace { get; init; } + + /// + /// The dblock-size setting in bytes + /// + public required long DblockSize { get; init; } + + /// + /// The restore-cache-max setting in bytes + /// + public required long RestoreCacheMax { get; init; } +} diff --git a/Duplicati/WebserverCore/Endpoints/V2/TempDiskSpace.cs b/Duplicati/WebserverCore/Endpoints/V2/TempDiskSpace.cs new file mode 100644 index 000000000..efe474b1a --- /dev/null +++ b/Duplicati/WebserverCore/Endpoints/V2/TempDiskSpace.cs @@ -0,0 +1,147 @@ +// Copyright (C) 2025, The Duplicati Team +// https://duplicati.com, hello@duplicati.com +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +using Duplicati.Library.Utility; +using Duplicati.Server.Database; +using Duplicati.WebserverCore.Abstractions; +using Duplicati.WebserverCore.Dto.V2; +using Microsoft.AspNetCore.Mvc; + +namespace Duplicati.WebserverCore.Endpoints.V2; + +/// +/// Endpoint for checking temporary disk space +/// +public class TempDiskSpace : IEndpointV2 +{ + /// + /// Maps the endpoint routes + /// + /// The route group builder + public static void Map(RouteGroupBuilder group) + { + group.MapPost("/system/temp-disk-space", ([FromServices] Connection connection, [FromBody] TempDiskSpaceRequestDto input) + => Execute(connection, input)) + .RequireAuthorization(); + } + + /// + /// Executes the temp disk space check + /// + /// The database connection + /// The request input + /// The response DTO + private static TempDiskSpaceResponseDto Execute(Connection connection, TempDiskSpaceRequestDto input) + { + try + { + // Get the merged options for the backup (or defaults) + var options = GetMergedOptions(connection, input.BackupId); + + // Determine the temp folder path from the merged options + var tempPath = GetTempPath(options); + + // Get the free space for the temp path + var spaceInfo = Utility.GetFreeSpaceForPath(tempPath); + + // Get the default options + var defaultOptions = new Library.Main.Options(new Dictionary()); + + // Get dblock-size and restore-cache-max from the options + var dblockSize = Sizeparser.ParseSize( + options.TryGetValue("dblock-size", out var dblockVal) && !string.IsNullOrWhiteSpace(dblockVal) + ? dblockVal + : $"{defaultOptions.VolumeSize}b", + "mb"); + + var restoreCacheMax = Sizeparser.ParseSize( + options.TryGetValue("restore-cache-max", out var cacheVal) && !string.IsNullOrWhiteSpace(cacheVal) + ? cacheVal + : $"{defaultOptions.RestoreCacheMax}b", + "mb"); + + return TempDiskSpaceResponseDto.Create( + tempPath: tempPath, + freeSpace: spaceInfo?.FreeSpace, + totalSpace: spaceInfo?.TotalSpace, + dblockSize: dblockSize, + restoreCacheMax: restoreCacheMax + ); + } + catch (Exception ex) + { + return TempDiskSpaceResponseDto.Failure( + error: ex.Message, + statusCode: "error-checking-temp-space" + ); + } + } + + /// + /// Gets the merged options dictionary, combining application-level settings + /// with backup-specific settings (if a backupId is provided). + /// + /// The database connection + /// The optional backup ID + /// The merged options dictionary + private static Dictionary GetMergedOptions(Connection connection, string? backupId) + { + // Start with application-level settings (ANY_BACKUP_ID) + var options = connection.Settings + .GroupBy( + k => k.Name.StartsWith("--", StringComparison.Ordinal) ? k.Name.Substring(2) : k.Name, + k => (string?)k.Value + ) + .ToDictionary(g => g.Key, g => g.FirstOrDefault()); + + // If a backupId is provided, overlay the backup-specific settings + if (!string.IsNullOrWhiteSpace(backupId)) + { + var backup = connection.GetBackup(backupId); + if (backup?.Settings != null) + { + foreach (var setting in backup.Settings) + { + var name = setting.Name.StartsWith("--", StringComparison.Ordinal) + ? setting.Name.Substring(2) + : setting.Name; + options[name] = setting.Value; + } + } + } + + return options; + } + + /// + /// Gets the temporary folder path from the merged options. + /// Falls back to the system default temp path if no override is configured. + /// + /// The merged options dictionary + /// The temp folder path + private static string GetTempPath(Dictionary options) + { + if (options.TryGetValue("tempdir", out var tempDir) && !string.IsNullOrWhiteSpace(tempDir)) + return tempDir; + + return TempFolder.SystemTempPath; + } +} diff --git a/Duplicati/WebserverCore/Services/SystemInfoProvider.cs b/Duplicati/WebserverCore/Services/SystemInfoProvider.cs index 999717d7a..af7047d9e 100644 --- a/Duplicati/WebserverCore/Services/SystemInfoProvider.cs +++ b/Duplicati/WebserverCore/Services/SystemInfoProvider.cs @@ -56,6 +56,7 @@ public class SystemInfoProvider(IApplicationSettings applicationSettings, Connec "v1:subscribe:taskqueue", "v1:subscribe:taskcompleted", "v1:subscribe:notifications", + "v2:system:temp-disk-space", // "v1:subscribe:scheduler", ];