Add temp disk space check endpoint
Add a new V2 API endpoint POST `/system/temp-disk-space` that reports free and total disk space for the configured temporary folder. The endpoint accepts an optional backup ID to resolve backup-specific tempdir overrides, and returns the resolved temp path, free/total space, `dblock-size`, and `restore-cache-max values`.
This commit is contained in:
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The temporary disk space request DTO
|
||||
/// </summary>
|
||||
public sealed record TempDiskSpaceRequestDto
|
||||
{
|
||||
/// <summary>
|
||||
/// The backup ID, if known. If provided, the system will check for a temp folder override in the backup settings.
|
||||
/// </summary>
|
||||
public string? BackupId { get; init; }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// The temporary disk space response DTO
|
||||
/// </summary>
|
||||
public sealed record TempDiskSpaceResponseDto : ResponseEnvelope<TempDiskSpaceResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a success response with the temp disk space information
|
||||
/// </summary>
|
||||
/// <param name="tempPath">The path to the temporary folder</param>
|
||||
/// <param name="freeSpace">The free space in bytes</param>
|
||||
/// <param name="totalSpace">The total space in bytes</param>
|
||||
/// <returns>The response DTO</returns>
|
||||
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
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a failure response
|
||||
/// </summary>
|
||||
/// <param name="error">The error message</param>
|
||||
/// <param name="statusCode">The status code</param>
|
||||
/// <returns>The response DTO</returns>
|
||||
public static new TempDiskSpaceResponseDto Failure(string error, string statusCode)
|
||||
{
|
||||
return new TempDiskSpaceResponseDto()
|
||||
{
|
||||
Success = false,
|
||||
Error = error,
|
||||
StatusCode = statusCode,
|
||||
Data = null
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The temporary disk space result
|
||||
/// </summary>
|
||||
public sealed record TempDiskSpaceResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to the temporary folder that was checked
|
||||
/// </summary>
|
||||
public required string TempPath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The free space available in bytes, or null if it could not be determined
|
||||
/// </summary>
|
||||
public required long? FreeSpace { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The total space of the drive in bytes, or null if it could not be determined
|
||||
/// </summary>
|
||||
public required long? TotalSpace { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The dblock-size setting in bytes
|
||||
/// </summary>
|
||||
public required long DblockSize { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The restore-cache-max setting in bytes
|
||||
/// </summary>
|
||||
public required long RestoreCacheMax { get; init; }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Endpoint for checking temporary disk space
|
||||
/// </summary>
|
||||
public class TempDiskSpace : IEndpointV2
|
||||
{
|
||||
/// <summary>
|
||||
/// Maps the endpoint routes
|
||||
/// </summary>
|
||||
/// <param name="group">The route group builder</param>
|
||||
public static void Map(RouteGroupBuilder group)
|
||||
{
|
||||
group.MapPost("/system/temp-disk-space", ([FromServices] Connection connection, [FromBody] TempDiskSpaceRequestDto input)
|
||||
=> Execute(connection, input))
|
||||
.RequireAuthorization();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes the temp disk space check
|
||||
/// </summary>
|
||||
/// <param name="connection">The database connection</param>
|
||||
/// <param name="input">The request input</param>
|
||||
/// <returns>The response DTO</returns>
|
||||
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<string, string?>());
|
||||
|
||||
// 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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the merged options dictionary, combining application-level settings
|
||||
/// with backup-specific settings (if a backupId is provided).
|
||||
/// </summary>
|
||||
/// <param name="connection">The database connection</param>
|
||||
/// <param name="backupId">The optional backup ID</param>
|
||||
/// <returns>The merged options dictionary</returns>
|
||||
private static Dictionary<string, string?> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the temporary folder path from the merged options.
|
||||
/// Falls back to the system default temp path if no override is configured.
|
||||
/// </summary>
|
||||
/// <param name="options">The merged options dictionary</param>
|
||||
/// <returns>The temp folder path</returns>
|
||||
private static string GetTempPath(Dictionary<string, string?> options)
|
||||
{
|
||||
if (options.TryGetValue("tempdir", out var tempDir) && !string.IsNullOrWhiteSpace(tempDir))
|
||||
return tempDir;
|
||||
|
||||
return TempFolder.SystemTempPath;
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user