Merge pull request #6429 from marceloduplicati/feature/adding-api-client
Adds DuplicatiServerClient for API interaction
This commit is contained in:
+8
-1
@@ -1,4 +1,4 @@
|
||||
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.1.32210.238
|
||||
@@ -192,6 +192,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.WindowsMo
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.WindowsModulesLoader", "Executables\net8\Duplicati.WindowsModulesLoader\Duplicati.WindowsModulesLoader.csproj", "{006167A3-64C4-40BD-AAE4-62E911ED8CBB}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebserverCore.Client.UsageExample", "WebserverCore.Client.UsageExample\WebserverCore.Client.UsageExample.csproj", "{58EF2528-EA65-4940-8AFE-16D0C6E0E8BB}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -534,6 +536,10 @@ Global
|
||||
{15315ED8-1F67-478B-AFAD-59D9E5760705}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{15315ED8-1F67-478B-AFAD-59D9E5760705}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{15315ED8-1F67-478B-AFAD-59D9E5760705}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{58EF2528-EA65-4940-8AFE-16D0C6E0E8BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{58EF2528-EA65-4940-8AFE-16D0C6E0E8BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{58EF2528-EA65-4940-8AFE-16D0C6E0E8BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{58EF2528-EA65-4940-8AFE-16D0C6E0E8BB}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{4F0613D6-9F06-41D8-B7E8-DEEFB88DD001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{4F0613D6-9F06-41D8-B7E8-DEEFB88DD001}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{4F0613D6-9F06-41D8-B7E8-DEEFB88DD001}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
@@ -611,6 +617,7 @@ Global
|
||||
{8DFF553E-9D2B-4E32-BE3A-74F476159580} = {566EBBDA-19A4-4056-A615-D901D57D2439}
|
||||
{F760DBF2-6D4A-4934-A56C-4C0CA6758DE6} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
|
||||
{15315ED8-1F67-478B-AFAD-59D9E5760705} = {D19A38DD-68F1-4EF5-BF5F-8966CE0D9A5B}
|
||||
{58EF2528-EA65-4940-8AFE-16D0C6E0E8BB} = {15388C37-9218-4818-972E-738EEA8F1602}
|
||||
{006167A3-64C4-40BD-AAE4-62E911ED8CBB} = {6B46F6B1-1898-49B8-ADA7-5CAF68EB77E3}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
|
||||
@@ -0,0 +1,884 @@
|
||||
// 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 System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Duplicati.WebserverCore.Dto;
|
||||
using Duplicati.WebserverCore.Dto.V2;
|
||||
using Duplicati.WebserverCore.Endpoints.V1.Backup;
|
||||
|
||||
namespace Duplicati.WebserverCore.Client;
|
||||
|
||||
/// <summary>
|
||||
/// A client for interacting with the Duplicati server API, supporting both v1 and v2 endpoints.
|
||||
/// </summary>
|
||||
public class DuplicatiServerClient : IDisposable
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly string _baseUrl;
|
||||
private readonly JsonSerializerOptions _jsonOptions;
|
||||
private bool _disposed;
|
||||
private readonly bool _selfOwnedHttpClient;
|
||||
private readonly SemaphoreSlim _tokenRefreshSemaphore = new(1, 1);
|
||||
private readonly ServerCredentialType _credentialType;
|
||||
private readonly string _credential;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the client is authenticated.
|
||||
/// </summary>
|
||||
public bool IsAuthenticated => !string.IsNullOrEmpty(_httpClient.DefaultRequestHeaders.Authorization?.Parameter);
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DuplicatiServerClient"/> class.
|
||||
/// </summary>
|
||||
/// <param name="baseUrl">The base URL of the Duplicati server.</param>
|
||||
/// <param name="credentialType">The type of credential being provided (Password or Token).</param>
|
||||
/// <param name="credential">The server password or access token.</param>
|
||||
/// <param name="httpClient">Optional HttpClient instance. If not provided, a new one will be created.</param>
|
||||
public DuplicatiServerClient(string baseUrl, ServerCredentialType credentialType, string credential, HttpClient? httpClient = null)
|
||||
{
|
||||
_selfOwnedHttpClient = httpClient is not null;
|
||||
_baseUrl = baseUrl.TrimEnd('/');
|
||||
_httpClient = httpClient ?? new HttpClient();
|
||||
_jsonOptions = new JsonSerializerOptions
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
WriteIndented = true,
|
||||
AllowTrailingCommas = true
|
||||
};
|
||||
_credential = credential;
|
||||
_credentialType = credentialType;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the client with the Duplicati server using the provided credentials and acquires a bearer token.
|
||||
///
|
||||
/// If a call is made to another method before calling Authenticate, it will automatically call this method to ensure the client is authenticated.
|
||||
/// The idea of having a separate Authenticate method is to allow for explicit authentication to avoid getting a 401 result on the server log.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
public async Task Authenticate(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await RefreshTokenAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a bearer token using password-based authentication.
|
||||
/// </summary>
|
||||
/// <param name="password">The server password.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
private async Task AcquireBearerViaPasswordAuthAsync(string password, CancellationToken cancellationToken)
|
||||
{
|
||||
var loginResult = await LoginV1Async(new LoginInputDto(password, true), cancellationToken).ConfigureAwait(false);
|
||||
SetTokenAuthentication(loginResult.AccessToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Acquires a bearer token using token-based authentication.
|
||||
/// </summary>
|
||||
/// <param name="token">The signin token.</param>
|
||||
/// <param name="cancellationToken">The cancellation token.</param>
|
||||
private async Task AcquireBearerViaTokenAuthAsync(string token, CancellationToken cancellationToken)
|
||||
{
|
||||
var signinResult = await SigninV1Async(new SigninInputDto(token, true), cancellationToken).ConfigureAwait(false);
|
||||
if (!string.IsNullOrEmpty(signinResult.AccessToken))
|
||||
SetTokenAuthentication(signinResult.AccessToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets token-based authentication.
|
||||
/// </summary>
|
||||
/// <param name="token">The authentication token.</param>
|
||||
public void SetTokenAuthentication(string token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
throw new ArgumentException("Token cannot be null or empty");
|
||||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes an HTTP GET request to the specified endpoint.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the response data.</typeparam>
|
||||
/// <param name="endpoint">The API endpoint.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <param name="retryOn401">Whether to retry on 401 unauthorized responses.</param>
|
||||
/// <returns>The response data.</returns>
|
||||
private async Task<T> GetAsync<T>(string endpoint, CancellationToken cancellationToken = default, bool retryOn401 = true)
|
||||
{
|
||||
return await ExecuteWithRetryAsync<T>(async () =>
|
||||
{
|
||||
using var response = await _httpClient.GetAsync($"{_baseUrl}{endpoint}", cancellationToken).ConfigureAwait(false);
|
||||
return await ProcessResponseAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||
}, retryOn401, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes an HTTP POST request to the specified endpoint.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the response data.</typeparam>
|
||||
/// <param name="endpoint">The API endpoint.</param>
|
||||
/// <param name="data">The request data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <param name="retryOn401">Whether to retry on 401 unauthorized responses.</param>
|
||||
/// <returns>The response data.</returns>
|
||||
private async Task<T> PostAsync<T>(string endpoint, object? data, CancellationToken cancellationToken = default, bool retryOn401 = true)
|
||||
{
|
||||
return await ExecuteWithRetryAsync<T>(async () =>
|
||||
{
|
||||
var json = data != null ? JsonSerializer.Serialize(data, _jsonOptions) : string.Empty;
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using var response = await _httpClient.PostAsync($"{_baseUrl}{endpoint}", content, cancellationToken).ConfigureAwait(false);
|
||||
return await ProcessResponseAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||
}, retryOn401, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes an HTTP PUT request to the specified endpoint.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the response data.</typeparam>
|
||||
/// <param name="endpoint">The API endpoint.</param>
|
||||
/// <param name="data">The request data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <param name="retryOn401">Whether to retry on 401 unauthorized responses.</param>
|
||||
/// <returns>The response data.</returns>
|
||||
private async Task<T> PutAsync<T>(string endpoint, object? data, CancellationToken cancellationToken = default, bool retryOn401 = true)
|
||||
{
|
||||
return await ExecuteWithRetryAsync<T>(async () =>
|
||||
{
|
||||
var json = data != null ? JsonSerializer.Serialize(data, _jsonOptions) : string.Empty;
|
||||
using var content = new StringContent(json, Encoding.UTF8, "application/json");
|
||||
using var response = await _httpClient.PutAsync($"{_baseUrl}{endpoint}", content, cancellationToken).ConfigureAwait(false);
|
||||
return await ProcessResponseAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||
}, retryOn401, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes an HTTP DELETE request to the specified endpoint.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the response data.</typeparam>
|
||||
/// <param name="endpoint">The API endpoint.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <param name="retryOn401">Whether to retry on 401 unauthorized responses.</param>
|
||||
/// <returns>The response data.</returns>
|
||||
private async Task<T> DeleteAsync<T>(string endpoint, CancellationToken cancellationToken = default, bool retryOn401 = true)
|
||||
{
|
||||
return await ExecuteWithRetryAsync<T>(async () =>
|
||||
{
|
||||
using var response = await _httpClient.DeleteAsync($"{_baseUrl}{endpoint}", cancellationToken).ConfigureAwait(false);
|
||||
return await ProcessResponseAsync<T>(response, cancellationToken).ConfigureAwait(false);
|
||||
}, retryOn401, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes an HTTP response and handles potential errors.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the response data.</typeparam>
|
||||
/// <param name="response">The HTTP response message.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The response data.</returns>
|
||||
private async Task<T> ProcessResponseAsync<T>(HttpResponseMessage response, CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (response.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
throw new UnauthorizedAccessException("Unauthorized access - token may be expired");
|
||||
}
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
if (typeof(T) == typeof(Stream))
|
||||
{
|
||||
// Response is a stream, read the response into it
|
||||
var ms = new MemoryStream();
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken).ConfigureAwait(false);
|
||||
await stream.CopyToAsync(ms, cancellationToken).ConfigureAwait(false);
|
||||
ms.Position = 0; // rewind
|
||||
return (T)(object)ms;
|
||||
}
|
||||
else
|
||||
{
|
||||
var content = await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
|
||||
return JsonSerializer.Deserialize<T>(content)!;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executes an HTTP request with retry logic for 401 unauthorized responses.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the response data.</typeparam>
|
||||
/// <param name="operation">The HTTP operation to execute.</param>
|
||||
/// <param name="retryOn401">Whether to retry on 401 unauthorized responses.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The response data.</returns>
|
||||
private async Task<T> ExecuteWithRetryAsync<T>(Func<Task<T>> operation, bool retryOn401, CancellationToken cancellationToken = default)
|
||||
{
|
||||
const int maxRetries = 3;
|
||||
var attempts = 0;
|
||||
|
||||
while (attempts <= maxRetries)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await operation().ConfigureAwait(false);
|
||||
}
|
||||
catch (UnauthorizedAccessException) when (retryOn401 && attempts < maxRetries)
|
||||
{
|
||||
attempts++;
|
||||
await RefreshTokenAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// This should never be reached due to the loop logic, but included for completeness
|
||||
throw new UnauthorizedAccessException("Maximum retry attempts reached for token refresh");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the authentication token based on the credential type.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
private async Task RefreshTokenAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await _tokenRefreshSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
switch (_credentialType)
|
||||
{
|
||||
case ServerCredentialType.Password:
|
||||
await AcquireBearerViaPasswordAuthAsync(_credential, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
case ServerCredentialType.Token:
|
||||
await AcquireBearerViaTokenAuthAsync(_credential, cancellationToken).ConfigureAwait(false);
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Unsupported credential type: {_credentialType}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_tokenRefreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
// V1 Authentication Methods
|
||||
|
||||
/// <summary>
|
||||
/// Performs password-based login to the Duplicati server (V1).
|
||||
/// </summary>
|
||||
/// <param name="input">The login input containing password and remember me flag.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The access token output.</returns>
|
||||
public async Task<AccessTokenOutputDto> LoginV1Async(LoginInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<AccessTokenOutputDto>("/api/v1/auth/login", input, cancellationToken, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs token-based signin to the Duplicati server (V1).
|
||||
/// </summary>
|
||||
/// <param name="input">The signin input containing signin token and remember me flag.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The access token output.</returns>
|
||||
public async Task<AccessTokenOutputDto> SigninV1Async(SigninInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<AccessTokenOutputDto>("/api/v1/auth/signin", input, cancellationToken, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Refreshes the access token using the refresh token (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The new access token output.</returns>
|
||||
public async Task<AccessTokenOutputDto> RefreshTokenV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<AccessTokenOutputDto>("/api/v1/auth/refresh", null, cancellationToken, false).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a signin token for authentication (V1).
|
||||
/// </summary>
|
||||
/// <param name="input">The signin token input.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The signin token output.</returns>
|
||||
public async Task<SigninTokenOutputDto> IssueSigninTokenV1Async(IssueSigninTokenInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<SigninTokenOutputDto>("/api/v1/auth/issuesignintoken", input, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a single-operation token for a specific operation (V1).
|
||||
/// </summary>
|
||||
/// <param name="operation">The operation name.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The single operation token output.</returns>
|
||||
public async Task<SingleOperationTokenOutputDto> IssueTokenV1Async(string operation, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<SingleOperationTokenOutputDto>($"/api/v1/auth/issuetoken/{operation}", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Issues a forever token for long-term authentication (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The single operation token output.</returns>
|
||||
public async Task<SingleOperationTokenOutputDto> IssueForeverTokenV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<SingleOperationTokenOutputDto>("/api/v1/auth/issue-forever-token", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Logs out and invalidates the refresh token (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task LogoutV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PostAsync<object>("/api/v1/auth/refresh/logout", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Backup Management Methods
|
||||
|
||||
/// <summary>
|
||||
/// Lists all backups configured on the server (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The list of backups.</returns>
|
||||
public async Task<BackupAndScheduleOutputDto[]> ListBackupsV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<BackupAndScheduleOutputDto[]>("/api/v1/backups", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new backup configuration (V1).
|
||||
/// </summary>
|
||||
/// <param name="backup">The backup configuration to create.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The created backup configuration.</returns>
|
||||
public async Task<BackupDto> CreateBackupV1Async(BackupDto backup, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<BackupDto>("/api/v1/backups", backup, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Imports a backup configuration (V1).
|
||||
/// </summary>
|
||||
/// <param name="input">The import backup input.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The import backup output.</returns>
|
||||
public async Task<ImportBackupOutputDto> ImportBackupV1Async(ImportBackupInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<ImportBackupOutputDto>("/api/v1/backups/import", input, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets details of a specific backup (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The backup details.</returns>
|
||||
public async Task<BackupGet.GetBackupResultDto> GetBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<BackupGet.GetBackupResultDto>($"/api/v1/backup/{backupId}", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a backup configuration (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="backup">The updated backup configuration.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The updated backup configuration.</returns>
|
||||
public async Task<BackupDto> UpdateBackupV1Async(string backupId, BackupDto backup, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PutAsync<BackupDto>($"/api/v1/backup/{backupId}", backup, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a backup configuration (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The delete backup output.</returns>
|
||||
public async Task<DeleteBackupOutputDto> DeleteBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await DeleteAsync<DeleteBackupOutputDto>($"/api/v1/backup/{backupId}", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Backup Operations
|
||||
|
||||
/// <summary>
|
||||
/// Starts a backup operation (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> StartBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/start", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a backup operation (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> RunBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/run", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restores files from a backup (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="input">The restore input parameters.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> RestoreBackupV1Async(string backupId, RestoreInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/restore", input, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verifies a backup's integrity (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> VerifyBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/verify", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repairs a backup (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="input">The repair input parameters.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> RepairBackupV1Async(string backupId, RepairInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/repair", input, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Repairs and updates a backup (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> RepairUpdateBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/repairupdate", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Compacts a backup (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> CompactBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/compact", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Vacuums a backup database (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> VacuumBackupV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/vacuum", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Backup Data Access
|
||||
|
||||
/// <summary>
|
||||
/// Lists files in a backup (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The list of files.</returns>
|
||||
public async Task<TreeNodeDto[]> ListFilesV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<TreeNodeDto[]>($"/api/v1/backup/{backupId}/files", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists backup filesets (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The list of filesets.</returns>
|
||||
public async Task<object[]> ListFilesetsV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<object[]>($"/api/v1/backup/{backupId}/filesets", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the backup log (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The backup log entries.</returns>
|
||||
public async Task<LogEntry[]> GetBackupLogV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<LogEntry[]>($"/api/v1/backup/{backupId}/log", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the remote operation log (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The remote log entries.</returns>
|
||||
public async Task<LogEntry[]> GetRemoteLogV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<LogEntry[]>($"/api/v1/backup/{backupId}/remotelog", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Database Management
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a backup database (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> DeleteDatabaseV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/deletedb", null, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves a backup database (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="input">The database path input.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> MoveDatabaseV1Async(string backupId, UpdateDbPathInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/movedb", input, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the database path (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="input">The database path input.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task started information.</returns>
|
||||
public async Task<TaskStartedDto> UpdateDatabaseV1Async(string backupId, UpdateDbPathInputDto input, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<TaskStartedDto>($"/api/v1/backup/{backupId}/updatedb", input, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Export Operations
|
||||
|
||||
/// <summary>
|
||||
/// Exports a backup configuration (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The backup configuration.</returns>
|
||||
public async Task<Stream> ExportBackupV1Async(string backupId, bool exportPasswords, string passPhrase, string exportToken, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
// [FromRoute] string id, [FromQuery(Name = "export-passwords")] bool? exportPasswords, [FromQuery] string? passphrase, [FromQuery] string token
|
||||
return await GetAsync<Stream>($"/api/v1/backup/{backupId}/export?exportpasswords={exportPasswords}&passphrase={Uri.EscapeDataString(passPhrase)}&token={Uri.EscapeDataString(exportToken)}", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports backup as command line (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The command line export.</returns>
|
||||
public async Task<ExportCommandlineDto> ExportCommandlineV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<ExportCommandlineDto>($"/api/v1/backup/{backupId}/export-cmdline", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Exports backup arguments only (V1).
|
||||
/// </summary>
|
||||
/// <param name="backupId">The backup identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The arguments export.</returns>
|
||||
public async Task<ExportArgsOnlyDto> ExportArgsOnlyV1Async(string backupId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<ExportArgsOnlyDto>($"/api/v1/backup/{backupId}/export-argsonly", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Server Management
|
||||
|
||||
/// <summary>
|
||||
/// Gets the server state (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The server status.</returns>
|
||||
public async Task<ServerStatusDto> GetServerStateV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<ServerStatusDto>("/api/v1/serverstate", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Pauses the server (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task PauseServerV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PostAsync<object>("/api/v1/serverstate/pause", null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resumes the server (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task ResumeServerV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PostAsync<object>("/api/v1/serverstate/resume", null, cancellationToken);
|
||||
}
|
||||
|
||||
// V1 Task Management
|
||||
|
||||
/// <summary>
|
||||
/// Lists all active tasks (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The list of active tasks.</returns>
|
||||
public async Task<object[]> ListTasksV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<object[]>("/api/v1/tasks", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets details of a specific task (V1).
|
||||
/// </summary>
|
||||
/// <param name="taskId">The task identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The task details.</returns>
|
||||
public async Task<GetTaskStateDto> GetTaskV1Async(string taskId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<GetTaskStateDto>($"/api/v1/task/{taskId}", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stops a running task (V1).
|
||||
/// </summary>
|
||||
/// <param name="taskId">The task identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task StopTaskV1Async(string taskId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PostAsync<object>($"/api/v1/task/{taskId}/stop", null, cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Aborts a running task (V1).
|
||||
/// </summary>
|
||||
/// <param name="taskId">The task identifier.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>A task representing the asynchronous operation.</returns>
|
||||
public async Task AbortTaskV1Async(string taskId, CancellationToken cancellationToken = default)
|
||||
{
|
||||
await PostAsync<object>($"/api/v1/task/{taskId}/abort", null, cancellationToken);
|
||||
}
|
||||
|
||||
// V1 System Information
|
||||
|
||||
/// <summary>
|
||||
/// Gets system information (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The system information.</returns>
|
||||
public async Task<SystemInfoDto> GetSystemInfoV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<SystemInfoDto>("/api/v1/systeminfo", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the changelog (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The changelog entries.</returns>
|
||||
public async Task<ChangelogDto[]> GetChangelogV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<ChangelogDto[]>("/api/v1/changelog", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets license information (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The license information.</returns>
|
||||
public async Task<LicenseDto[]> GetLicensesV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<LicenseDto[]>("/api/v1/licenses", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets acknowledgements (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The acknowledgements.</returns>
|
||||
public async Task<AcknowlegdementDto[]> GetAcknowledgementsV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<AcknowlegdementDto[]>("/api/v1/acknowledgements", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Settings Management
|
||||
|
||||
/// <summary>
|
||||
/// Gets server settings (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The server settings.</returns>
|
||||
public async Task<SettingDto[]> GetServerSettingsV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<SettingDto[]>("/api/v1/serversetting", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates server settings (V1).
|
||||
/// </summary>
|
||||
/// <param name="settings">The settings to update.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The updated settings.</returns>
|
||||
public async Task<SettingDto[]> UpdateServerSettingsV1Async(SettingDto[] settings, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PutAsync<SettingDto[]>("/api/v1/serversetting", settings, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V1 Filesystem Operations
|
||||
|
||||
/// <summary>
|
||||
/// Browses the filesystem (V1).
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The filesystem entries.</returns>
|
||||
public async Task<TreeNodeDto[]> BrowseFilesystemV1Async(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await GetAsync<TreeNodeDto[]>("/api/v1/filesystem", cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Performs filesystem operations (V1).
|
||||
/// </summary>
|
||||
/// <param name="data">The filesystem operation data.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The operation result.</returns>
|
||||
public async Task<object> PerformFilesystemOperationV1Async(object data, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<object>("/api/v1/filesystem", data, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// V2 API Methods
|
||||
|
||||
/// <summary>
|
||||
/// Lists filesets with pagination (V2).
|
||||
/// </summary>
|
||||
/// <param name="request">The list filesets request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The paged filesets response.</returns>
|
||||
public async Task<PagedResponseEnvelope<ListFilesetsResponseDto>> ListFilesetsV2Async(ListFilesetsRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<PagedResponseEnvelope<ListFilesetsResponseDto>>("/api/v2/backup/list-filesets", request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists folder contents with pagination (V2).
|
||||
/// </summary>
|
||||
/// <param name="request">The list folder content request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The paged folder content response.</returns>
|
||||
public async Task<PagedResponseEnvelope<ListFolderContentResponseDto>> ListFolderContentV2Async(ListFolderContentRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<PagedResponseEnvelope<ListFolderContentResponseDto>>("/api/v2/backup/list-folder", request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lists file versions with pagination (V2).
|
||||
/// </summary>
|
||||
/// <param name="request">The list file versions request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The paged file versions response.</returns>
|
||||
public async Task<PagedResponseEnvelope<ListFileVersionsOutputDto>> ListFileVersionsV2Async(ListFileVersionsRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<PagedResponseEnvelope<ListFileVersionsOutputDto>>("/api/v2/backup/list-versions", request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Searches entries with filters (V2).
|
||||
/// </summary>
|
||||
/// <param name="request">The search entries request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The paged search results response.</returns>
|
||||
public async Task<PagedResponseEnvelope<SearchEntriesResponseDto>> SearchEntriesV2Async(SearchEntriesRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<PagedResponseEnvelope<SearchEntriesResponseDto>>("/api/v2/backup/search", request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests destination connectivity (V2).
|
||||
/// </summary>
|
||||
/// <param name="request">The destination test request.</param>
|
||||
/// <param name="cancellationToken">The cancellation token. Optional, defaults to <see cref="CancellationToken.None"/>.</param>
|
||||
/// <returns>The destination test response.</returns>
|
||||
public async Task<ResponseEnvelope<DestinationTestResponseDto>> TestDestinationV2Async(DestinationTestRequestDto request, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await PostAsync<ResponseEnvelope<DestinationTestResponseDto>>("/api/v2/destination/test", request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases all resources used by the <see cref="DuplicatiServerClient"/>.
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Releases the unmanaged resources used by the <see cref="DuplicatiServerClient"/> and optionally releases the managed resources.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_disposed) return;
|
||||
if (disposing)
|
||||
{
|
||||
if (_selfOwnedHttpClient)
|
||||
_httpClient?.Dispose();
|
||||
_tokenRefreshSemaphore?.Dispose();
|
||||
}
|
||||
_disposed = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
namespace Duplicati.WebserverCore.Client;
|
||||
|
||||
/// <summary>
|
||||
/// Specifies the type of credential used for authenticating with the Duplicati server.
|
||||
/// </summary>
|
||||
public enum ServerCredentialType
|
||||
{
|
||||
/// <summary>
|
||||
/// Use password-based authentication. The credential should be the server's webserver password.
|
||||
/// This will perform a login operation via the /api/v1/auth/login endpoint.
|
||||
/// </summary>
|
||||
Password,
|
||||
|
||||
/// <summary>
|
||||
/// Use token-based authentication. The credential should be a signin token obtained from the server.
|
||||
/// This will perform a signin operation via the /api/v1/auth/signin endpoint.
|
||||
/// </summary>
|
||||
Token
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace Duplicati.WebserverCore.Endpoints.V1.Backup;
|
||||
|
||||
public class BackupGet : IEndpointV1
|
||||
{
|
||||
private record GetBackupResultDto(Dto.ScheduleDto? Schedule, Dto.BackupDto Backup, Dictionary<string, string> DisplayNames);
|
||||
public record GetBackupResultDto(Dto.ScheduleDto? Schedule, Dto.BackupDto Backup, Dictionary<string, string> DisplayNames);
|
||||
|
||||
public static void Map(RouteGroupBuilder group)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,628 @@
|
||||
// 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.WebserverCore.Client;
|
||||
using Duplicati.WebserverCore.Dto;
|
||||
using Duplicati.WebserverCore.Dto.V2;
|
||||
|
||||
class Program
|
||||
{
|
||||
static async Task Main(string[] args)
|
||||
{
|
||||
var serverUrl = "http://localhost:8200";
|
||||
|
||||
var password = Environment.GetEnvironmentVariable("TEST_PASSWORD") ??
|
||||
throw new InvalidOperationException("Must set the TEST_PASSWORD environment variable");
|
||||
|
||||
using var client = new DuplicatiServerClient(serverUrl, ServerCredentialType.Password, password);
|
||||
|
||||
try
|
||||
{
|
||||
// Authenticate explicitly (optional - happens automatically on first API call)
|
||||
await client.Authenticate();
|
||||
Console.WriteLine("✓ Authentication successful");
|
||||
|
||||
// Demonstrate all client methods
|
||||
await DemonstrateAuthenticationMethods(client);
|
||||
await DemonstrateBackupManagement(client);
|
||||
await DemonstrateBackupOperations(client);
|
||||
await DemonstrateBackupDataAccess(client);
|
||||
await DemonstrateDatabaseManagement(client);
|
||||
await DemonstrateExportOperations(client);
|
||||
await DemonstrateServerManagement(client);
|
||||
await DemonstrateTaskManagement(client);
|
||||
await DemonstrateSystemInformation(client);
|
||||
await DemonstrateSettingsManagement(client);
|
||||
await DemonstrateFilesystemOperations(client);
|
||||
await DemonstrateV2ApiMethods(client);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates authentication-related methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateAuthenticationMethods(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Authentication Methods ===");
|
||||
|
||||
try
|
||||
{
|
||||
// Issue a signin token
|
||||
var signinTokenResult = await client.IssueSigninTokenV1Async(
|
||||
new IssueSigninTokenInputDto( Environment.GetEnvironmentVariable("TEST_PASSWORD") ) { }, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Signin token issued: {signinTokenResult.Token[..10]}...");
|
||||
|
||||
// Issue a single operation token
|
||||
var operationToken = await client.IssueTokenV1Async("export", CancellationToken.None);
|
||||
Console.WriteLine($"✓ Operation token issued: {operationToken.Token[..10]}...");
|
||||
|
||||
await client.Authenticate();
|
||||
// Issue a forever token
|
||||
try
|
||||
{
|
||||
var foreverToken = await client.IssueForeverTokenV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Forever token issued: {foreverToken.Token[..10]}...");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// expected here if foreever tokens are disabled
|
||||
}
|
||||
|
||||
// Refresh token (if using refresh tokens)
|
||||
try
|
||||
{
|
||||
var refreshResult = await client.RefreshTokenV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Token refreshed: {refreshResult.AccessToken[..10]}...");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ℹ️ Token refresh not available: {ex.Message}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Authentication demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates backup management methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateBackupManagement(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Backup Management ===");
|
||||
|
||||
try
|
||||
{
|
||||
// List all backups
|
||||
var backups = await client.ListBackupsV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Found {backups.Length} backups");
|
||||
|
||||
foreach (var backup in backups)
|
||||
{
|
||||
Console.WriteLine($" - {backup.Backup.Name} (ID: {backup.Backup.ID})");
|
||||
|
||||
// Get detailed backup information
|
||||
var backupDetails = await client.GetBackupV1Async(backup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($" Name: {backupDetails.Backup.Name}");
|
||||
Console.WriteLine($" Sources: {string.Join(", ", backupDetails.Backup.Sources ?? [])}");
|
||||
|
||||
|
||||
var exportToken = await client.IssueTokenV1Async("export", CancellationToken.None);
|
||||
// Export backup configuration
|
||||
var exportedBackup = await client.ExportBackupV1Async(backup.Backup.ID, true, "random", exportToken.Token, CancellationToken.None);
|
||||
Console.WriteLine($" ✓ Backup configuration exported the stream is {exportedBackup.Length} bytes long");
|
||||
|
||||
// Export as command line
|
||||
var cmdlineExport = await client.ExportCommandlineV1Async(backup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($" ✓ Command line exported: {cmdlineExport.Command[..50]}...");
|
||||
|
||||
// Export arguments only
|
||||
var argsExport = await client.ExportArgsOnlyV1Async(backup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($" ✓ Arguments exported: {argsExport.Arguments.ToArray().Length} args");
|
||||
}
|
||||
|
||||
// Demonstrate backup creation (commented out to avoid creating test backups)
|
||||
/*
|
||||
var newBackup = new BackupDto
|
||||
{
|
||||
Name = "Test Backup",
|
||||
Description = "Created via API example",
|
||||
TargetURL = "file:///tmp/test-backup",
|
||||
Sources = ["~/Documents"],
|
||||
Settings = new Dictionary<string, string>
|
||||
{
|
||||
{ "compression-module", "zip" },
|
||||
{ "encryption-module", "aes" }
|
||||
}
|
||||
};
|
||||
|
||||
var createdBackup = await client.CreateBackupV1Async(newBackup, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Created backup: {createdBackup.Name}");
|
||||
|
||||
// Update the backup
|
||||
createdBackup.Description = "Updated via API";
|
||||
var updatedBackup = await client.UpdateBackupV1Async(createdBackup.ID, createdBackup, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Updated backup: {updatedBackup.Description}");
|
||||
|
||||
// Delete the backup
|
||||
var deleteResult = await client.DeleteBackupV1Async(createdBackup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Deleted backup: {deleteResult.DeletedFileCount} files deleted");
|
||||
*/
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Backup management demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates backup operations
|
||||
/// </summary>
|
||||
static async Task DemonstrateBackupOperations(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Backup Operations ===");
|
||||
|
||||
try
|
||||
{
|
||||
var backups = await client.ListBackupsV1Async(CancellationToken.None);
|
||||
if (backups.Length == 0)
|
||||
{
|
||||
Console.WriteLine("ℹ️ No backups found to demonstrate operations");
|
||||
return;
|
||||
}
|
||||
|
||||
var firstBackup = backups[0];
|
||||
Console.WriteLine($"Demonstrating operations on backup: {firstBackup.Backup.Name}");
|
||||
|
||||
// Start a backup (commented out to avoid actual backup operations)
|
||||
/*
|
||||
var startResult = await client.StartBackupV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Backup started: Task ID {startResult.TaskID}");
|
||||
|
||||
// Run a backup
|
||||
var runResult = await client.RunBackupV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Backup run: Task ID {runResult.TaskID}");
|
||||
|
||||
// Verify backup
|
||||
var verifyResult = await client.VerifyBackupV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Backup verification started: Task ID {verifyResult.TaskID}");
|
||||
|
||||
// Compact backup
|
||||
var compactResult = await client.CompactBackupV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Backup compaction started: Task ID {compactResult.TaskID}");
|
||||
|
||||
// Vacuum backup database
|
||||
var vacuumResult = await client.VacuumBackupV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Database vacuum started: Task ID {vacuumResult.TaskID}");
|
||||
|
||||
// Repair backup
|
||||
var repairInput = new RepairInputDto { OnlyLogErrors = true };
|
||||
var repairResult = await client.RepairBackupV1Async(firstBackup.Backup.ID, repairInput, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Backup repair started: Task ID {repairResult.TaskID}");
|
||||
|
||||
// Repair and update backup
|
||||
var repairUpdateResult = await client.RepairUpdateBackupV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Backup repair/update started: Task ID {repairUpdateResult.TaskID}");
|
||||
|
||||
// Restore files
|
||||
var restoreInput = new RestoreInputDto
|
||||
{
|
||||
Path = "~/Documents/test.txt",
|
||||
RestoreLocation = "~/Desktop/restored_test.txt"
|
||||
};
|
||||
var restoreResult = await client.RestoreBackupV1Async(firstBackup.Backup.ID, restoreInput, CancellationToken.None);
|
||||
Console.WriteLine($"✓ File restoration started: Task ID {restoreResult.TaskID}");
|
||||
*/
|
||||
|
||||
Console.WriteLine("ℹ️ Backup operations are commented out to avoid actual operations");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Backup operations demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates backup data access methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateBackupDataAccess(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Backup Data Access ===");
|
||||
|
||||
try
|
||||
{
|
||||
var backups = await client.ListBackupsV1Async(CancellationToken.None);
|
||||
if (backups.Length == 0)
|
||||
{
|
||||
Console.WriteLine("ℹ️ No backups found to demonstrate data access");
|
||||
return;
|
||||
}
|
||||
|
||||
var firstBackup = backups[0];
|
||||
Console.WriteLine($"Accessing data for backup: {firstBackup.Backup.Name}");
|
||||
|
||||
// List files in backup
|
||||
var files = await client.ListFilesV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Found {files.Length} files/folders in backup");
|
||||
|
||||
// List filesets
|
||||
var filesets = await client.ListFilesetsV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Found {filesets.Length} filesets");
|
||||
|
||||
// Get backup log
|
||||
var backupLog = await client.GetBackupLogV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Retrieved backup log: {backupLog.Length} entries");
|
||||
|
||||
// Get remote log
|
||||
var remoteLog = await client.GetRemoteLogV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Retrieved remote log: {remoteLog.Length} entries");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Backup data access demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates database management methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateDatabaseManagement(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Database Management ===");
|
||||
|
||||
try
|
||||
{
|
||||
var backups = await client.ListBackupsV1Async(CancellationToken.None);
|
||||
if (backups.Length == 0)
|
||||
{
|
||||
Console.WriteLine("ℹ️ No backups found to demonstrate database management");
|
||||
return;
|
||||
}
|
||||
|
||||
var firstBackup = backups[0];
|
||||
Console.WriteLine($"Database management for backup: {firstBackup.Backup.Name}");
|
||||
|
||||
// Database operations are commented out to avoid destructive actions
|
||||
/*
|
||||
// Move database
|
||||
var moveDbInput = new UpdateDbPathInputDto { Path = "/tmp/new_db_location" };
|
||||
var moveResult = await client.MoveDatabaseV1Async(firstBackup.Backup.ID, moveDbInput, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Database move started: Task ID {moveResult.TaskID}");
|
||||
|
||||
// Update database path
|
||||
var updateDbInput = new UpdateDbPathInputDto { Path = "/tmp/updated_db_location" };
|
||||
var updateResult = await client.UpdateDatabaseV1Async(firstBackup.Backup.ID, updateDbInput, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Database update started: Task ID {updateResult.TaskID}");
|
||||
|
||||
// Delete database
|
||||
var deleteDbResult = await client.DeleteDatabaseV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Database deletion started: Task ID {deleteDbResult.TaskID}");
|
||||
*/
|
||||
|
||||
Console.WriteLine("ℹ️ Database operations are commented out to avoid destructive actions");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Database management demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates export operations
|
||||
/// </summary>
|
||||
static async Task DemonstrateExportOperations(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Export Operations ===");
|
||||
|
||||
try
|
||||
{
|
||||
var backups = await client.ListBackupsV1Async(CancellationToken.None);
|
||||
if (backups.Length == 0)
|
||||
{
|
||||
Console.WriteLine("ℹ️ No backups found to demonstrate export operations");
|
||||
return;
|
||||
}
|
||||
|
||||
var firstBackup = backups[0];
|
||||
Console.WriteLine($"Export operations for backup: {firstBackup.Backup.Name}");
|
||||
|
||||
// Export backup configuration
|
||||
var exportToken = await client.IssueTokenV1Async("export", CancellationToken.None);
|
||||
var exportedConfig = await client.ExportBackupV1Async(firstBackup.Backup.ID,true, "random", exportToken.Token, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Exported backup configuration: {exportedConfig}");
|
||||
|
||||
// Export as command line
|
||||
var cmdlineExport = await client.ExportCommandlineV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Exported command line: {cmdlineExport.Command.Length} characters");
|
||||
|
||||
// Export arguments only
|
||||
var argsExport = await client.ExportArgsOnlyV1Async(firstBackup.Backup.ID, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Exported arguments: {argsExport.Arguments.ToArray().Length} arguments");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Export operations demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates server management methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateServerManagement(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Server Management ===");
|
||||
|
||||
try
|
||||
{
|
||||
// Get server state
|
||||
var serverState = await client.GetServerStateV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Server state: {serverState.ProgramState}");
|
||||
Console.WriteLine($" - Active task: {serverState.ActiveTask?.Item2 ?? "None"}");
|
||||
Console.WriteLine($" - Scheduled tasks: {serverState.SchedulerQueueIds?.Count ?? 0}");
|
||||
|
||||
// Server control operations are commented out to avoid disrupting the server
|
||||
/*
|
||||
// Pause server
|
||||
await client.PauseServerV1Async(CancellationToken.None);
|
||||
Console.WriteLine("✓ Server paused");
|
||||
|
||||
// Resume server
|
||||
await client.ResumeServerV1Async(CancellationToken.None);
|
||||
Console.WriteLine("✓ Server resumed");
|
||||
*/
|
||||
|
||||
Console.WriteLine("ℹ️ Server control operations are commented out to avoid disruption");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Server management demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates task management methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateTaskManagement(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Task Management ===");
|
||||
|
||||
try
|
||||
{
|
||||
// List active tasks
|
||||
var tasks = await client.ListTasksV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Found {tasks.Length} active tasks");
|
||||
|
||||
// If there are tasks, get details of the first one
|
||||
if (tasks.Length > 0 && tasks[0] is System.Text.Json.JsonElement taskElement)
|
||||
{
|
||||
if (taskElement.TryGetProperty("TaskID", out var taskIdProperty))
|
||||
{
|
||||
var taskId = taskIdProperty.GetString();
|
||||
if (!string.IsNullOrEmpty(taskId))
|
||||
{
|
||||
var taskDetails = await client.GetTaskV1Async(taskId, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Task details: {taskDetails.Status}");
|
||||
|
||||
// Task control operations are commented out to avoid disrupting running tasks
|
||||
/*
|
||||
// Stop task
|
||||
await client.StopTaskV1Async(taskId, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Task {taskId} stopped");
|
||||
|
||||
// Abort task
|
||||
await client.AbortTaskV1Async(taskId, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Task {taskId} aborted");
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("ℹ️ Task control operations are commented out to avoid disruption");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Task management demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates system information methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateSystemInformation(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== System Information ===");
|
||||
|
||||
try
|
||||
{
|
||||
// Get system information
|
||||
var systemInfo = await client.GetSystemInfoV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ System Info:");
|
||||
Console.WriteLine($" - Version: {systemInfo.ServerVersionName}");
|
||||
Console.WriteLine($" - Server Version: {systemInfo.ServerVersion}");
|
||||
Console.WriteLine($" - Machine Name: {systemInfo.MachineName}");
|
||||
Console.WriteLine($" - User Name: {systemInfo.UserName}");
|
||||
Console.WriteLine($" - OS Name: {systemInfo.OSType}");
|
||||
Console.WriteLine($" - .NET Version: {systemInfo.CLRVersion}");
|
||||
|
||||
// Get changelog
|
||||
var changelog = await client.GetChangelogV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Changelog: {changelog.Length} entries");
|
||||
|
||||
// Get licenses
|
||||
var licenses = await client.GetLicensesV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Licenses: {licenses.Length} licenses");
|
||||
|
||||
// Get acknowledgements
|
||||
var acknowledgements = await client.GetAcknowledgementsV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Acknowledgements: {acknowledgements.Length} acknowledgements");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ System information demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates settings management methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateSettingsManagement(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Settings Management ===");
|
||||
|
||||
try
|
||||
{
|
||||
// Get server settings
|
||||
var settings = await client.GetServerSettingsV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Retrieved {settings.Length} server settings");
|
||||
|
||||
foreach (var setting in settings.Take(5)) // Show first 5 settings
|
||||
{
|
||||
Console.WriteLine($" - {setting.Name}: {setting.Value}");
|
||||
}
|
||||
|
||||
// Settings update is commented out to avoid changing server configuration
|
||||
/*
|
||||
// Update server settings
|
||||
var settingsToUpdate = new[]
|
||||
{
|
||||
new SettingDto { Name = "example-setting", Value = "example-value" }
|
||||
};
|
||||
var updatedSettings = await client.UpdateServerSettingsV1Async(settingsToUpdate, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Updated {updatedSettings.Length} settings");
|
||||
*/
|
||||
|
||||
Console.WriteLine("ℹ️ Settings updates are commented out to avoid changing configuration");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Settings management demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates filesystem operations
|
||||
/// </summary>
|
||||
static async Task DemonstrateFilesystemOperations(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== Filesystem Operations ===");
|
||||
|
||||
try
|
||||
{
|
||||
// Browse filesystem
|
||||
var fsEntries = await client.BrowseFilesystemV1Async(CancellationToken.None);
|
||||
Console.WriteLine($"✓ Found {fsEntries.Length} filesystem entries");
|
||||
|
||||
foreach (var entry in fsEntries.Take(5)) // Show first 5 entries
|
||||
{
|
||||
Console.WriteLine($" - {entry.text} Size: ({entry.fileSize})");
|
||||
}
|
||||
|
||||
// Filesystem operations are commented out to avoid file system changes
|
||||
/*
|
||||
// Perform filesystem operation
|
||||
var fsOperation = new { operation = "list", path = "/" };
|
||||
var fsResult = await client.PerformFilesystemOperationV1Async(fsOperation, CancellationToken.None);
|
||||
Console.WriteLine($"✓ Filesystem operation completed");
|
||||
*/
|
||||
|
||||
Console.WriteLine("ℹ️ Filesystem operations are commented out to avoid file system changes");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ Filesystem operations demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Demonstrates V2 API methods
|
||||
/// </summary>
|
||||
static async Task DemonstrateV2ApiMethods(DuplicatiServerClient client)
|
||||
{
|
||||
Console.WriteLine("\n=== V2 API Methods ===");
|
||||
|
||||
try
|
||||
{
|
||||
var backups = await client.ListBackupsV1Async(CancellationToken.None);
|
||||
if (backups.Length == 0)
|
||||
{
|
||||
Console.WriteLine("ℹ️ No backups found to demonstrate V2 API methods");
|
||||
return;
|
||||
}
|
||||
|
||||
var firstBackup = backups[0];
|
||||
Console.WriteLine($"V2 API demonstrations for backup: {firstBackup.Backup.Name}");
|
||||
|
||||
// List filesets with pagination
|
||||
var filesetsRequest = new ListFilesetsRequestDto
|
||||
{
|
||||
BackupId = firstBackup.Backup.ID
|
||||
};
|
||||
var filesetsResponse = await client.ListFilesetsV2Async(filesetsRequest, CancellationToken.None);
|
||||
Console.WriteLine($"✓ V2 Filesets: {filesetsResponse.Data.ToArray().Length} items");
|
||||
|
||||
// List folder content with pagination
|
||||
var folderRequest = new ListFolderContentRequestDto
|
||||
{
|
||||
BackupId = firstBackup.Backup.ID,
|
||||
PageSize = 10,
|
||||
Paths = null,
|
||||
Time = null,
|
||||
Page = null
|
||||
};
|
||||
var folderResponse = await client.ListFolderContentV2Async(folderRequest, CancellationToken.None);
|
||||
Console.WriteLine($"✓ V2 Folder Content: {folderResponse.Data.ToArray().Length} items,");
|
||||
|
||||
// Search entries
|
||||
var searchRequest = new SearchEntriesRequestDto
|
||||
{
|
||||
BackupId = firstBackup.Backup.ID,
|
||||
Filters = ["*"],
|
||||
PageSize = 10,
|
||||
Paths = null,
|
||||
Time = null,
|
||||
Page = 0
|
||||
};
|
||||
var searchResponse = await client.SearchEntriesV2Async(searchRequest, CancellationToken.None);
|
||||
Console.WriteLine($"✓ V2 Search Results: {searchResponse.Data.ToArray().Length} items");
|
||||
|
||||
// Test destination (commented out as it requires valid destination configuration)
|
||||
/*
|
||||
var destTestRequest = new DestinationTestRequestDto
|
||||
{
|
||||
BackupId = firstBackup.Backup.ID
|
||||
};
|
||||
var destTestResponse = await client.TestDestinationV2Async(destTestRequest, CancellationToken.None);
|
||||
Console.WriteLine($"✓ V2 Destination Test: {destTestResponse.Data.Success}");
|
||||
*/
|
||||
|
||||
Console.WriteLine("ℹ️ Destination test is commented out as it requires valid configuration");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"❌ V2 API methods demo error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Duplicati\WebserverCore\Duplicati.WebserverCore.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
Reference in New Issue
Block a user