Merge remote-tracking branch 'upstream/master' into about.html

This commit is contained in:
Suguru Hirahara
2024-08-02 11:54:49 -04:00
10 changed files with 121 additions and 34 deletions
@@ -30,7 +30,7 @@ namespace Duplicati.Library.Common.IO
{
public class WriterMetaData
{
public string Name { get; set; }
public string Name { get; set; }
public string LogicalPath { get; set; }
public Guid Guid { get; set; }
public List<string> Paths { get; set; }
@@ -129,7 +129,11 @@ namespace Duplicati.Library.Common.IO
_volumeMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
foreach (var kvp in _volumes)
{
_volumeMap.Add(kvp.Key, _vssBackupComponents.GetSnapshotProperties(kvp.Value).SnapshotDeviceObject);
// The snapshot path has the format of "\\?\GLOBALROOT\Device\HarddiskVolumeShadowCopy1"
// Attempting to get attributes on this path will fail, so we need to append a double backslash
// Strangely, the double backslash is not needed when acessing a subfolder, here a single backslash is enough,
// but double backslash works for both cases due to path normalization.
_volumeMap.Add(kvp.Key, Util.AppendDirSeparator(_vssBackupComponents.GetSnapshotProperties(kvp.Value).SnapshotDeviceObject) + "\\");
}
_volumeReverseMap = _volumeMap.ToDictionary(x => x.Value, x => x.Key);
@@ -137,7 +141,8 @@ namespace Duplicati.Library.Common.IO
public Dictionary<string, string> SnapshotDeviceAndVolumes
{
get {
get
{
return _volumeReverseMap;
}
}
@@ -178,10 +183,13 @@ namespace Duplicati.Library.Common.IO
var writer = _vssBackupComponents.WriterMetadata.First(o => o.WriterId.Equals(writerGUID));
foreach (var component in writer.Components)
{
yield return new WriterMetaData{ Guid = writerGUID,
yield return new WriterMetaData
{
Guid = writerGUID,
Name = component.ComponentName,
LogicalPath = component.LogicalPath,
Paths = GetPathsFromComponent(component) };
Paths = GetPathsFromComponent(component)
};
}
}
}
+22 -1
View File
@@ -508,11 +508,21 @@ ul.notification {
}
.longdescription {
margin-top: 10px;
--margin-block: 10px; /* Magic number */
margin-top: var(--margin-block);
margin-left: 190px;
clear: both;
font-style: italic;
white-space: pre-wrap;
.longdescription__item {
margin-block: 0 var(--margin-block);
}
.longdescription__default {
margin-block: var(--margin-block) 0;
}
}
}
@@ -1692,6 +1702,17 @@ body {
margin-top: 10px; /* Magic number */
}
}
.input.text {
#generic_server {
width: 335px;
}
#generic_port {
width: 50px;
margin-left: 10px; /* Magic number */
}
}
}
div.add {
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -41,7 +41,7 @@
<a href ng-click="deleteItem(item)" title="{{'Remove option' | translate}}">x</a>
<div ng-if="getDeprecationMessage(item)" class="longdescription" translate>DEPRECATED: {{getDeprecationMessage(item)}}</div>
<div class="longdescription">{{getLongDescription(item)}}<div translate>Default value: "{{getDefaultValue(item)}}"</div></div>
<div class="longdescription"><p class="longdescription__item">{{getLongDescription(item)}}</p><p class="longdescription__default" translate>Default value: "{{getDefaultValue(item)}}"</p></div>
</li>
<li>
@@ -5,8 +5,8 @@
<div class="input text">
<label for="generic_server" translate>Server and port</label>
<input type="text" style="width: 345px" name="generic_server" id="generic_server" ng-model="$parent.Server" placeholder="{{'Server hostname or IP' | translate}}" />
<input type="text" style="width: 50px" name="generic_port" id="generic_port" ng-model="$parent.Port" placeholder="{{'Port' | translate}}" />
<input type="text" name="generic_server" id="generic_server" ng-model="$parent.Server" placeholder="{{'Server hostname or IP' | translate}}" />
<input type="text" name="generic_port" id="generic_port" ng-model="$parent.Port" placeholder="{{'Port' | translate}}" />
</div>
<div class="input text">
@@ -14,7 +14,7 @@
<div class="input textarea linklabel">
<label for="target"><a href class="target" ng-click="EditUriState = true">Target URL &gt;</a></label>
<textarea name="target" id="target" ng-model="TargetURL" placeholder="{{'Enter a url, or click the \'Target URL &gt;\' link | translate}}"></textarea>
<textarea name="target" id="target" ng-model="TargetURL" placeholder="{{'Enter a url, or click the &quot;Target URL &gt;&quot; link' | translate}}"></textarea>
</div>
<div class="input textarea">
@@ -6,7 +6,8 @@ namespace Duplicati.WebserverCore.Middlewares;
public static class WebsocketExtensions
{
public static IApplicationBuilder UseNotifications(this IApplicationBuilder app, IEnumerable<string> allowedOrigins, string notificationPath)
public static IApplicationBuilder UseNotifications(this IApplicationBuilder app, IEnumerable<string> allowedOrigins,
string notificationPath)
{
var opts = new WebSocketOptions();
if (allowedOrigins.Any())
@@ -52,24 +53,37 @@ public static class WebsocketExtensions
var result = await ReceiveAsync();
while (!result.CloseStatus.HasValue)
while (!result?.CloseStatus.HasValue == true)
{
result = await ReceiveAsync();
}
await webSocket.CloseAsync(
result.CloseStatus.Value,
result.CloseStatusDescription,
CancellationToken.None);
if (result?.CloseStatus is not null)
{
await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);
}
return;
async Task<WebSocketReceiveResult> ReceiveAsync()
async Task<WebSocketReceiveResult?> ReceiveAsync()
{
var receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
WebSocketReceiveResult? receiveResult;
try
{
receiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken);
}
catch (WebSocketException e)
when (e is { WebSocketErrorCode: WebSocketError.ConnectionClosedPrematurely })
{
Console.WriteLine("[WebSocket] Client closed connection prematurely.");
receiveResult = null;
}
var message = Encoding.Default.GetString(buffer[..receiveResult.Count]);
await websocketAccessor.HandleClientMessage(message);
if (receiveResult?.CloseStatus is not null)
{
var message = Encoding.Default.GetString(buffer[..receiveResult.Count]);
await websocketAccessor.HandleClientMessage(message);
}
return receiveResult;
}
+55 -8
View File
@@ -1,5 +1,6 @@
using System.Globalization;
using System.IO.Compression;
using System.Text;
namespace ReleaseBuilder.Build;
@@ -468,6 +469,32 @@ public static partial class Command
await rtcfg.Productsign(pkgFile);
}
/// <summary>
/// Creates a GZip compressed file
/// </summary>
/// <param name="inputStream">The input stream path</param>
/// <param name="outputFilePath">The output file path</param>
/// <returns>A task representing the asynchronous operation</returns>
static async Task GzipCompressFileAsync(Stream inputStream, string outputFilePath)
{
using (var outputFileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write))
using (var gzipStream = new GZipStream(outputFileStream, CompressionMode.Compress))
await inputStream.CopyToAsync(gzipStream);
}
/// <summary>
/// Ensures the folder for the file exists
/// </summary>
/// <param name="filename">The filename</param>
/// <returns>The filename</returns>
public static string EnsureFolderForFile(string filename)
{
var folder = Path.GetDirectoryName(filename);
if (folder != null && !Directory.Exists(folder))
Directory.CreateDirectory(folder);
return filename;
}
/// <summary>
/// Builds a DEB package using Docker
/// </summary>
@@ -529,17 +556,25 @@ public static partial class Command
// Copy debian files
var resourcesDir = Path.Combine(baseDir, "ReleaseBuilder", "Resources", "debian");
// Write in the release notes
if (!string.IsNullOrEmpty(rtcfg.ChangelogNews))
File.WriteAllText(Path.Combine(pkgroot, "DEBIAN", "releasenotes"), rtcfg.ChangelogNews);
// Write the license file
File.Copy(
Path.Combine(baseDir, "LICENSE.txt"),
EnsureFolderForFile(
Path.Combine(pkgroot, "usr", "share", "doc", "duplicati", "copyright")
)
);
// Write a custom changelog file
File.WriteAllText(
Path.Combine(pkgroot, "DEBIAN", "changelog"),
File.ReadAllText(Path.Combine(resourcesDir, "changelog.template.txt"))
var changelogData = File.ReadAllText(Path.Combine(resourcesDir, "changelog.template.txt"))
.Replace("%VERSION%", rtcfg.ReleaseInfo.Version.ToString())
.Replace("%DATE%", DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss +0000", CultureInfo.InvariantCulture))
);
.Replace("%DATE%", DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss +0000", CultureInfo.InvariantCulture));
// Write a compressed changelog file
using (var changelogStream = new MemoryStream(Encoding.UTF8.GetBytes(changelogData)))
await GzipCompressFileAsync(
changelogStream,
EnsureFolderForFile(Path.Combine(pkgroot, "usr", "share", "doc", "duplicati", "changelog.gz"))
);
// Custom arch, from: https://wiki.debian.org/SupportedArchitectures
var debArchString = target.Arch switch
@@ -602,6 +637,18 @@ public static partial class Command
File.SetUnixFileMode(f.Destination, UnixFileMode.OtherRead | UnixFileMode.GroupRead | UnixFileMode.UserRead | UnixFileMode.UserWrite);
}
// Write a conf file
var confroot = Path.Combine(pkgroot, "etc");
var conffiles = Directory.EnumerateFiles(confroot, "*", SearchOption.AllDirectories)
.Select(x => "/" + Path.GetRelativePath(pkgroot, x))
.Append(string.Empty)
.ToList();
File.WriteAllText(
Path.Combine(pkgroot, "DEBIAN", "conffiles"),
string.Join("\n", conffiles)
);
// Copy the Docker build file
File.Copy(
Path.Combine(resourcesDir, "Dockerfile.build"),
@@ -1,11 +1,8 @@
Source: duplicati
Section: utils
Priority: extra
Build-Depends: debhelper-compat (= 13)
Maintainer: Kenneth Skovhede <kenneth@duplicati.com>
Standards-Version: 3.9.4
Homepage: http://duplicati.com
Vcs-Git: https://github.com/duplicati/duplicati.git
Homepage: https://duplicati.com
Package: duplicati
Architecture: %ARCH%
Depends: %DEPENDS%