From d34b20d38a4387947ba90068a72985b8aea024bb Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 22 May 2025 15:28:59 +0200 Subject: [PATCH] Remove Sia backend The Sia backend implementation is based on the previous version of Sia and will no longer work after the hard fork later this year. --- Duplicati.sln | 7 - .../Sia/Duplicati.Library.Backend.Sia.csproj | 22 - Duplicati/Library/Backend/Sia/Sia.cs | 478 ------------------ Duplicati/Library/Backend/Sia/Strings.cs | 35 -- Duplicati/Library/Backends/BackendModules.cs | 1 - .../Duplicati.Library.Backends.csproj | 1 - .../Library/Main/Operation/RestoreHandler.cs | 2 +- Duplicati/Library/RestAPI/Database/Backup.cs | 40 +- .../ngax/scripts/services/EditUriBuiltins.js | 50 -- .../ngax/scripts/services/SystemInfo.js | 1 - .../webroot/ngax/templates/backends/sia.html | 20 - README.ja-JP.md | 2 +- 12 files changed, 22 insertions(+), 637 deletions(-) delete mode 100644 Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj delete mode 100644 Duplicati/Library/Backend/Sia/Sia.cs delete mode 100644 Duplicati/Library/Backend/Sia/Strings.cs delete mode 100644 Duplicati/Server/webroot/ngax/templates/backends/sia.html diff --git a/Duplicati.sln b/Duplicati.sln index ff0f27dae..db8a2960a 100644 --- a/Duplicati.sln +++ b/Duplicati.sln @@ -83,8 +83,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Tools", "Duplicat EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Jottacloud", "Duplicati\Library\Backend\Jottacloud\Duplicati.Library.Backend.Jottacloud.csproj", "{2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Sia", "Duplicati\Library\Backend\Sia\Duplicati.Library.Backend.Sia.csproj", "{32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Backend.Rclone", "Duplicati\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj", "{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Duplicati.Library.Common", "Duplicati\Library\Common\Duplicati.Library.Common.csproj", "{D63E53E4-A458-4C2F-914D-92F715F58ACF}" @@ -356,10 +354,6 @@ Global {2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2}.Release|Any CPU.Build.0 = Release|Any CPU - {32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Debug|Any CPU.Build.0 = Debug|Any CPU - {32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Release|Any CPU.ActiveCfg = Release|Any CPU - {32A74526-3E5F-413A-8CB4-1EFDAD4C8B91}.Release|Any CPU.Build.0 = Release|Any CPU {851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Debug|Any CPU.Build.0 = Debug|Any CPU {851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -567,7 +561,6 @@ Global {59C8BBC5-6E42-46FB-AB3E-6C183A82459A} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} {B20A7CEE-9C5B-47B9-8B76-BC85ADFE8493} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} {2CD5DBC3-3DA6-432D-BA97-F0B8D24501C2} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} - {32A74526-3E5F-413A-8CB4-1EFDAD4C8B91} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} {851A1CB8-3CEB-41B4-956F-34D760D2A8E5} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} {E9AB8491-BD4C-4E4F-84C3-0BD551CC7489} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} {545DD6D4-9476-42D6-B51C-A28E000C489E} = {E1A9B303-F281-45C5-A4F6-CADD9DE3F3C4} diff --git a/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj b/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj deleted file mode 100644 index 7cef28639..000000000 --- a/Duplicati/Library/Backend/Sia/Duplicati.Library.Backend.Sia.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - - net8.0 - Library - Copyright © 2025 Team Duplicati, MIT license - enable - enable - - - - - - - - - - - - - - diff --git a/Duplicati/Library/Backend/Sia/Sia.cs b/Duplicati/Library/Backend/Sia/Sia.cs deleted file mode 100644 index 2d41d9e0d..000000000 --- a/Duplicati/Library/Backend/Sia/Sia.cs +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright (C) 2025, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Common.IO; -using Duplicati.Library.Interface; -using Duplicati.Library.Utility; -using Duplicati.Library.Utility.Options; -using Newtonsoft.Json; -using System.Globalization; -using System.Net; -using System.Runtime.CompilerServices; - -namespace Duplicati.Library.Backend.Sia -{ - public class Sia : IBackend - { - private const string SIA_PASSWORD = "sia-password"; - private const string SIA_TARGETPATH = "sia-targetpath"; - private const string SIA_REDUNDANCY = "sia-redundancy"; - - private readonly string m_apihost; - private readonly int m_apiport; - private readonly string m_targetpath; - private readonly float m_redundancy; - private readonly string? m_authorization; - private readonly TimeoutOptionsHelper.Timeouts m_timeouts; - - // ReSharper disable once UnusedMember.Global - // This constructor is needed by the BackendLoader. - public Sia() - { - m_apihost = null!; - m_targetpath = null!; - m_timeouts = null!; - } - - // ReSharper disable once UnusedMember.Global - // This constructor is needed by the BackendLoader. - public Sia(string url, Dictionary options) - { - var uri = new Utility.Uri(url); - - m_apihost = uri.Host; - m_apiport = uri.Port; - m_targetpath = uri.Path; - m_timeouts = TimeoutOptionsHelper.Parse(options); - - m_redundancy = 1.5F; - var redundancyString = options.GetValueOrDefault(SIA_REDUNDANCY); - if (!string.IsNullOrWhiteSpace(redundancyString)) - m_redundancy = (float)decimal.Parse(redundancyString, CultureInfo.InvariantCulture); - - if (m_apiport <= 0) - m_apiport = 9980; - - var targetPathString = options.GetValueOrDefault(SIA_TARGETPATH); - if (!string.IsNullOrWhiteSpace(targetPathString)) - m_targetpath = targetPathString; - - while (m_targetpath.Contains("//")) - m_targetpath = m_targetpath.Replace("//", "/"); - while (m_targetpath.StartsWith("/", StringComparison.Ordinal)) - m_targetpath = m_targetpath.Substring(1); - while (m_targetpath.EndsWith("/", StringComparison.Ordinal)) - m_targetpath = m_targetpath.Remove(m_targetpath.Length - 1); - - if (m_targetpath.Length == 0) - m_targetpath = "backup"; - - m_authorization = options.ContainsKey(SIA_PASSWORD) && !string.IsNullOrEmpty(options[SIA_PASSWORD]) - ? "Basic " + Convert.ToBase64String(System.Text.Encoding.GetEncoding("ISO-8859-1").GetBytes(":" + options[SIA_PASSWORD])) - : null; - } - - private HttpWebRequest CreateRequest(string endpoint) - { - string baseurl = "http://" + m_apihost + ":" + m_apiport; - var req = (HttpWebRequest)WebRequest.Create(baseurl + endpoint); - - if (m_authorization != null) - { - // Manually set Authorization header, since System.Net.NetworkCredential ignores credentials with empty usernames - req.Headers.Add("Authorization", m_authorization); - } - - req.KeepAlive = false; - req.UserAgent = string.Format("Sia-Agent (Duplicati SIA client {0})", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version); - - return req; - } - - private async Task GetResponseBodyOnError(string context, WebException wex, CancellationToken cancellationToken) - { - var response = wex.Response as HttpWebResponse; - if (response is null) - return $"{context} failed with error: {wex.Message}"; - - var body = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => - { - using (var data = response.GetResponseStream()) - using (var reader = new StreamReader(data)) - return reader.ReadToEnd(); - }).ConfigureAwait(false); - return string.Format("{0} failed, response: {1}", context, body); - } - - public class SiaFile - { - [JsonProperty("siapath")] - public string? Siapath { get; set; } - [JsonProperty("available")] - public bool Available { get; set; } - [JsonProperty("filesize")] - public long Filesize { get; set; } - [JsonProperty("uploadprogress")] - public float Uploadprogress { get; set; } - [JsonProperty("redundancy")] - public float Redundancy { get; set; } - } - - public class SiaFileList - { - [JsonProperty("files")] - public SiaFile[]? Files { get; set; } - } - - public class SiaDownloadFile - { - [JsonProperty("siapath")] - public string? Siapath { get; set; } - [JsonProperty("destination")] - public string? Destination { get; set; } - [JsonProperty("filesize")] - public long Filesize { get; set; } - [JsonProperty("received")] - public long Received { get; set; } - [JsonProperty("starttime")] - public string? Starttime { get; set; } - [JsonProperty("error")] - public string? Error { get; set; } - } - - public class SiaDownloadList - { - [JsonProperty("downloads")] - public SiaDownloadFile[]? Files { get; set; } - } - - private async Task GetFiles(CancellationToken cancelToken) - { - // Remove warning until this is rewritten to use HttpClient - await Task.CompletedTask; - - var fl = new SiaFileList(); - var endpoint = "/renter/files"; - - try - { - var req = CreateRequest(endpoint); - req.Method = WebRequestMethods.Http.Get; - - var areq = new AsyncHttpRequest(req); - - using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) - { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) - throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - - var serializer = new JsonSerializer(); - - using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => areq.GetResponseStream()).ConfigureAwait(false)) - using (var sr = new StreamReader(rs)) - using (var jr = new JsonTextReader(sr)) - fl = serializer.Deserialize(jr) - ?? throw new Exception("Failed to deserialize response"); - } - } - catch (WebException wex) - { - throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); - } - return fl; - } - - private async Task IsUploadComplete(string siafilename, CancellationToken cancelToken) - { - var fl = await GetFiles(cancelToken).ConfigureAwait(false); - if (fl.Files == null) - return false; - - foreach (var f in fl.Files) - { - if (f.Siapath == siafilename) - { - if (f.Available == true && f.Redundancy >= m_redundancy /* && f.Uploadprogress >= 100 */ ) - { - return true; - } - } - } - return false; - } - - private async Task GetDownloads(CancellationToken cancelToken) - { - var fl = new SiaDownloadList(); - string endpoint = "/renter/downloads"; - - try - { - var req = CreateRequest(endpoint); - req.Method = WebRequestMethods.Http.Get; - - var areq = new AsyncHttpRequest(req); - - using (var resp = await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancelToken, _ => (HttpWebResponse)areq.GetResponse()).ConfigureAwait(false)) - { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) - throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - - var serializer = new JsonSerializer(); - - using (var rs = await Utility.Utility.WithTimeout(m_timeouts.ListTimeout, cancelToken, _ => areq.GetResponseStream())) - using (var sr = new StreamReader(rs)) - using (var jr = new JsonTextReader(sr)) - fl = serializer.Deserialize(jr) - ?? throw new Exception("Failed to deserialize response"); - } - } - catch (WebException wex) - { - throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); - } - return fl; - } - - private async Task IsDownloadComplete(string siafilename, string localname, CancellationToken cancelToken) - { - var fl = await GetDownloads(cancelToken).ConfigureAwait(false); - if (fl.Files == null) - return false; - - foreach (var f in fl.Files) - { - if (f.Siapath == siafilename) - { - if (f.Error != "") - { - throw new Exception("failed to download " + siafilename + "err: " + f.Error); - } - if (f.Filesize == f.Received) - { - try - { - // Sia seems to keep the file open/locked for a while, make sure we can open it - using (var fs = new FileStream(localname, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) - fs.Close(); - } - catch (IOException) - { - return false; - } - return true; - } - } - } - return false; - } - - #region IBackend Members - - public Task TestAsync(CancellationToken cancelToken) - => this.TestListAsync(cancelToken); - - public Task CreateFolderAsync(CancellationToken cancelToken) - { - // Dummy method, Sia doesn't have folders - return Task.CompletedTask; - } - - public string DisplayName => Strings.Sia.DisplayName; - - public string ProtocolKey => "sia"; - - /// - public async IAsyncEnumerable ListAsync([EnumeratorCancellation] CancellationToken cancelToken) - { - SiaFileList fl; - try - { - fl = await GetFiles(cancelToken).ConfigureAwait(false); - } - catch (WebException wex) - { - throw new Exception("failed to call /renter/files " + wex.Message); - } - - if (fl.Files != null) - { - foreach (var f in fl.Files) - { - // Sia returns a complete file list, but we're only interested in files that are - // in our target path - if (f.Siapath != null && f.Siapath.StartsWith(m_targetpath, StringComparison.Ordinal)) - { - var fe = new FileEntry(f.Siapath.Substring(m_targetpath.Length + 1)) - { - Size = f.Filesize, - IsFolder = false - }; - yield return fe; - } - } - } - } - - public async Task PutAsync(string remotename, string filename, CancellationToken cancelToken) - { - var endpoint = ""; - var siafile = m_targetpath + "/" + remotename; - - try - { - endpoint = string.Format("/renter/upload/{0}/{1}?source={2}", - m_targetpath, - Utility.Uri.UrlEncode(remotename).Replace("+", "%20"), - Utility.Uri.UrlEncode(filename).Replace("+", "%20") - ); - - var req = CreateRequest(endpoint); - req.Method = WebRequestMethods.Http.Post; - - var areq = new AsyncHttpRequest(req); - - using (var resp = (HttpWebResponse)areq.GetResponse()) - { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) - throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - - while (!await IsUploadComplete(siafile, cancelToken).ConfigureAwait(false)) - await Task.Delay(5000, cancelToken).ConfigureAwait(false); - } - } - catch (WebException wex) - { - throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); - } - } - - public async Task GetAsync(string remotename, string localname, CancellationToken cancelToken) - { - string endpoint = ""; - string siafile = m_targetpath + "/" + remotename; - string tmpfilename = localname + ".tmp"; - - try - { - endpoint = string.Format("/renter/download/{0}/{1}?destination={2}", - m_targetpath, - Utility.Uri.UrlEncode(remotename).Replace("+", "%20"), - Utility.Uri.UrlEncode(tmpfilename).Replace("+", "%20") - ); - var req = CreateRequest(endpoint); - req.Method = WebRequestMethods.Http.Get; - - var areq = new AsyncHttpRequest(req); - - using (var resp = (HttpWebResponse)areq.GetResponse()) - { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) - throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - - while (!await IsDownloadComplete(siafile, localname, cancelToken).ConfigureAwait(false)) - await Task.Delay(5000, cancelToken).ConfigureAwait(false); - - File.Copy(tmpfilename, localname, true); - try - { - File.Delete(tmpfilename); - } - catch (Exception) - { - - } - } - } - catch (WebException wex) - { - if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) - throw new FileMissingException(wex); - else - throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancelToken).ConfigureAwait(false)); - } - } - - public async Task DeleteAsync(string remotename, CancellationToken cancellationToken) - { - string endpoint = ""; - - try - { - endpoint = string.Format("/renter/delete/{0}/{1}", - m_targetpath, - Utility.Uri.UrlEncode(remotename).Replace("+", "%20") - ); - var req = CreateRequest(endpoint); - req.Method = WebRequestMethods.Http.Post; - - await Utility.Utility.WithTimeout(m_timeouts.ShortTimeout, cancellationToken, _ => - { - var areq = new AsyncHttpRequest(req); - using (var resp = (HttpWebResponse)areq.GetResponse()) - { - int code = (int)resp.StatusCode; - if (code < 200 || code >= 300) - throw new WebException(resp.StatusDescription, null, WebExceptionStatus.ProtocolError, resp); - } - }).ConfigureAwait(false); - } - catch (WebException wex) - { - if (wex.Response is HttpWebResponse response && response.StatusCode == HttpStatusCode.NotFound) - throw new FileMissingException(wex); - else - throw new Exception(await GetResponseBodyOnError(endpoint, wex, cancellationToken).ConfigureAwait(false)); - } - } - - - public IList SupportedCommands => new List([ - new CommandLineArgument(SIA_TARGETPATH, CommandLineArgument.ArgumentType.String, Strings.Sia.SiaPathDescriptionShort, Strings.Sia.SiaPathDescriptionLong, "/backup"), - new CommandLineArgument(SIA_PASSWORD, CommandLineArgument.ArgumentType.Password, Strings.Sia.SiaPasswordShort, Strings.Sia.SiaPasswordLong, null), - new CommandLineArgument(SIA_REDUNDANCY, CommandLineArgument.ArgumentType.Decimal, Strings.Sia.SiaRedundancyDescriptionShort, Strings.Sia.SiaRedundancyDescriptionLong, "1.5"), - .. TimeoutOptionsHelper.GetOptions() - .Where(x => x.Name != TimeoutOptionsHelper.ReadWriteTimeoutOption) - ]); - - public string Description => Strings.Sia.Description; - - public Task GetDNSNamesAsync(CancellationToken cancelToken) => Task.FromResult(new[] { new System.Uri(m_apihost).Host }); - - #endregion - - #region IDisposable Members - - public void Dispose() - { - - } - - #endregion - - - } - - -} diff --git a/Duplicati/Library/Backend/Sia/Strings.cs b/Duplicati/Library/Backend/Sia/Strings.cs deleted file mode 100644 index 268bde58c..000000000 --- a/Duplicati/Library/Backend/Sia/Strings.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (C) 2025, The Duplicati Team -// https://duplicati.com, hello@duplicati.com -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -using Duplicati.Library.Localization.Short; -namespace Duplicati.Library.Backend.Strings -{ - internal static class Sia - { - public static string Description => LC.L(@"This backend can read and write data to Sia."); - public static string DisplayName => LC.L(@"Sia Decentralized Cloud"); - public static string SiaPathDescriptionLong => LC.L(@"Set the target path. Example: /backup"); - public static string SiaPathDescriptionShort => LC.L(@"Backup path"); - public static string SiaPasswordLong => LC.L(@"Supply a password for Sia server."); - public static string SiaPasswordShort => LC.L(@"Sia password"); - public static string SiaRedundancyDescriptionLong => LC.L(@"The minimum value for redundancy is 1.0."); - public static string SiaRedundancyDescriptionShort => LC.L(@"Set the minimum redundancy"); - } -} diff --git a/Duplicati/Library/Backends/BackendModules.cs b/Duplicati/Library/Backends/BackendModules.cs index 05ef8db4f..bd516f619 100644 --- a/Duplicati/Library/Backends/BackendModules.cs +++ b/Duplicati/Library/Backends/BackendModules.cs @@ -60,7 +60,6 @@ public static class BackendModules new Backend.OneDriveForBusinessBackend(), new Backend.SharePointBackend(), new Backend.SharePointV2(), - new Backend.Sia.Sia(), IsStorjSupported? new Backend.Storj.Storj() : null, new Backend.TahoeBackend(), new Backend.TencentCOS.COS(), diff --git a/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj b/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj index 1b29df065..acc47b148 100644 --- a/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj +++ b/Duplicati/Library/Backends/Duplicati.Library.Backends.csproj @@ -27,7 +27,6 @@ - diff --git a/Duplicati/Library/Main/Operation/RestoreHandler.cs b/Duplicati/Library/Main/Operation/RestoreHandler.cs index b4746e3a9..9913aa753 100644 --- a/Duplicati/Library/Main/Operation/RestoreHandler.cs +++ b/Duplicati/Library/Main/Operation/RestoreHandler.cs @@ -359,7 +359,7 @@ namespace Duplicati.Library.Main.Operation // Start the progress updater using (new Logging.Timer(LOGTAG, "RestoreNetworkWait", "RestoreNetworkWait")) - using (var kill_updater = new CancellationTokenSource()) + using (var kill_updater = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { var updater = Task.Run(async () => { diff --git a/Duplicati/Library/RestAPI/Database/Backup.cs b/Duplicati/Library/RestAPI/Database/Backup.cs index 8c49f0c5c..299e02371 100644 --- a/Duplicati/Library/RestAPI/Database/Backup.cs +++ b/Duplicati/Library/RestAPI/Database/Backup.cs @@ -1,22 +1,22 @@ -// 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 +// 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; @@ -33,7 +33,6 @@ namespace Duplicati.Server.Database private readonly string[] UrlPasswords = { "authid", "auth-password", - "sia-password", "storj-secret", "storj-shared-access", }; @@ -135,6 +134,7 @@ namespace Duplicati.Server.Database // breaks assumptions made by the decode_uri function in AppUtils.js. Since we are simply // removing password parameters, we will leave the parameters as they are in the target URL. filteredParameters = Library.Utility.Uri.ParseQueryString(url.Query, false); + // TODO: This list is not exhaustive, look at the BackendLoader and find all password fields + aliases foreach (string field in this.UrlPasswords) { filteredParameters.Remove(field); diff --git a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js index 25a8f1752..e3f934b7c 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js @@ -28,7 +28,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.templates['idrive'] = 'templates/backends/idrive.html'; EditUriBackendConfig.templates['box'] = 'templates/backends/oauth.html'; EditUriBackendConfig.templates['dropbox'] = 'templates/backends/oauth.html'; - EditUriBackendConfig.templates['sia'] = 'templates/backends/sia.html'; EditUriBackendConfig.templates['storj'] = 'templates/backends/storj.html'; EditUriBackendConfig.templates['rclone'] = 'templates/backends/rclone.html'; EditUriBackendConfig.templates['cos'] = 'templates/backends/cos.html'; @@ -543,20 +542,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, delete options[nukeopts[x]]; } - - EditUriBackendConfig.parsers['sia'] = function (scope, module, server, port, path, options) { - if (options['--sia-targetpath']) - scope.sia_targetpath = options['--sia-targetpath']; - if (options['--sia-redundancy']) - scope.sia_redundancy = options['--sia-redundancy']; - if (options['--sia-password']) - scope.sia_password = options['--sia-password']; - - var nukeopts = ['--sia-targetpath', '--sia-redundancy', '--sia-password']; - for (var x in nukeopts) - delete options[nukeopts[x]]; - } - EditUriBackendConfig.parsers['storj'] = function (scope, module, server, port, path, options) { if (options['--storj-auth-method']) scope.storj_auth_method = options['--storj-auth-method']; @@ -893,25 +878,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, return url; }; - EditUriBackendConfig.builders['sia'] = function (scope) { - var opts = { - 'sia-password': scope.sia_password, - 'sia-targetpath': scope.sia_targetpath, - 'sia-redundancy': scope.sia_redundancy - }; - - EditUriBackendConfig.merge_in_advanced_options(scope, opts, false); - - var url = AppUtils.format('{0}://{1}/{2}{3}', - scope.Backend.Key, - scope.Server || '', - scope.sia_targetpath || '', - AppUtils.encodeDictAsUrl(opts) - ); - - return url; - } - EditUriBackendConfig.builders['storj'] = function (scope) { var opts = { 'storj-auth-method': scope.storj_auth_method, @@ -1334,22 +1300,6 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, if (res) continuation(); }; - - EditUriBackendConfig.validaters['sia'] = function (scope, continuation) { - var res = - EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Server')); - - var re = new RegExp('^(([a-zA-Z0-9-])|(\/(?!\/)))*$'); - if (res && !re.test(scope['sia_targetpath'])) { - res = EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('Invalid characters in path')); - } - - if (res && (scope['sia_redundancy'] || '').trim().length == 0 || parseFloat(scope['sia_redundancy']) < 1.0) - res = EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('Minimum redundancy is 1.0')); // Do not forget to update SiaRedundancyDescriptionLong as well if the value is changed - - if (res) - continuation(); - }; EditUriBackendConfig.validaters['storj'] = function (scope, continuation) { var res = true; diff --git a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js index 645435500..4e6d122f7 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js @@ -71,7 +71,6 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ 'od4b': null, 'mssp': null, 'dropbox': null, - 'sia': null, 'storj': null, 'jottacloud': null, 'rclone': null, diff --git a/Duplicati/Server/webroot/ngax/templates/backends/sia.html b/Duplicati/Server/webroot/ngax/templates/backends/sia.html deleted file mode 100644 index 67e8543ec..000000000 --- a/Duplicati/Server/webroot/ngax/templates/backends/sia.html +++ /dev/null @@ -1,20 +0,0 @@ -
- - -
-
- - -
-
- - -
-
- - -
-
-

Choose 1.0 for fast backup, 1.5 for decent reliability, 2.0 for safer upload but slow backup.

-

Note: Sia will still boost redundancy later as long as you're connected to your hosts.

-
diff --git a/README.ja-JP.md b/README.ja-JP.md index 26b3200cc..e9a800024 100644 --- a/README.ja-JP.md +++ b/README.ja-JP.md @@ -10,7 +10,7 @@ Duplicati は、フリー(自由)でオープンソースのバックアップ用クライアントです。圧縮し、暗号化した増分バックアップを、クラウドストレージサービスや遠隔のファイルサーバー上に安全に保存できます。Duplicati は、主に以下のサービスやソフトウェアで使うことができます。 -   _Amazon S3、[IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2")、[Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage")、Box、Dropbox、FTP、Google クラウド、Google ドライブ、MEGA、Microsoft Azure、Microsoft OneDrive、Rackspace Cloud Files、OpenStack Storage (Swift)、Sia、Storj DCS、SSH (SFTP)、WebDAV、Tencent クラウドオブジェクトストレージ(COS)、Aliyun OSS、[その他にも対応しています!](https://docs.duplicati.com/backup-destinations/destination-overview)_ +   _Amazon S3、[IDrive e2](https://www.idrive.com/e2/duplicati "Using Duplicati with IDrive e2")、[Backblaze (B2)](https://www.backblaze.com/blog/duplicati-backups-cloud-storage/ "Duplicati with Backblaze B2 Cloud Storage")、Box、Dropbox、FTP、Google クラウド、Google ドライブ、MEGA、Microsoft Azure、Microsoft OneDrive、Rackspace Cloud Files、OpenStack Storage (Swift)、Storj DCS、SSH (SFTP)、WebDAV、Tencent クラウドオブジェクトストレージ(COS)、Aliyun OSS、[その他にも対応しています!](https://docs.duplicati.com/backup-destinations/destination-overview)_ Duplicati は MIT ライセンスで公開されており、Windows、OSX、Linux で利用できます(.NET 4.7.1 以上、または Mono 5.10.0 以上が必要です)。