2026-02-19 19:55:01 +01:00
|
|
|
using System;
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.IO;
|
2026-02-20 15:29:25 +01:00
|
|
|
using System.Linq;
|
2026-02-19 19:55:01 +01:00
|
|
|
using System.Text.Json;
|
|
|
|
|
using System.Threading;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Duplicati.Library.Interface;
|
2026-02-20 11:38:17 +01:00
|
|
|
using Duplicati.Library.Logging;
|
2026-02-19 19:55:01 +01:00
|
|
|
using Duplicati.Library.Utility;
|
2026-03-05 15:30:36 +01:00
|
|
|
using Duplicati.Proprietary.DiskImage.General;
|
2026-02-19 19:55:01 +01:00
|
|
|
|
|
|
|
|
namespace Duplicati.Proprietary.DiskImage;
|
|
|
|
|
|
|
|
|
|
public class WebModule : IWebModule
|
|
|
|
|
{
|
2026-02-20 11:38:17 +01:00
|
|
|
private static readonly string LOGTAG = Log.LogTagFromType<WebModule>();
|
|
|
|
|
|
2026-02-19 19:55:01 +01:00
|
|
|
public string Key => OptionsHelper.ModuleKey;
|
|
|
|
|
|
|
|
|
|
public string DisplayName => Strings.WebModuleDisplayName;
|
|
|
|
|
|
|
|
|
|
public string Description => Strings.WebModuleDescription;
|
|
|
|
|
|
|
|
|
|
public enum Operation
|
|
|
|
|
{
|
|
|
|
|
ListDestination
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static readonly Operation DEFAULT_OPERATION = Operation.ListDestination;
|
|
|
|
|
private const string KEY_OPERATION = "operation";
|
|
|
|
|
private const string KEY_URL = "url";
|
|
|
|
|
private const string KEY_PATH = "path";
|
|
|
|
|
|
|
|
|
|
public IList<ICommandLineArgument> SupportedCommands => [
|
|
|
|
|
new CommandLineArgument(KEY_OPERATION, CommandLineArgument.ArgumentType.Enumeration, Strings.WebModuleOperationShort, Strings.WebModuleOperationLong, DEFAULT_OPERATION.ToString(), null, Enum.GetNames<Operation>()),
|
|
|
|
|
new CommandLineArgument(KEY_URL, CommandLineArgument.ArgumentType.String, Strings.WebModuleURLShort, Strings.WebModuleURLLong),
|
|
|
|
|
new CommandLineArgument(KEY_PATH, CommandLineArgument.ArgumentType.String, Strings.WebModulePathShort, Strings.WebModulePathLong)
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
public async Task<IDictionary<string, string>> Execute(IDictionary<string, string?> options, CancellationToken cancellationToken)
|
|
|
|
|
{
|
|
|
|
|
var op = Utility.ParseEnumOption(options.AsReadOnly(), KEY_OPERATION, DEFAULT_OPERATION);
|
|
|
|
|
options.TryGetValue(KEY_PATH, out var path);
|
|
|
|
|
|
|
|
|
|
if (op != Operation.ListDestination)
|
|
|
|
|
throw new UserInformationException($"Unsupported operation: {op}", "UnsupportedOperation");
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrWhiteSpace(path) || path == "/")
|
2026-03-05 15:30:36 +01:00
|
|
|
{
|
|
|
|
|
var res = new Dictionary<string, string>();
|
|
|
|
|
await foreach (var drive in SourceProvider.ListPhysicalDrives(cancellationToken))
|
2026-06-12 11:06:21 +02:00
|
|
|
res[drive.Path] = JsonSerializer.Serialize(await drive.GetMinorMetadata(cancellationToken));
|
|
|
|
|
|
2026-03-05 15:30:36 +01:00
|
|
|
return res;
|
|
|
|
|
}
|
2026-02-27 14:33:34 +01:00
|
|
|
|
2026-03-05 16:08:32 +01:00
|
|
|
var prefix = SourceProvider.GetDevicePrefix();
|
2026-02-27 14:33:34 +01:00
|
|
|
List<string> parts = [.. path[prefix.Length..].Split(Path.DirectorySeparatorChar, 2)];
|
|
|
|
|
var physicalDrivePath = prefix + parts.First();
|
2026-02-20 15:29:25 +01:00
|
|
|
var subpath = parts.Last();
|
2026-02-19 19:55:01 +01:00
|
|
|
|
2026-02-20 15:29:25 +01:00
|
|
|
using var client = new SourceProvider("diskimage://" + physicalDrivePath, "", new Dictionary<string, string?>(options));
|
2026-06-03 17:27:34 +02:00
|
|
|
await client.InitializeAsync(cancellationToken);
|
2026-02-19 19:55:01 +01:00
|
|
|
|
2026-02-20 15:29:25 +01:00
|
|
|
if (string.IsNullOrWhiteSpace(subpath))
|
|
|
|
|
return new Dictionary<string, string>()
|
|
|
|
|
{
|
2026-02-27 14:33:34 +01:00
|
|
|
{$"{physicalDrivePath}{Path.DirectorySeparatorChar}root{Path.DirectorySeparatorChar}", "{}"}
|
2026-02-20 15:29:25 +01:00
|
|
|
};
|
|
|
|
|
|
2026-06-03 17:27:34 +02:00
|
|
|
var targetEntry = await client.GetEntryAsync(subpath, isFolder: true, cancellationToken).ConfigureAwait(false)
|
2026-02-27 14:33:34 +01:00
|
|
|
?? throw new DirectoryNotFoundException($"Path not found: {path}");
|
2026-02-19 19:55:01 +01:00
|
|
|
|
|
|
|
|
var result = new Dictionary<string, string>();
|
|
|
|
|
await foreach (var entry in targetEntry.Enumerate(cancellationToken))
|
|
|
|
|
{
|
2026-02-20 15:29:25 +01:00
|
|
|
if (entry.Path.EndsWith("geometry.json", StringComparison.OrdinalIgnoreCase))
|
|
|
|
|
continue;
|
2026-02-19 19:55:01 +01:00
|
|
|
var metadata = await entry.GetMinorMetadata(cancellationToken);
|
2026-02-20 15:29:25 +01:00
|
|
|
if (metadata.ContainsKey("partition:Number"))
|
2026-02-27 14:33:34 +01:00
|
|
|
result[$"{physicalDrivePath}{Path.DirectorySeparatorChar}{entry.Path.TrimEnd(Path.DirectorySeparatorChar)}"] = JsonSerializer.Serialize(metadata);
|
2026-02-20 15:29:25 +01:00
|
|
|
else
|
2026-02-27 14:33:34 +01:00
|
|
|
result[$"{physicalDrivePath}{Path.DirectorySeparatorChar}{entry.Path}"] = JsonSerializer.Serialize(metadata);
|
2026-02-19 19:55:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public IDictionary<string, IDictionary<string, string>> GetLookups()
|
|
|
|
|
=> new Dictionary<string, IDictionary<string, string>>();
|
|
|
|
|
}
|