Merge remote-tracking branch 'upstream/master' into about.html
This commit is contained in:
@@ -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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 ></a></label>
|
||||
<textarea name="target" id="target" ng-model="TargetURL" placeholder="{{'Enter a url, or click the \'Target URL >\' link | translate}}"></textarea>
|
||||
<textarea name="target" id="target" ng-model="TargetURL" placeholder="{{'Enter a url, or click the "Target URL >" 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;
|
||||
}
|
||||
|
||||
@@ -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%
|
||||
|
||||
Reference in New Issue
Block a user