From f73bc2213c87d8f1155f34a1b45df61e96c67e88 Mon Sep 17 00:00:00 2001 From: Muhammad Abrar Date: Fri, 27 May 2022 15:50:55 +0500 Subject: Added support for new backend IDrivee2(https://www.idrivee2.com) --- Duplicati/GUI/Duplicati.GUI.TrayIcon/app.config | 4 +- Duplicati/Library/Backend/Backblaze/Strings.cs | 6 +- Duplicati/Library/Backend/Dropbox/WebApi.cs | 2 +- .../Duplicati.Library.Backend.Idrivee2.csproj | 111 +++++++++ Duplicati/Library/Backend/Idrivee2/Duplicati.snk | Bin 0 -> 596 bytes .../Library/Backend/Idrivee2/Idrivee2Backend.cs | 249 +++++++++++++++++++++ .../Backend/Idrivee2/Properties/AssemblyInfo.cs | 54 +++++ Duplicati/Library/Backend/Idrivee2/Strings.cs | 17 ++ Duplicati/Library/Backend/Idrivee2/app.config | 6 + Duplicati/Library/Backend/Idrivee2/packages.config | 9 + Duplicati/Server/Duplicati.Server.csproj | 4 + .../Server/WebServer/RESTMethods/ServerSettings.cs | 6 +- Duplicati/Server/app.config | 4 +- .../ngax/scripts/services/EditUriBuiltins.js | 74 ++++++ .../webroot/ngax/scripts/services/SystemInfo.js | 1 + .../Server/webroot/ngax/templates/backends/e2.html | 18 ++ Duplicati/Service/Runner.cs | 12 +- Duplicati/Service/app.config | 2 +- Duplicati/UnitTest/BackendToolTests.cs | 118 +++++----- 19 files changed, 624 insertions(+), 73 deletions(-) create mode 100644 Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj create mode 100644 Duplicati/Library/Backend/Idrivee2/Duplicati.snk create mode 100644 Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs create mode 100644 Duplicati/Library/Backend/Idrivee2/Properties/AssemblyInfo.cs create mode 100644 Duplicati/Library/Backend/Idrivee2/Strings.cs create mode 100644 Duplicati/Library/Backend/Idrivee2/app.config create mode 100644 Duplicati/Library/Backend/Idrivee2/packages.config create mode 100644 Duplicati/Server/webroot/ngax/templates/backends/e2.html (limited to 'Duplicati') diff --git a/Duplicati/GUI/Duplicati.GUI.TrayIcon/app.config b/Duplicati/GUI/Duplicati.GUI.TrayIcon/app.config index 9eaab5dd7..7cfe686fd 100644 --- a/Duplicati/GUI/Duplicati.GUI.TrayIcon/app.config +++ b/Duplicati/GUI/Duplicati.GUI.TrayIcon/app.config @@ -1,6 +1,8 @@  - + + + diff --git a/Duplicati/Library/Backend/Backblaze/Strings.cs b/Duplicati/Library/Backend/Backblaze/Strings.cs index 168553757..b4d0b2429 100644 --- a/Duplicati/Library/Backend/Backblaze/Strings.cs +++ b/Duplicati/Library/Backend/Backblaze/Strings.cs @@ -14,9 +14,9 @@ namespace Duplicati.Library.Backend.Strings { public static string NoB2UserIDError { get { return LC.L(@"No ""B2 Cloud Storage Account ID"" given"); } } public static string Description { get { return LC.L(@"This backend can read and write data to the Backblaze B2 Cloud Storage. Allowed formats are: ""b2://bucketname/prefix"""); } } public static string B2createbuckettypeDescriptionLong { get { return LC.L(@"By default, a private bucket is created. Use this option to set the bucket type. Refer to the B2 documentation for allowed types "); } } - public static string B2createbuckettypeDescriptionShort { get { return LC.L(@"The bucket type used when creating a bucket"); } } - public static string B2pagesizeDescriptionLong { get { return LC.L(@"Use this option to set the page size for listing contents of B2 buckets. A lower number means less data, but can increase the number of Class C transaction on B2. Suggested values are between 100 and 1000"); } } - public static string B2pagesizeDescriptionShort { get { return LC.L(@"The size of file-listing pages"); } } + public static string B2createbuckettypeDescriptionShort { get { return LC.L(@"The bucket type used when creating a bucket"); } } + public static string B2pagesizeDescriptionLong { get { return LC.L(@"Use this option to set the page size for listing contents of B2 buckets. A lower number means less data, but can increase the number of Class C transaction on B2. Suggested values are between 100 and 1000"); } } + public static string B2pagesizeDescriptionShort { get { return LC.L(@"The size of file-listing pages"); } } public static string B2downloadurlDescriptionLong { get { return LC.L(@"Change this if you want to use your custom domain to download files, and uploading will not be affected. The default download url depends on your account and looks like ""https://f00X.backblazeb2.com"""); } } public static string B2downloadurlDescriptionShort { get { return LC.L(@"The base URL to use for downloading files"); } } public static string InvalidPageSizeError(string argname, string value) { return LC.L(@"The setting ""{0}"" is invalid for ""{1}"", it must be an integer larger than zero", value, argname); } diff --git a/Duplicati/Library/Backend/Dropbox/WebApi.cs b/Duplicati/Library/Backend/Dropbox/WebApi.cs index 1d0f2af86..376dd4ec9 100644 --- a/Duplicati/Library/Backend/Dropbox/WebApi.cs +++ b/Duplicati/Library/Backend/Dropbox/WebApi.cs @@ -41,7 +41,7 @@ namespace Duplicati.Library.Backend.WebApi return Uri.UriBuilder(Url.API, Path.DeleteFolder); } - public static string UploadSessionStartUrl() + public static string UploadSessionStartUrl() { return Uri.UriBuilder(Url.CONTENT_API_URL, Path.UploadSessionStart); } diff --git a/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj b/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj new file mode 100644 index 000000000..1447efbf7 --- /dev/null +++ b/Duplicati/Library/Backend/Idrivee2/Duplicati.Library.Backend.Idrivee2.csproj @@ -0,0 +1,111 @@ + + + + Debug + AnyCPU + {6B594D23-B629-465C-B799-70EE9E56C218} + Library + Properties + Duplicati.Library.Backend + Duplicati.Library.Backend.idrivee2 + Duplicati.snk + + + v4.7.1 + + 3.5 + + + false + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + false + + + + ..\..\..\..\packages\AWSSDK.Core.3.3.103.37\lib\net45\AWSSDK.Core.dll + + + ..\..\..\..\packages\AWSSDK.S3.3.3.104.25\lib\net45\AWSSDK.S3.dll + + + + ..\..\..\..\packages\RestSharp.106.3.1\lib\net452\RestSharp.dll + True + + + + ..\..\..\..\packages\System.Reactive.4.0.0\lib\net46\System.Reactive.dll + True + + + ..\..\..\..\packages\System.Reactive.Linq.4.0.0\lib\net46\System.Reactive.Linq.dll + True + + + + + + + + + + + + + + {DE3E5D4C-51AB-4E5E-BEE8-E636CEBFBA65} + Duplicati.Library.Utility + + + {C5899F45-B0FF-483C-9D38-24A9FCAAB237} + Duplicati.Library.Interface + + + {B68F2214-951F-4F78-8488-66E1ED3F50BF} + Duplicati.Library.Localization + + + {D10A5FC0-11B4-4E70-86AA-8AEA52BD9798} + Duplicati.Library.Logging + + + {D63E53E4-A458-4C2F-914D-92F715F58ACF} + Duplicati.Library.Common + + + {C03F6DFD-805A-4BE0-9338-64870ADDB4A2} + Duplicati.Library.Backend.S3 + + + + + + + + + + \ No newline at end of file diff --git a/Duplicati/Library/Backend/Idrivee2/Duplicati.snk b/Duplicati/Library/Backend/Idrivee2/Duplicati.snk new file mode 100644 index 000000000..e0c1e2dd8 Binary files /dev/null and b/Duplicati/Library/Backend/Idrivee2/Duplicati.snk differ diff --git a/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs b/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs new file mode 100644 index 000000000..aef41259b --- /dev/null +++ b/Duplicati/Library/Backend/Idrivee2/Idrivee2Backend.cs @@ -0,0 +1,249 @@ +#region Disclaimer / License +// Copyright (C) 2015, The Duplicati Team +// http://www.duplicati.com, info@duplicati.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using Duplicati.Library.Common.IO; +using Duplicati.Library.Interface; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Duplicati.Library.Backend +{ + public class Idrivee2Backend : IBackend, IStreamingBackend + { + private static readonly string LOGTAG = Logging.Log.LogTagFromType(); + + static Idrivee2Backend() + { + + } + + private readonly string m_prefix; + private readonly string m_bucket; + + private IS3Client m_s3Client; + + public Idrivee2Backend() + { + } + + public Idrivee2Backend(string url, Dictionary options) + { + var uri = new Utility.Uri(url); + m_bucket = uri.Host; + m_prefix = uri.Path; + m_prefix = m_prefix.Trim(); + if (m_prefix.Length != 0) + { + m_prefix = Util.AppendDirSeparator(m_prefix, "/"); + } + string accessKeyId = null; + string accessKeySecret = null; + + if (options.ContainsKey("auth-username")) + accessKeyId = options["auth-username"]; + if (options.ContainsKey("auth-password")) + accessKeySecret = options["auth-password"]; + + if (options.ContainsKey("access_key_id")) + accessKeyId = options["access_key_id"]; + if (options.ContainsKey("secret_access_key")) + accessKeySecret = options["secret_access_key"]; + + if (string.IsNullOrEmpty(accessKeyId)) + throw new UserInformationException(Strings.Idrivee2Backend.NoKeyIdError, "Idrivee2NoKeyId"); + if (string.IsNullOrEmpty(accessKeySecret)) + throw new UserInformationException(Strings.Idrivee2Backend.NoKeySecretError, "Idrivee2NoKeySecret"); + string host= GetRegionEndpoint("https://api.idrivee2.com/api/service/get_region_end_point/" + accessKeyId); + + + m_s3Client = new S3AwsClient(accessKeyId, accessKeySecret, null, host, null, true, options); + + } + + public string GetRegionEndpoint(string url) + { + try + { + System.Net.HttpWebRequest req = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(url); + req.Method = System.Net.WebRequestMethods.Http.Get; + + Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req); + + using (System.Net.HttpWebResponse resp = (System.Net.HttpWebResponse)areq.GetResponse()) + { + int code = (int)resp.StatusCode; + if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically + throw new Exception("Failed to fetch region endpoint"); + using (var s = areq.GetResponseStream()) + { + using (var reader = new StreamReader(s)) + { + string endpoint = reader.ReadToEnd(); + return endpoint; + } + } + } + } + catch (System.Net.WebException wex) + { + //Convert to better exception + throw new Exception("Failed to fetch region endpoint"); + } + } + + #region IBackend Members + + public string DisplayName + { + get { return Strings.Idrivee2Backend.DisplayName; } + } + + public string ProtocolKey => "e2"; + + public bool SupportsStreaming => true; + + + public IEnumerable List() + { + foreach (IFileEntry file in Connection.ListBucket(m_bucket, m_prefix)) + { + ((FileEntry)file).Name = file.Name.Substring(m_prefix.Length); + if (file.Name.StartsWith("/", StringComparison.Ordinal) && !m_prefix.StartsWith("/", StringComparison.Ordinal)) + ((FileEntry)file).Name = file.Name.Substring(1); + + yield return file; + } + } + + public async Task PutAsync(string remotename, string localname, CancellationToken cancelToken) + { + using (FileStream fs = File.Open(localname, FileMode.Open, FileAccess.Read, FileShare.Read)) + await PutAsync(remotename, fs, cancelToken); + } + + public async Task PutAsync(string remotename, Stream input, CancellationToken cancelToken) + { + await Connection.AddFileStreamAsync(m_bucket, GetFullKey(remotename), input, cancelToken); + } + + public void Get(string remotename, string localname) + { + using (var fs = File.Open(localname, FileMode.Create, FileAccess.Write, FileShare.None)) + Get(remotename, fs); + } + + public void Get(string remotename, Stream output) + { + Connection.GetFileStream(m_bucket, GetFullKey(remotename), output); + } + + public void Delete(string remotename) + { + Connection.DeleteObject(m_bucket, GetFullKey(remotename)); + } + + public IList SupportedCommands + { + get + { + + var defaults = new Amazon.S3.AmazonS3Config(); + + var exts = + typeof(Amazon.S3.AmazonS3Config).GetProperties().Where(x => x.CanRead && x.CanWrite && (x.PropertyType == typeof(string) || x.PropertyType == typeof(bool) || x.PropertyType == typeof(int) || x.PropertyType == typeof(long) || x.PropertyType.IsEnum)) + .Select(x => (ICommandLineArgument)new CommandLineArgument( + "s3-ext-" + x.Name.ToLowerInvariant(), + x.PropertyType == typeof(bool) ? CommandLineArgument.ArgumentType.Boolean : x.PropertyType.IsEnum ? CommandLineArgument.ArgumentType.Enumeration : CommandLineArgument.ArgumentType.String, + x.Name, + string.Format("Extended option {0}", x.Name), + string.Format("{0}", x.GetValue(defaults)), + null, + x.PropertyType.IsEnum ? Enum.GetNames(x.PropertyType) : null)); + + + var normal = new ICommandLineArgument[] { + + new CommandLineArgument("access_key_secret", CommandLineArgument.ArgumentType.Password, Strings.Idrivee2Backend.KeySecretDescriptionShort, Strings.Idrivee2Backend.KeySecretDescriptionLong, null, new[]{"auth-password"}, null), + new CommandLineArgument("access_key_id", CommandLineArgument.ArgumentType.String, Strings.Idrivee2Backend.KeyIDDescriptionShort, Strings.Idrivee2Backend.KeyIDDescriptionLong,null, new[]{"auth-username"}, null) + + }; + + return normal.Union(exts).ToList(); + + } + } + + public string Description + { + get + { + return Strings.Idrivee2Backend.Description; + } + } + + public void Test() + { + this.TestList(); + } + + public void CreateFolder() + { + //S3 does not complain if the bucket already exists + Connection.AddBucket(m_bucket); + } + + #endregion + + #region IRenameEnabledBackend Members + + public void Rename(string source, string target) + { + Connection.RenameFile(m_bucket, GetFullKey(source), GetFullKey(target)); + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + m_s3Client?.Dispose(); + m_s3Client = null; + } + + #endregion + + private IS3Client Connection => m_s3Client; + + public string[] DNSName + { + get { return new[] { m_s3Client.GetDnsHost() }; } + } + + private string GetFullKey(string name) + { + //AWS SDK encodes the filenames correctly + return m_prefix + name; + } + } +} diff --git a/Duplicati/Library/Backend/Idrivee2/Properties/AssemblyInfo.cs b/Duplicati/Library/Backend/Idrivee2/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..e169e70d0 --- /dev/null +++ b/Duplicati/Library/Backend/Idrivee2/Properties/AssemblyInfo.cs @@ -0,0 +1,54 @@ +#region Disclaimer / License +// Copyright (C) 2015, The Duplicati Team +// http://www.duplicati.com, info@duplicati.com +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; either +// version 2.1 of the License, or (at your option) any later version. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// +#endregion +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("S3")] +[assembly: AssemblyDescription("An S3-compatible backend for Duplicati")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Duplicati Team")] +[assembly: AssemblyProduct("Duplicati.Backend.S3")] +[assembly: AssemblyCopyright("LGPL, Copyright © Duplicati Team 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("afa68988-4d82-490e-8044-acbc7d52ef4f")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("2.0.0.7")] +[assembly: AssemblyFileVersion("2.0.0.7")] diff --git a/Duplicati/Library/Backend/Idrivee2/Strings.cs b/Duplicati/Library/Backend/Idrivee2/Strings.cs new file mode 100644 index 000000000..f80a5f159 --- /dev/null +++ b/Duplicati/Library/Backend/Idrivee2/Strings.cs @@ -0,0 +1,17 @@ +using Duplicati.Library.Localization.Short; +namespace Duplicati.Library.Backend.Strings { + internal static class Idrivee2Backend { + public static string KeySecretDescriptionLong { get { return LC.L(@"The ""Access Key Secret "" can be obtained after logging into your IDrive e2 account, this can also be supplied through the ""auth-password"" property."); } } + public static string KeySecretDescriptionShort { get { return LC.L(@"The ""Access Key Secret"""); } } + public static string KeyIDDescriptionLong { get { return LC.L(@"The ""Access Key ID"" can be obtained after logging into your IDrive e2 account., this can also be supplied through the ""auth-username"" property."); } } + public static string KeyIDDescriptionShort { get { return LC.L(@"The ""Access Key ID"""); } } + + public static string BucketNameOrPathDescriptionLong { get { return LC.L(@"The ""Bucket Name or Complete Path"" is name of target bucket or complete of a folder inside the bucket."); } } + public static string BucketNameOrPathDescriptionShort { get { return LC.L(@"The ""Bucket Name or Complete Path"""); } } + + public static string DisplayName { get { return LC.L(@"IDrive e2"); } } + public static string NoKeySecretError { get { return LC.L(@"No Access key secret given"); } } + public static string NoKeyIdError { get { return LC.L(@"No Access key Id given"); } } + public static string Description { get; set; } + } +} diff --git a/Duplicati/Library/Backend/Idrivee2/app.config b/Duplicati/Library/Backend/Idrivee2/app.config new file mode 100644 index 000000000..ba98fc967 --- /dev/null +++ b/Duplicati/Library/Backend/Idrivee2/app.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Duplicati/Library/Backend/Idrivee2/packages.config b/Duplicati/Library/Backend/Idrivee2/packages.config new file mode 100644 index 000000000..52f490cda --- /dev/null +++ b/Duplicati/Library/Backend/Idrivee2/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Duplicati/Server/Duplicati.Server.csproj b/Duplicati/Server/Duplicati.Server.csproj index c0697798d..f661556c9 100644 --- a/Duplicati/Server/Duplicati.Server.csproj +++ b/Duplicati/Server/Duplicati.Server.csproj @@ -168,6 +168,10 @@ {F61679A9-E5DE-468A-B5A4-05F92D0143D2} Duplicati.Library.Backend.FTP + + {6b594d23-b629-465c-b799-70ee9e56c218} + Duplicati.Library.Backend.Idrivee2 + {2cd5dbc3-3da6-432d-ba97-f0b8d24501c2} Duplicati.Library.Backend.Jottacloud diff --git a/Duplicati/Server/WebServer/RESTMethods/ServerSettings.cs b/Duplicati/Server/WebServer/RESTMethods/ServerSettings.cs index 1df9d13cf..cb09037d8 100644 --- a/Duplicati/Server/WebServer/RESTMethods/ServerSettings.cs +++ b/Duplicati/Server/WebServer/RESTMethods/ServerSettings.cs @@ -75,10 +75,10 @@ namespace Duplicati.Server.WebServer.RESTMethods var serversettings = data.Where(x => !string.IsNullOrWhiteSpace(x.Key)).ToDictionary(x => x.Key, x => x.Key.StartsWith("--", StringComparison.Ordinal) ? null : x.Value); var globalsettings = data.Where(x => !string.IsNullOrWhiteSpace(x.Key) && x.Key.StartsWith("--", StringComparison.Ordinal)); - serversettings.Remove("server-ssl-certificate"); - serversettings.Remove("ServerSSLCertificate"); + serversettings.Remove("server-ssl-certificate"); + serversettings.Remove("ServerSSLCertificate"); - if (serversettings.Any()) + if (serversettings.Any()) Program.DataConnection.ApplicationSettings.UpdateSettings(serversettings, false); if (globalsettings.Any()) diff --git a/Duplicati/Server/app.config b/Duplicati/Server/app.config index 9eaab5dd7..7cfe686fd 100644 --- a/Duplicati/Server/app.config +++ b/Duplicati/Server/app.config @@ -1,6 +1,8 @@  - + + + diff --git a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js index 63a94b64d..e8e90ffda 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/EditUriBuiltins.js @@ -33,6 +33,7 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, EditUriBackendConfig.templates['tardigrade'] = 'templates/backends/tardigrade.html'; EditUriBackendConfig.templates['rclone'] = 'templates/backends/rclone.html'; EditUriBackendConfig.templates['cos'] = 'templates/backends/cos.html'; + EditUriBackendConfig.templates['e2'] = 'templates/backends/e2.html'; EditUriBackendConfig.testers['s3'] = function(scope, callback) { @@ -478,6 +479,17 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, delete options[nukeopts[x]]; }; + EditUriBackendConfig.parsers['e2'] = function (scope, module, server, port, path, options) { + if (options['--access-key-id']) + scope.Username = options['--access-key-id']; + if (options['--access-key-secret']) + scope.Password = options['--access-key-secret']; + + var nukeopts = ['--access-key-id', '--access-key-secret']; + for (var x in nukeopts) + delete options[nukeopts[x]]; + }; + EditUriBackendConfig.parsers['mega'] = function (scope, module, server, port, path, options) { EditUriBackendConfig.mergeServerAndPath(scope); }; @@ -759,6 +771,24 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, return url; }; + EditUriBackendConfig.builders['e2'] = function (scope) { + var opts = {}; + + EditUriBackendConfig.merge_in_advanced_options(scope, opts); + + // Slightly better error message + scope.Folder = scope.Server; + + var url = AppUtils.format('{0}://{1}/{2}{3}', + scope.Backend.Key, + scope.Server || '', + scope.Path || '', + AppUtils.encodeDictAsUrl(opts) + ); + + return url; + }; + EditUriBackendConfig.builders['mega'] = function (scope) { var opts = {}; @@ -1272,4 +1302,48 @@ backupApp.service('EditUriBuiltins', function (AppService, AppUtils, SystemInfo, if (res) continuation(); }; + + EditUriBackendConfig.validaters['e2'] = function (scope, continuation) { + var res = + EditUriBackendConfig.require_field(scope, 'Username', gettextCatalog.getString('Idrivee2 Access Key Id')) && + EditUriBackendConfig.require_field(scope, 'Password', gettextCatalog.getString('Idrivee2 Access Key Secret')) && + EditUriBackendConfig.require_field(scope, 'Server', gettextCatalog.getString('Bucket Name')); + + if (res) { + var re = new RegExp('[^A-Za-z0-9-]'); + var bucketname = scope['Server'] || ''; + var ix = bucketname.search(/[^A-Za-z0-9-]/g); + + if (ix >= 0) { + EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('The \'{{fieldname}}\' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})', { + value: bucketname[ix].charCodeAt(), + pos: ix, + character: bucketname[ix], + fieldname: gettextCatalog.getString('Bucket Name') + })); + res = false; + } + } + + if (res) { + var pathname = scope['Path'] || ''; + for (var i = pathname.length - 1; i >= 0; i--) { + var char = pathname.charCodeAt(i); + + if (char == '\\'.charCodeAt(0) || char == 127 || char < 32) { + EditUriBackendConfig.show_error_dialog(gettextCatalog.getString('The \'{{fieldname}}\' field contains an invalid character: {{character}} (value: {{value}}, index: {{pos}})', { + value: char, + pos: i, + character: pathname[i], + fieldname: gettextCatalog.getString('Path') + })); + res = false; + break; + } + } + } + + if (res) + continuation(); + }; }); diff --git a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js index b6b4e4e52..7569b5eab 100644 --- a/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js +++ b/Duplicati/Server/webroot/ngax/scripts/services/SystemInfo.js @@ -53,6 +53,7 @@ backupApp.service('SystemInfo', function($rootScope, $timeout, $cookies, AppServ }, local: {'file': null}, prop: { + 'e2':null, 's3': null, 'azure': null, 'googledrive': null, diff --git a/Duplicati/Server/webroot/ngax/templates/backends/e2.html b/Duplicati/Server/webroot/ngax/templates/backends/e2.html new file mode 100644 index 000000000..86af8a762 --- /dev/null +++ b/Duplicati/Server/webroot/ngax/templates/backends/e2.html @@ -0,0 +1,18 @@ +
+ + +
+
+ + +
+ +
+ + +
+ +
+ + +
diff --git a/Duplicati/Service/Runner.cs b/Duplicati/Service/Runner.cs index 4f2ae0f76..3922f7910 100644 --- a/Duplicati/Service/Runner.cs +++ b/Duplicati/Service/Runner.cs @@ -79,13 +79,13 @@ namespace Duplicati.Service m_reportMessage(string.Format("Starting process {0} with cmd args {1}", exec, cmdargs), false); - var pr = new System.Diagnostics.ProcessStartInfo(exec, cmdargs) - { - UseShellExecute = false, - RedirectStandardInput = true, + var pr = new System.Diagnostics.ProcessStartInfo(exec, cmdargs) + { + UseShellExecute = false, + RedirectStandardInput = true, RedirectStandardOutput = true, - RedirectStandardError = false, - WorkingDirectory = path + RedirectStandardError = false, + WorkingDirectory = path }; if (!m_terminate) diff --git a/Duplicati/Service/app.config b/Duplicati/Service/app.config index f8e90b9d2..29826702b 100644 --- a/Duplicati/Service/app.config +++ b/Duplicati/Service/app.config @@ -20,4 +20,4 @@
-
+
\ No newline at end of file diff --git a/Duplicati/UnitTest/BackendToolTests.cs b/Duplicati/UnitTest/BackendToolTests.cs index 6c4b17122..e12b4b484 100755 --- a/Duplicati/UnitTest/BackendToolTests.cs +++ b/Duplicati/UnitTest/BackendToolTests.cs @@ -1,58 +1,62 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Duplicati.Library.Interface; -using Duplicati.Library.Main; -using NUnit.Framework; - -namespace Duplicati.UnitTest -{ - [TestFixture] - public class BackendToolTests : BasicSetupHelper - { - [Test] - [Category("BackendTool")] - public void Get() +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Duplicati.Library.Interface; +using Duplicati.Library.Main; +using NUnit.Framework; + +namespace Duplicati.UnitTest +{ + [TestFixture] + public class BackendToolTests : BasicSetupHelper + { + [Test] + [Category("BackendTool")] + public void Get() { - // Files to create in MB. - int[] fileSizes = {10, 20, 30}; - foreach (int size in fileSizes) - { - var data = new byte[size * 1024 * 1024]; - var rng = new Random(); - rng.NextBytes(data); - File.WriteAllBytes(Path.Combine(DATAFOLDER, size + "MB"), data); - } - - // Run a backup. - var options = new Dictionary(TestOptions); - var backendURL = "file://" + this.TARGETFOLDER; - using (Controller c = new Controller(backendURL, options, null)) - { - var backupResults = c.Backup(new[] {DATAFOLDER}); - Assert.AreEqual(0, backupResults.Errors.Count()); - Assert.AreEqual(0, backupResults.Warnings.Count()); - } - - // Get the backend files using absolute paths - var absoluteDownloadFolder = Path.Combine(RESTOREFOLDER, "target-files-absolute"); - Directory.CreateDirectory(absoluteDownloadFolder); - foreach (var targetFile in Directory.GetFiles(TARGETFOLDER)) - { - // Absolute path + // Files to create in MB. + int[] fileSizes = {10, 20, 30}; + foreach (int size in fileSizes) + { + var data = new byte[size * 1024 * 1024]; + var rng = new Random(); + rng.NextBytes(data); + File.WriteAllBytes(Path.Combine(DATAFOLDER, size + "MB"), data); + } + + // Run a backup. + var options = new Dictionary(TestOptions); + var backendURL = "file://" + this.TARGETFOLDER; + using (Controller c = new Controller(backendURL, options, null)) + { + var backupResults = c.Backup(new[] {DATAFOLDER}); + foreach (var backupResultsWarning in backupResults.Warnings) + { + TestContext.WriteLine("Backend result warning:" + backupResultsWarning); + } + Assert.AreEqual(0, backupResults.Errors.Count()); + Assert.AreEqual(0, backupResults.Warnings.Count()); + } + + // Get the backend files using absolute paths + var absoluteDownloadFolder = Path.Combine(RESTOREFOLDER, "target-files-absolute"); + Directory.CreateDirectory(absoluteDownloadFolder); + foreach (var targetFile in Directory.GetFiles(TARGETFOLDER)) + { + // Absolute path var downloadFileName = Path.Combine(absoluteDownloadFolder, Path.GetFileName(targetFile)); var status = CommandLine.BackendTool.Program.RealMain(new[] { "GET", $"{backendURL}", $"{downloadFileName}" }); - Assert.AreEqual(0, status); - Assert.IsTrue(File.Exists(downloadFileName)); - TestUtils.AssertFilesAreEqual(targetFile, downloadFileName, false, downloadFileName); - } - - // Get the backend files using relative paths - var relativeDownloadFolder = Path.Combine(RESTOREFOLDER, "target-files-relative"); - Directory.CreateDirectory(relativeDownloadFolder); - var originalCurrentDirectory = Directory.GetCurrentDirectory(); - Directory.SetCurrentDirectory(relativeDownloadFolder); + Assert.AreEqual(0, status); + Assert.IsTrue(File.Exists(downloadFileName)); + TestUtils.AssertFilesAreEqual(targetFile, downloadFileName, false, downloadFileName); + } + + // Get the backend files using relative paths + var relativeDownloadFolder = Path.Combine(RESTOREFOLDER, "target-files-relative"); + Directory.CreateDirectory(relativeDownloadFolder); + var originalCurrentDirectory = Directory.GetCurrentDirectory(); + Directory.SetCurrentDirectory(relativeDownloadFolder); try { foreach (var targetFile in Directory.GetFiles(TARGETFOLDER)) @@ -63,12 +67,12 @@ namespace Duplicati.UnitTest Assert.AreEqual(0, status); Assert.IsTrue(File.Exists(downloadFileName)); TestUtils.AssertFilesAreEqual(targetFile, downloadFileName, false, downloadFileName); - } - } - finally + } + } + finally { - Directory.SetCurrentDirectory(originalCurrentDirectory); - } + Directory.SetCurrentDirectory(originalCurrentDirectory); + } } } } \ No newline at end of file -- cgit v1.2.3