From db312092167d3014a1d68a1d27879c35154c7728 Mon Sep 17 00:00:00 2001 From: Rune Henriksen Date: Sun, 8 Apr 2018 22:01:40 +0200 Subject: [PATCH 01/12] made dismiss all option only show up with 2 or more notifications --- Duplicati/Server/webroot/ngax/templates/notificationarea.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Server/webroot/ngax/templates/notificationarea.html b/Duplicati/Server/webroot/ngax/templates/notificationarea.html index c73d4c94b..2880f1556 100644 --- a/Duplicati/Server/webroot/ngax/templates/notificationarea.html +++ b/Duplicati/Server/webroot/ngax/templates/notificationarea.html @@ -35,7 +35,7 @@ -
  • +
  • Dismiss all From 04c7218a08fd80073a3e3eb5e8d9426ed5e87a23 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Mon, 9 Apr 2018 14:26:53 +0200 Subject: [PATCH 02/12] Added some extra logging and error checking for the IO priority handler --- Duplicati/Library/Main/ProcessController.cs | 44 +++++++++++++++++---- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/Duplicati/Library/Main/ProcessController.cs b/Duplicati/Library/Main/ProcessController.cs index e56850e3c..8a75c11c9 100755 --- a/Duplicati/Library/Main/ProcessController.cs +++ b/Duplicati/Library/Main/ProcessController.cs @@ -180,15 +180,25 @@ namespace Duplicati.Library.Main if (Duplicati.Library.Utility.Utility.IsClientOSX) { var data = RunProcessAndGetResult("ps", $"-onice -p {pid}"); - m_originalNiceLevel = int.Parse(data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Last()); + if (data.Item1 != 0) + { + Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", null, "Failed to get background IO priority, exitcode: {0}, stderr: {1}", data.Item1, data.Item3); + } + else + { + m_originalNiceLevel = int.Parse(data.Item2.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Last()); - data = RunProcessAndGetResult("renice", $"20 {pid}"); - m_hasEnabledBackgroundIOPriority = true; + data = RunProcessAndGetResult("renice", $"20 -p {pid}"); + if (data.Item1 != 0) + Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", null, "Failed to get background IO priority, exitcode: {0}, stderr: {1}", data.Item1, data.Item3); + else + m_hasEnabledBackgroundIOPriority = true; + } } else { var data = RunProcessAndGetResult("ionice", $"-p {pid}"); - var results = data.Split(new char[] { ':', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); + var results = data.Item2.Split(new char[] { ':', ' ', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries); var ioclass = results[0]; if (string.Equals(ioclass, "idle", StringComparison.OrdinalIgnoreCase)) { @@ -326,18 +336,28 @@ namespace Duplicati.Library.Main { m_hasEnabledBackgroundIOPriority = false; var pid = System.Diagnostics.Process.GetCurrentProcess().Id; + Tuple data; + if (Duplicati.Library.Utility.Utility.IsClientOSX) { - var data = RunProcessAndGetResult($"renice", $"{m_originalNiceLevel} {pid}"); + // TODO: We can only give lower priority, thus not reset it ... + data = RunProcessAndGetResult($"renice", $"{m_originalNiceLevel} -p {pid}"); + if (data.Item1 != 0) + Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", null, "Failed to reset background IO priority, exitcode: {0}, stderr: {1}", data.Item1, data.Item3); } else { - string data; if (m_originalNiceLevel < 0) data = RunProcessAndGetResult($"ionice", $"-c {m_originalNiceClass} -p {pid}"); else data = RunProcessAndGetResult($"ionice", $"-c {m_originalNiceClass} -n {m_originalNiceLevel} -p {pid}"); + + if (!string.IsNullOrWhiteSpace(data.Item3)) + Logging.Log.WriteWarningMessage(LOGTAG, "BackgroundPriorityError", null, "Failed to reset background IO priority, exitcode: {0}, stderr: {1}", data.Item1, data.Item3); + } + + } } } @@ -358,10 +378,11 @@ namespace Duplicati.Library.Main /// The stdout data. /// The executable to invoke. /// The commandline arguments. - private static string RunProcessAndGetResult(string filename, string arguments) + private static Tuple RunProcessAndGetResult(string filename, string arguments) { var psi = new System.Diagnostics.ProcessStartInfo(filename, arguments); psi.RedirectStandardOutput = true; + psi.RedirectStandardError = true; psi.UseShellExecute = false; Logging.Log.WriteExplicitMessage(LOGTAG, "RunningCommand", null, "Running: {0} {1}", filename, arguments); @@ -369,7 +390,14 @@ namespace Duplicati.Library.Main var pi = System.Diagnostics.Process.Start(psi); pi.WaitForExit(5000); if (pi.HasExited) - return pi.StandardOutput.ReadToEnd().Trim(); + { + return + new Tuple( + pi.ExitCode, + pi.StandardOutput.ReadToEnd().Trim(), + pi.StandardError.ReadToEnd().Trim() + ); + } pi.Kill(); throw new Library.Interface.UserInformationException($"The process {filename} with arguments {arguments} failed to stop", "LaunchProcessFailed"); From b1615bceefa57022c1b312c51da6788742cd5e34 Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Tue, 10 Apr 2018 13:08:17 +0200 Subject: [PATCH 03/12] Removed the repeating log scope for now --- Duplicati/Library/Main/Controller.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Duplicati/Library/Main/Controller.cs b/Duplicati/Library/Main/Controller.cs index ef83945a0..777260d40 100644 --- a/Duplicati/Library/Main/Controller.cs +++ b/Duplicati/Library/Main/Controller.cs @@ -559,7 +559,6 @@ namespace Duplicati.Library.Main using (new ProcessController(m_options)) using (new Logging.Timer(LOGTAG, string.Format("Run{0}", result.MainOperation), string.Format("Running {0}", result.MainOperation))) - using (new Logging.RepeatingLogScope()) method(result); if (result.EndTime.Ticks == 0) From e7e1ccdec2d98f369776abc76cb6c61eae13d56b Mon Sep 17 00:00:00 2001 From: Tyler Gill Date: Tue, 10 Apr 2018 10:10:19 -0600 Subject: [PATCH 04/12] Fix auth-id hiding in MS Graph backends. --- Duplicati/Library/Backend/OneDrive/Exceptions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Duplicati/Library/Backend/OneDrive/Exceptions.cs b/Duplicati/Library/Backend/OneDrive/Exceptions.cs index fa8e154ea..d31562da2 100644 --- a/Duplicati/Library/Backend/OneDrive/Exceptions.cs +++ b/Duplicati/Library/Backend/OneDrive/Exceptions.cs @@ -11,7 +11,7 @@ namespace Duplicati.Library.Backend.MicrosoftGraph { public class MicrosoftGraphException : Exception { - private static readonly Regex authorizationHeaderRemover = new Regex(@"Authorization:\s*Bearer\s+[A-Za-z0-9+/=]+", RegexOptions.IgnoreCase); + private static readonly Regex authorizationHeaderRemover = new Regex(@"Authorization:\s*Bearer\s+\S+", RegexOptions.IgnoreCase); public MicrosoftGraphException(HttpResponseMessage response) : this(string.Format("{0}: {1} error from request {2}", response.StatusCode, response.ReasonPhrase, response.RequestMessage.RequestUri), response) From 03fe3729cf9f91dbf39f6aff91e4246f2699a7ef Mon Sep 17 00:00:00 2001 From: Fish Date: Tue, 10 Apr 2018 17:39:57 -0700 Subject: [PATCH 05/12] Cache IsFSCaseSensitive to speed up file counting. "IsFSCaseSensitive" used to call GetEnvironmentVariable() every time it was accessed, which unnecessarily slows down the file counting procedure. Caching the value of this environment variable speeds up things quite a bit. --- Duplicati/Library/Utility/Utility.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/Duplicati/Library/Utility/Utility.cs b/Duplicati/Library/Utility/Utility.cs index 8e840d0aa..1ae969d18 100644 --- a/Duplicati/Library/Utility/Utility.cs +++ b/Duplicati/Library/Utility/Utility.cs @@ -35,6 +35,11 @@ namespace Duplicati.Library.Utility /// public static long DEFAULT_BUFFER_SIZE => SystemContextSettings.Buffersize; + /// + /// A cache of the FileSystemCaseSensitive property, which is computed upon the first access. + /// + private static bool? CachedIsFSCaseSensitive = null; + /// /// Gets the hash algorithm used for calculating a hash /// @@ -834,13 +839,19 @@ namespace Duplicati.Library.Utility { get { - var str = Environment.GetEnvironmentVariable("FILESYSTEM_CASE_SENSITIVE"); + if (!CachedIsFSCaseSensitive.HasValue) + { + var str = Environment.GetEnvironmentVariable("FILESYSTEM_CASE_SENSITIVE"); - // TODO: This should probably be determined by filesystem rather than OS, - // OSX can actually have the disks formated as Case Sensitive, but insensitive is default - Func defaultReply = () => Utility.IsClientLinux && !Utility.IsClientOSX; + // TODO: This should probably be determined by filesystem rather than OS, + // OSX can actually have the disks formated as Case Sensitive, but insensitive is default - return Utility.ParseBool(str, defaultReply); + Func defaultReply = () => Utility.IsClientLinux && !Utility.IsClientOSX; + + CachedIsFSCaseSensitive = Utility.ParseBool(str, defaultReply); + } + + return CachedIsFSCaseSensitive.Value; } } From 904616aff55e93f416eca0396eaf4aacf59a2a7d Mon Sep 17 00:00:00 2001 From: Enrico Polesel Date: Wed, 11 Apr 2018 00:18:38 +0200 Subject: [PATCH 06/12] Solved some codacy warnings on Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs --- .../Backend/OpenStack/OpenStackStorage.cs | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs index 7d94cba02..e8915a629 100644 --- a/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs +++ b/Duplicati/Library/Backend/OpenStack/OpenStackStorage.cs @@ -42,31 +42,31 @@ namespace Duplicati.Library.Backend.OpenStack private const int PAGE_LIMIT = 500; - private string m_container; - private string m_prefix; + private readonly string m_container; + private readonly string m_prefix; private readonly string m_domainName; - private string m_username; - private string m_password; - private string m_authUri; + private readonly string m_username; + private readonly string m_password; + private readonly string m_authUri; private readonly string m_version; - private string m_tenantName; - private string m_apikey; - private string m_region; + private readonly string m_tenantName; + private readonly string m_apikey; + private readonly string m_region; protected string m_simplestorageendpoint; - private WebHelper m_helper = null; + private readonly WebHelper m_helper; private OpenStackAuthResponse.TokenClass m_accessToken; - public static readonly KeyValuePair[] KNOWN_OPENSTACK_PROVIDERS = new KeyValuePair[] { + public static readonly KeyValuePair[] KNOWN_OPENSTACK_PROVIDERS = { new KeyValuePair("Rackspace US", "https://identity.api.rackspacecloud.com/v2.0"), new KeyValuePair("Rackspace UK", "https://lon.identity.api.rackspacecloud.com/v2.0"), new KeyValuePair("OVH Cloud Storage", "https://auth.cloud.ovh.net/v2.0"), new KeyValuePair("Selectel Cloud Storage", "https://auth.selcdn.ru"), }; - public static readonly KeyValuePair[] OPENSTACK_VERSIONS = new KeyValuePair[] { + public static readonly KeyValuePair[] OPENSTACK_VERSIONS = { new KeyValuePair("v2.0", "v2"), new KeyValuePair("v3", "v3"), }; @@ -209,7 +209,7 @@ namespace Duplicati.Library.Backend.OpenStack if (string.IsNullOrEmpty(apikey)) { - this.auth.PasswordCredentials = new PasswordBasedRequest() + this.auth.PasswordCredentials = new PasswordBasedRequest { username = username, password = password, @@ -217,7 +217,7 @@ namespace Duplicati.Library.Backend.OpenStack } else { - this.auth.ApiCredentials = new ApiKeyBasedRequest() + this.auth.ApiCredentials = new ApiKeyBasedRequest { username = username, apiKey = apikey @@ -297,7 +297,7 @@ namespace Duplicati.Library.Backend.OpenStack private class WebHelper : JSONWebHelper { - private OpenStackStorage m_parent; + private readonly OpenStackStorage m_parent; public WebHelper(OpenStackStorage parent) { m_parent = parent; } @@ -376,12 +376,12 @@ namespace Duplicati.Library.Backend.OpenStack } } - private string JoinUrls(string uri, string fragment) + private static string JoinUrls(string uri, string fragment) { fragment = fragment ?? ""; return uri + (uri.EndsWith("/", StringComparison.Ordinal) ? "" : "/") + (fragment.StartsWith("/", StringComparison.Ordinal) ? fragment.Substring(1) : fragment); } - private string JoinUrls(string uri, string fragment1, string fragment2) + private static string JoinUrls(string uri, string fragment1, string fragment2) { return JoinUrls(JoinUrls(uri, fragment1), fragment2); } @@ -610,7 +610,7 @@ namespace Duplicati.Library.Backend.OpenStack foreach(var s in KNOWN_OPENSTACK_PROVIDERS) authuris.AppendLine(string.Format("{0}: {1}", s.Key, s.Value)); - return new List(new CommandLineArgument[] { + return new List(new [] { new CommandLineArgument(DOMAINNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.DomainnameOptionShort, Strings.OpenStack.UsernameOptionLong), new CommandLineArgument(USERNAME_OPTION, CommandLineArgument.ArgumentType.String, Strings.OpenStack.UsernameOptionShort, Strings.OpenStack.UsernameOptionLong), new CommandLineArgument(PASSWORD_OPTION, CommandLineArgument.ArgumentType.Password, Strings.OpenStack.PasswordOptionShort, Strings.OpenStack.PasswordOptionLong(TENANTNAME_OPTION)), From 46170de84ece88d5297aaaa98af32b8225ade15a Mon Sep 17 00:00:00 2001 From: Stephen Gregory Date: Sun, 14 Jan 2018 20:19:07 -0330 Subject: [PATCH 07/12] Extract result serialization to file --- .../Duplicati.Library.Modules.Builtin.csproj | 1 + .../Modules/Builtin/ResultSerializer.cs | 74 +++++++++++++++++++ .../Library/Modules/Builtin/RunScript.cs | 67 +---------------- .../Modules/Builtin/SendHttpMessage.cs | 2 +- .../Modules/Builtin/SendJabberMessage.cs | 2 +- Duplicati/Library/Modules/Builtin/SendMail.cs | 2 +- 6 files changed, 79 insertions(+), 69 deletions(-) create mode 100644 Duplicati/Library/Modules/Builtin/ResultSerializer.cs diff --git a/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj b/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj index a23687eed..e46227a13 100644 --- a/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj +++ b/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj @@ -67,6 +67,7 @@ + diff --git a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs new file mode 100644 index 000000000..1795bdabf --- /dev/null +++ b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.IO; + +namespace Duplicati.Library.Modules.Builtin +{ + class ResultSerializer + { + public static void SerializeResult(string file, object result) + { + using (StreamWriter sw = new StreamWriter(file)) + { + if (result == null) + { + sw.WriteLine("null?"); + } + else if (result is System.Collections.IEnumerable) + { + System.Collections.IEnumerable ie = (System.Collections.IEnumerable)result; + System.Collections.IEnumerator ien = ie.GetEnumerator(); + ien.Reset(); + + while (ien.MoveNext()) + { + object c = ien.Current; + if (c == null) + continue; + + if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + object key = c.GetType().GetProperty("Key").GetValue(c, null); + object value = c.GetType().GetProperty("Value").GetValue(c, null); + sw.WriteLine("{0}: {1}", key, value); + } + else + sw.WriteLine(c); + } + } + else if (result.GetType().IsArray) + { + Array a = (Array)result; + + for (int i = a.GetLowerBound(0); i <= a.GetUpperBound(0); i++) + { + object c = a.GetValue(i); + + if (c == null) + continue; + + if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + object key = c.GetType().GetProperty("Key").GetValue(c, null); + object value = c.GetType().GetProperty("Value").GetValue(c, null); + sw.WriteLine("{0}: {1}", key, value); + } + else + sw.WriteLine(c); + } + } + else if (result is Exception) + { + //No localization, must be parseable by script + Exception e = (Exception)result; + sw.WriteLine("Failed: {0}", e.Message); + sw.WriteLine("Details: {0}", e); + } + else + { + Utility.Utility.PrintSerializeObject(result, sw); + } + } + } + } +} diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs index d900c0362..93bb7f0e9 100644 --- a/Duplicati/Library/Modules/Builtin/RunScript.cs +++ b/Duplicati/Library/Modules/Builtin/RunScript.cs @@ -115,77 +115,12 @@ namespace Duplicati.Library.Modules.Builtin using (TempFile tmpfile = new TempFile()) { - SerializeResult(tmpfile, result); + ResultSerializer.SerializeResult(tmpfile, result); Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, tmpfile, level); } } #endregion - public static void SerializeResult(string file, object result) - { - using(StreamWriter sw = new StreamWriter(file)) - { - if (result == null) - { - sw.WriteLine("null?"); - } - else if (result is System.Collections.IEnumerable) - { - System.Collections.IEnumerable ie = (System.Collections.IEnumerable)result; - System.Collections.IEnumerator ien = ie.GetEnumerator(); - ien.Reset(); - - while (ien.MoveNext()) - { - object c = ien.Current; - if (c == null) - continue; - - if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - object key = c.GetType().GetProperty("Key").GetValue(c, null); - object value = c.GetType().GetProperty("Value").GetValue(c, null); - sw.WriteLine("{0}: {1}", key, value); - } - else - sw.WriteLine(c); - } - } - else if (result.GetType().IsArray) - { - Array a = (Array)result; - - for(int i = a.GetLowerBound(0); i <= a.GetUpperBound(0); i++) - { - object c = a.GetValue(i); - - if (c == null) - continue; - - if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - object key = c.GetType().GetProperty("Key").GetValue(c, null); - object value = c.GetType().GetProperty("Value").GetValue(c, null); - sw.WriteLine("{0}: {1}", key, value); - } - else - sw.WriteLine(c); - } - } - else if (result is Exception) - { - //No localization, must be parseable by script - Exception e = (Exception)result; - sw.WriteLine("Failed: {0}", e.Message); - sw.WriteLine("Details: {0}", e); - } - else - { - Utility.Utility.PrintSerializeObject(result, sw); - } - } - } - private static void Execute(string scriptpath, string eventname, string operationname, ref string remoteurl, ref string[] localpath, int timeout, bool requiredScript, IDictionary options, string datafile, ParsedResultType? level) { try diff --git a/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs b/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs index 662eacd2d..389cb68f0 100644 --- a/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs @@ -321,7 +321,7 @@ namespace Duplicati.Library.Modules.Builtin { if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) using (TempFile tf = new TempFile()) { - RunScript.SerializeResult(tf, result); + ResultSerializer.SerializeResult(tf, result); input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } diff --git a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs index 2f4481666..160fac106 100644 --- a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs @@ -357,7 +357,7 @@ namespace Duplicati.Library.Modules.Builtin if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) using (TempFile tf = new TempFile()) { - RunScript.SerializeResult(tf, result); + ResultSerializer.SerializeResult(tf, result); input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } diff --git a/Duplicati/Library/Modules/Builtin/SendMail.cs b/Duplicati/Library/Modules/Builtin/SendMail.cs index bc5ca49da..62f3162c3 100644 --- a/Duplicati/Library/Modules/Builtin/SendMail.cs +++ b/Duplicati/Library/Modules/Builtin/SendMail.cs @@ -460,7 +460,7 @@ namespace Duplicati.Library.Modules.Builtin if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) using(TempFile tf = new TempFile()) { - RunScript.SerializeResult(tf, result); + ResultSerializer.SerializeResult(tf, result); input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } } From 637e5a4a48d037d8fe862e29ef712c271f8f8a6e Mon Sep 17 00:00:00 2001 From: Stephen Gregory Date: Sun, 14 Jan 2018 21:43:37 -0330 Subject: [PATCH 08/12] Change method signature of ResultSerializer --- Duplicati/Library/Modules/Builtin/ResultSerializer.cs | 4 ++-- Duplicati/Library/Modules/Builtin/RunScript.cs | 2 +- Duplicati/Library/Modules/Builtin/SendHttpMessage.cs | 2 +- Duplicati/Library/Modules/Builtin/SendJabberMessage.cs | 2 +- Duplicati/Library/Modules/Builtin/SendMail.cs | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs index 1795bdabf..f036e1271 100644 --- a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs +++ b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs @@ -6,9 +6,9 @@ namespace Duplicati.Library.Modules.Builtin { class ResultSerializer { - public static void SerializeResult(string file, object result) + public static void SerializeToFile(string filename, object result) { - using (StreamWriter sw = new StreamWriter(file)) + using (StreamWriter sw = new StreamWriter(filename)) { if (result == null) { diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs index 93bb7f0e9..84d35033b 100644 --- a/Duplicati/Library/Modules/Builtin/RunScript.cs +++ b/Duplicati/Library/Modules/Builtin/RunScript.cs @@ -115,7 +115,7 @@ namespace Duplicati.Library.Modules.Builtin using (TempFile tmpfile = new TempFile()) { - ResultSerializer.SerializeResult(tmpfile, result); + ResultSerializer.SerializeToFile(tmpfile, result); Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, tmpfile, level); } } diff --git a/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs b/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs index 389cb68f0..777c7a4db 100644 --- a/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs @@ -321,7 +321,7 @@ namespace Duplicati.Library.Modules.Builtin { if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) using (TempFile tf = new TempFile()) { - ResultSerializer.SerializeResult(tf, result); + ResultSerializer.SerializeToFile(tf, result); input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } diff --git a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs index 160fac106..04866310b 100644 --- a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs @@ -357,7 +357,7 @@ namespace Duplicati.Library.Modules.Builtin if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) using (TempFile tf = new TempFile()) { - ResultSerializer.SerializeResult(tf, result); + ResultSerializer.SerializeToFile(tf, result); input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } diff --git a/Duplicati/Library/Modules/Builtin/SendMail.cs b/Duplicati/Library/Modules/Builtin/SendMail.cs index 62f3162c3..a40d96c75 100644 --- a/Duplicati/Library/Modules/Builtin/SendMail.cs +++ b/Duplicati/Library/Modules/Builtin/SendMail.cs @@ -460,7 +460,7 @@ namespace Duplicati.Library.Modules.Builtin if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) using(TempFile tf = new TempFile()) { - ResultSerializer.SerializeResult(tf, result); + ResultSerializer.SerializeToFile(tf, result); input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); } } From 5a59930ed69e63a4e471a63e27a520b29a7664d5 Mon Sep 17 00:00:00 2001 From: Stephen Gregory Date: Sun, 14 Jan 2018 21:56:15 -0330 Subject: [PATCH 09/12] Clean up serializing result to custom Duplicati format --- .../Modules/Builtin/ResultSerializer.cs | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs index f036e1271..4e28e583c 100644 --- a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs +++ b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.IO; @@ -14,38 +15,44 @@ namespace Duplicati.Library.Modules.Builtin { sw.WriteLine("null?"); } - else if (result is System.Collections.IEnumerable) + else if (result is IEnumerable) { - System.Collections.IEnumerable ie = (System.Collections.IEnumerable)result; - System.Collections.IEnumerator ien = ie.GetEnumerator(); - ien.Reset(); + IEnumerable resultEnumerable = (IEnumerable)result; + IEnumerator resultEnumerator = resultEnumerable.GetEnumerator(); + resultEnumerator.Reset(); - while (ien.MoveNext()) + while (resultEnumerator.MoveNext()) { - object c = ien.Current; - if (c == null) - continue; - - if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + object current = resultEnumerator.Current; + if (current == null) { - object key = c.GetType().GetProperty("Key").GetValue(c, null); - object value = c.GetType().GetProperty("Value").GetValue(c, null); + continue; + } + + if (current.GetType().IsGenericType && !current.GetType().IsGenericTypeDefinition && current.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + object key = current.GetType().GetProperty("Key").GetValue(current, null); + object value = current.GetType().GetProperty("Value").GetValue(current, null); sw.WriteLine("{0}: {1}", key, value); } else - sw.WriteLine(c); + { + sw.WriteLine(current); + } } } else if (result.GetType().IsArray) { - Array a = (Array)result; + Array array = (Array)result; - for (int i = a.GetLowerBound(0); i <= a.GetUpperBound(0); i++) + for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); i++) { - object c = a.GetValue(i); + object c = array.GetValue(i); if (c == null) + { continue; + } if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) { @@ -54,15 +61,17 @@ namespace Duplicati.Library.Modules.Builtin sw.WriteLine("{0}: {1}", key, value); } else + { sw.WriteLine(c); + } } } else if (result is Exception) { //No localization, must be parseable by script - Exception e = (Exception)result; - sw.WriteLine("Failed: {0}", e.Message); - sw.WriteLine("Details: {0}", e); + Exception exception = (Exception)result; + sw.WriteLine("Failed: {0}", exception.Message); + sw.WriteLine("Details: {0}", exception); } else { From b7bda1d1c4f2a40afb37e22203ecd4d8be6ac860 Mon Sep 17 00:00:00 2001 From: Stephen Gregory Date: Mon, 9 Apr 2018 18:53:37 -0230 Subject: [PATCH 10/12] Add result serializers --- ...ti.Library.Modules.Builtin.UnitTest.csproj | 61 ++++++++++++++ .../Properties/AssemblyInfo.cs | 20 +++++ .../ResultFormatSerializerProviderTest.cs | 23 +++++ .../packages.config | 5 ++ Duplicati.sln | 6 ++ .../Duplicati.Library.Modules.Builtin.csproj | 9 +- .../Builtin/Properties/AssemblyInfo.cs | 1 + .../Modules/Builtin/ResultExportFormat.cs | 9 ++ .../DuplicatiFormatSerializer.cs | 84 +++++++++++++++++++ .../IResultFormatSerializer.cs | 7 ++ .../JsonFormatSerializer.cs | 21 +++++ .../ResultFormatSerializerProvider.cs | 25 ++++++ .../Modules/Builtin/ResultSerializer.cs | 83 ------------------ .../Library/Modules/Builtin/RunScript.cs | 31 ++++++- .../Modules/Builtin/SendHttpMessage.cs | 42 ++++++++-- .../Modules/Builtin/SendJabberMessage.cs | 41 +++++++-- Duplicati/Library/Modules/Builtin/SendMail.cs | 41 +++++++-- Duplicati/Library/Modules/Builtin/Strings.cs | 10 ++- 18 files changed, 416 insertions(+), 103 deletions(-) create mode 100644 Duplicati.Library.Modules.Builtin.UnitTest/Duplicati.Library.Modules.Builtin.UnitTest.csproj create mode 100644 Duplicati.Library.Modules.Builtin.UnitTest/Properties/AssemblyInfo.cs create mode 100644 Duplicati.Library.Modules.Builtin.UnitTest/ResultSerialization/ResultFormatSerializerProviderTest.cs create mode 100644 Duplicati.Library.Modules.Builtin.UnitTest/packages.config create mode 100644 Duplicati/Library/Modules/Builtin/ResultExportFormat.cs create mode 100644 Duplicati/Library/Modules/Builtin/ResultSerialization/DuplicatiFormatSerializer.cs create mode 100644 Duplicati/Library/Modules/Builtin/ResultSerialization/IResultFormatSerializer.cs create mode 100644 Duplicati/Library/Modules/Builtin/ResultSerialization/JsonFormatSerializer.cs create mode 100644 Duplicati/Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs delete mode 100644 Duplicati/Library/Modules/Builtin/ResultSerializer.cs diff --git a/Duplicati.Library.Modules.Builtin.UnitTest/Duplicati.Library.Modules.Builtin.UnitTest.csproj b/Duplicati.Library.Modules.Builtin.UnitTest/Duplicati.Library.Modules.Builtin.UnitTest.csproj new file mode 100644 index 000000000..e3e161590 --- /dev/null +++ b/Duplicati.Library.Modules.Builtin.UnitTest/Duplicati.Library.Modules.Builtin.UnitTest.csproj @@ -0,0 +1,61 @@ + + + + Debug + AnyCPU + {3E5B9D4B-962A-4D80-8922-F2150BB48343} + Library + Properties + Duplicati.Library.Modules.Builtin.UnitTest + Duplicati.Library.Modules.Builtin.UnitTest + v4.6.1 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll + + + + + + + + + + + + + + {52826615-7964-47fe-b4b3-1b2dbdf605b9} + Duplicati.Library.Modules.Builtin + + + + + \ No newline at end of file diff --git a/Duplicati.Library.Modules.Builtin.UnitTest/Properties/AssemblyInfo.cs b/Duplicati.Library.Modules.Builtin.UnitTest/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..83f857c9d --- /dev/null +++ b/Duplicati.Library.Modules.Builtin.UnitTest/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("Duplicati.Library.Modules.Builtin.UnitTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Duplicati.Library.Modules.Builtin.UnitTest")] +[assembly: AssemblyCopyright("Copyright © 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("3e5b9d4b-962a-4d80-8922-f2150bb48343")] + +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Duplicati.Library.Modules.Builtin.UnitTest/ResultSerialization/ResultFormatSerializerProviderTest.cs b/Duplicati.Library.Modules.Builtin.UnitTest/ResultSerialization/ResultFormatSerializerProviderTest.cs new file mode 100644 index 000000000..fcfbb75fe --- /dev/null +++ b/Duplicati.Library.Modules.Builtin.UnitTest/ResultSerialization/ResultFormatSerializerProviderTest.cs @@ -0,0 +1,23 @@ +using Duplicati.Library.Modules.Builtin.ResultSerialization; +using NUnit.Framework; + +namespace Duplicati.Library.Modules.Builtin.UnitTest +{ + [TestFixture] + public class ResultFormatSerializerProviderTest + { + [Test] + public void TestGetSerializerGivenDuplicatiReturnsDuplicatiSerializer() + { + IResultFormatSerializer serializer = ResultFormatSerializerProvider.GetSerializer(ResultExportFormat.Duplicati); + Assert.AreEqual(typeof(DuplicatiFormatSerializer), serializer.GetType()); + } + + [Test] + public void TestGetSerializerGivenJsonReturnsJsonSerializer() + { + IResultFormatSerializer serializer = ResultFormatSerializerProvider.GetSerializer(ResultExportFormat.Json); + Assert.AreEqual(typeof(JsonFormatSerializer), serializer.GetType()); + } + } +} diff --git a/Duplicati.Library.Modules.Builtin.UnitTest/packages.config b/Duplicati.Library.Modules.Builtin.UnitTest/packages.config new file mode 100644 index 000000000..77de612bc --- /dev/null +++ b/Duplicati.Library.Modules.Builtin.UnitTest/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Duplicati.sln b/Duplicati.sln index 99721e4a6..2fdf32fcf 100644 --- a/Duplicati.sln +++ b/Duplicati.sln @@ -99,6 +99,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.S EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Backend.Rclone", "Duplicati\Library\Backend\Rclone\Duplicati.Library.Backend.Rclone.csproj", "{851A1CB8-3CEB-41B4-956F-34D760D2A8E5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Duplicati.Library.Modules.Builtin.UnitTest", "Duplicati.Library.Modules.Builtin.UnitTest\Duplicati.Library.Modules.Builtin.UnitTest.csproj", "{3E5B9D4B-962A-4D80-8922-F2150BB48343}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -297,6 +299,10 @@ Global {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 {851A1CB8-3CEB-41B4-956F-34D760D2A8E5}.Release|Any CPU.Build.0 = Release|Any CPU + {3E5B9D4B-962A-4D80-8922-F2150BB48343}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3E5B9D4B-962A-4D80-8922-F2150BB48343}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3E5B9D4B-962A-4D80-8922-F2150BB48343}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3E5B9D4B-962A-4D80-8922-F2150BB48343}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj b/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj index e46227a13..943ff5493 100644 --- a/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj +++ b/Duplicati/Library/Modules/Builtin/Duplicati.Library.Modules.Builtin.csproj @@ -67,7 +67,7 @@ - + @@ -77,6 +77,10 @@ DnsLite.cs + + + + @@ -129,4 +133,7 @@ PreserveNewest + + + \ No newline at end of file diff --git a/Duplicati/Library/Modules/Builtin/Properties/AssemblyInfo.cs b/Duplicati/Library/Modules/Builtin/Properties/AssemblyInfo.cs index 399ba857a..03f8cb13a 100644 --- a/Duplicati/Library/Modules/Builtin/Properties/AssemblyInfo.cs +++ b/Duplicati/Library/Modules/Builtin/Properties/AssemblyInfo.cs @@ -53,3 +53,4 @@ using System.Runtime.InteropServices; // [assembly: AssemblyVersion("2.0.0.7")] [assembly: AssemblyVersion("2.0.0.7")] [assembly: AssemblyFileVersion("2.0.0.7")] +[assembly: InternalsVisibleTo("Duplicati.Library.Modules.Builtin.UnitTest")] diff --git a/Duplicati/Library/Modules/Builtin/ResultExportFormat.cs b/Duplicati/Library/Modules/Builtin/ResultExportFormat.cs new file mode 100644 index 000000000..b5d7ba58f --- /dev/null +++ b/Duplicati/Library/Modules/Builtin/ResultExportFormat.cs @@ -0,0 +1,9 @@ + +namespace Duplicati.Library.Modules.Builtin +{ + public enum ResultExportFormat + { + Duplicati, + Json + } +} diff --git a/Duplicati/Library/Modules/Builtin/ResultSerialization/DuplicatiFormatSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerialization/DuplicatiFormatSerializer.cs new file mode 100644 index 000000000..be84a1e40 --- /dev/null +++ b/Duplicati/Library/Modules/Builtin/ResultSerialization/DuplicatiFormatSerializer.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace Duplicati.Library.Modules.Builtin.ResultSerialization +{ + class DuplicatiFormatSerializer : IResultFormatSerializer + { + public string Serialize(object result) + { + StringBuilder sb = new StringBuilder(); + + if (result == null) + { + sb.Append("null?"); + } + else if (result is IEnumerable) + { + IEnumerable resultEnumerable = (IEnumerable)result; + IEnumerator resultEnumerator = resultEnumerable.GetEnumerator(); + resultEnumerator.Reset(); + + while (resultEnumerator.MoveNext()) + { + object current = resultEnumerator.Current; + if (current == null) + { + continue; + } + + if (current.GetType().IsGenericType && !current.GetType().IsGenericTypeDefinition && current.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + object key = current.GetType().GetProperty("Key").GetValue(current, null); + object value = current.GetType().GetProperty("Value").GetValue(current, null); + sb.AppendFormat("{0}: {1}", key, value).AppendLine(); + } + else + { + sb.AppendLine(current.ToString()); + } + } + } + else if (result.GetType().IsArray) + { + Array array = (Array)result; + + for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); i++) + { + object c = array.GetValue(i); + + if (c == null) + { + continue; + } + + if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) + { + object key = c.GetType().GetProperty("Key").GetValue(c, null); + object value = c.GetType().GetProperty("Value").GetValue(c, null); + sb.AppendFormat("{0}: {1}", key, value).AppendLine(); + } + else + { + sb.AppendLine(c.ToString()); + } + } + } + else if (result is Exception) + { + //No localization, must be parseable by script + Exception exception = (Exception)result; + sb.AppendFormat("Failed: {0}", exception.Message).AppendLine(); + sb.AppendFormat("Details: {0}", exception).AppendLine(); + } + else + { + Utility.Utility.PrintSerializeObject(result, sb); + } + + return sb.ToString(); + } + } +} diff --git a/Duplicati/Library/Modules/Builtin/ResultSerialization/IResultFormatSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerialization/IResultFormatSerializer.cs new file mode 100644 index 000000000..57f2ef07c --- /dev/null +++ b/Duplicati/Library/Modules/Builtin/ResultSerialization/IResultFormatSerializer.cs @@ -0,0 +1,7 @@ +namespace Duplicati.Library.Modules.Builtin +{ + public interface IResultFormatSerializer + { + string Serialize(object result); + } +} diff --git a/Duplicati/Library/Modules/Builtin/ResultSerialization/JsonFormatSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerialization/JsonFormatSerializer.cs new file mode 100644 index 000000000..25e3cc32c --- /dev/null +++ b/Duplicati/Library/Modules/Builtin/ResultSerialization/JsonFormatSerializer.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Duplicati.Library.Modules.Builtin.ResultSerialization +{ + class JsonFormatSerializer : IResultFormatSerializer + { + public string Serialize(object result) + { + return JsonConvert.SerializeObject(result, new JsonSerializerSettings() + { + ReferenceLoopHandling = ReferenceLoopHandling.Ignore, + Converters = new List() + { + new StringEnumConverter() + } + }); + } + } +} diff --git a/Duplicati/Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs b/Duplicati/Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs new file mode 100644 index 000000000..e1d184aab --- /dev/null +++ b/Duplicati/Library/Modules/Builtin/ResultSerialization/ResultFormatSerializerProvider.cs @@ -0,0 +1,25 @@ +using System; + +namespace Duplicati.Library.Modules.Builtin.ResultSerialization +{ + public static class ResultFormatSerializerProvider + { + public static IResultFormatSerializer GetSerializer(ResultExportFormat format) { + switch (format) + { + case ResultExportFormat.Duplicati: + { + return new DuplicatiFormatSerializer(); + } + case ResultExportFormat.Json: + { + return new JsonFormatSerializer(); + } + default: + { + throw new NotImplementedException(string.Format("Cannot export to {0} format yet", format)); + } + } + } + } +} diff --git a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs b/Duplicati/Library/Modules/Builtin/ResultSerializer.cs deleted file mode 100644 index 4e28e583c..000000000 --- a/Duplicati/Library/Modules/Builtin/ResultSerializer.cs +++ /dev/null @@ -1,83 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; - -namespace Duplicati.Library.Modules.Builtin -{ - class ResultSerializer - { - public static void SerializeToFile(string filename, object result) - { - using (StreamWriter sw = new StreamWriter(filename)) - { - if (result == null) - { - sw.WriteLine("null?"); - } - else if (result is IEnumerable) - { - IEnumerable resultEnumerable = (IEnumerable)result; - IEnumerator resultEnumerator = resultEnumerable.GetEnumerator(); - resultEnumerator.Reset(); - - while (resultEnumerator.MoveNext()) - { - object current = resultEnumerator.Current; - if (current == null) - { - continue; - } - - if (current.GetType().IsGenericType && !current.GetType().IsGenericTypeDefinition && current.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - object key = current.GetType().GetProperty("Key").GetValue(current, null); - object value = current.GetType().GetProperty("Value").GetValue(current, null); - sw.WriteLine("{0}: {1}", key, value); - } - else - { - sw.WriteLine(current); - } - } - } - else if (result.GetType().IsArray) - { - Array array = (Array)result; - - for (int i = array.GetLowerBound(0); i <= array.GetUpperBound(0); i++) - { - object c = array.GetValue(i); - - if (c == null) - { - continue; - } - - if (c.GetType().IsGenericType && !c.GetType().IsGenericTypeDefinition && c.GetType().GetGenericTypeDefinition() == typeof(KeyValuePair<,>)) - { - object key = c.GetType().GetProperty("Key").GetValue(c, null); - object value = c.GetType().GetProperty("Value").GetValue(c, null); - sw.WriteLine("{0}: {1}", key, value); - } - else - { - sw.WriteLine(c); - } - } - } - else if (result is Exception) - { - //No localization, must be parseable by script - Exception exception = (Exception)result; - sw.WriteLine("Failed: {0}", exception.Message); - sw.WriteLine("Details: {0}", exception); - } - else - { - Utility.Utility.PrintSerializeObject(result, sw); - } - } - } - } -} diff --git a/Duplicati/Library/Modules/Builtin/RunScript.cs b/Duplicati/Library/Modules/Builtin/RunScript.cs index 84d35033b..937d10e02 100644 --- a/Duplicati/Library/Modules/Builtin/RunScript.cs +++ b/Duplicati/Library/Modules/Builtin/RunScript.cs @@ -22,6 +22,7 @@ using System.Text; using System.Collections.Generic; using Duplicati.Library.Utility; using Duplicati.Library.Interface; +using Duplicati.Library.Modules.Builtin.ResultSerialization; namespace Duplicati.Library.Modules.Builtin { @@ -37,6 +38,8 @@ namespace Duplicati.Library.Modules.Builtin private const string REQUIRED_OPTION = "run-script-before-required"; private const string TIMEOUT_OPTION = "run-script-timeout"; + private const string RESULT_FORMAT_OPTION = "run-script-result-output-format"; + private const string DEFAULT_TIMEOUT = "60s"; private string m_requiredScript = null; @@ -48,6 +51,7 @@ namespace Duplicati.Library.Modules.Builtin private string m_remoteurl; private string[] m_localpath; private IDictionary m_options; + private IResultFormatSerializer resultFormatSerializer; #region IGenericModule implementation public void Configure(IDictionary commandlineOptions) @@ -56,11 +60,23 @@ namespace Duplicati.Library.Modules.Builtin commandlineOptions.TryGetValue(REQUIRED_OPTION, out m_requiredScript); commandlineOptions.TryGetValue(FINISH_OPTION, out m_finishScript); + string tmpResultFormat; + ResultExportFormat resultFormat; + if (!commandlineOptions.TryGetValue(RESULT_FORMAT_OPTION, out tmpResultFormat)) { + resultFormat = ResultExportFormat.Duplicati; + } + else if (!Enum.TryParse(tmpResultFormat, true, out resultFormat)) { + resultFormat = ResultExportFormat.Duplicati; + } + + resultFormatSerializer = ResultFormatSerializerProvider.GetSerializer(resultFormat); + string t; if (!commandlineOptions.TryGetValue(TIMEOUT_OPTION, out t)) t = DEFAULT_TIMEOUT; m_timeout = (int)Utility.Timeparser.ParseTimeSpan(t).TotalMilliseconds; + m_options = commandlineOptions; } @@ -73,10 +89,18 @@ namespace Duplicati.Library.Modules.Builtin { get { + string[] resultOutputFormatOptions = new string[] { ResultExportFormat.Duplicati.ToString(), ResultExportFormat.Json.ToString() }; return new List(new Duplicati.Library.Interface.ICommandLineArgument[] { new Duplicati.Library.Interface.CommandLineArgument(STARTUP_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.RunScript.StartupoptionShort, Strings.RunScript.StartupoptionLong), new Duplicati.Library.Interface.CommandLineArgument(FINISH_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.RunScript.FinishoptionShort, Strings.RunScript.FinishoptionLong), new Duplicati.Library.Interface.CommandLineArgument(REQUIRED_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Path, Strings.RunScript.RequiredoptionShort, Strings.RunScript.RequiredoptionLong), + new CommandLineArgument(RESULT_FORMAT_OPTION, + CommandLineArgument.ArgumentType.Enumeration, + Strings.RunScript.ResultFormatShort, + Strings.RunScript.ResultFormatLong(resultOutputFormatOptions), + ResultExportFormat.Duplicati.ToString(), + null, + resultOutputFormatOptions), new Duplicati.Library.Interface.CommandLineArgument(TIMEOUT_OPTION, Duplicati.Library.Interface.CommandLineArgument.ArgumentType.Timespan, Strings.RunScript.TimeoutoptionShort, Strings.RunScript.TimeoutoptionLong, DEFAULT_TIMEOUT), }); } @@ -115,7 +139,12 @@ namespace Duplicati.Library.Modules.Builtin using (TempFile tmpfile = new TempFile()) { - ResultSerializer.SerializeToFile(tmpfile, result); + using (var streamWriter = new StreamWriter(tmpfile)) + { + string resultExport = resultFormatSerializer.Serialize(result); + streamWriter.Write(resultExport); + } + Execute(m_finishScript, "AFTER", m_operationName, ref m_remoteurl, ref m_localpath, m_timeout, false, m_options, tmpfile, level); } } diff --git a/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs b/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs index 777c7a4db..1732a1682 100644 --- a/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendHttpMessage.cs @@ -1,5 +1,6 @@ using Duplicati.Library.Interface; using Duplicati.Library.Logging; +using Duplicati.Library.Modules.Builtin.ResultSerialization; using Duplicati.Library.Utility; using System; using System.Collections.Generic; @@ -43,6 +44,10 @@ namespace Duplicati.Library.Modules.Builtin { /// Option used to specify if reports are sent for other operations than backups /// private const string OPTION_SENDALL = "send-http-any-operation"; + /// + /// Option used to specify what format the result is sent in. + /// + private const string OPTION_RESULT_FORMAT = "send-http-result-output-format"; #endregion @@ -83,6 +88,10 @@ namespace Duplicati.Library.Modules.Builtin { /// The parsed result level /// private string m_parsedresultlevel = string.Empty; + /// + /// Serializer to use when serializing the message. + /// + private IResultFormatSerializer resultFormatSerializer; /// /// The HTTP report URL @@ -143,13 +152,21 @@ namespace Duplicati.Library.Modules.Builtin { { get { + string[] resultOutputFormatOptions = new string[] { ResultExportFormat.Duplicati.ToString(), ResultExportFormat.Json.ToString() }; return new List(new ICommandLineArgument[] { new CommandLineArgument(OPTION_URL, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpurlShort, Strings.SendHttpMessage.SendhttpurlLong), new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageShort, Strings.SendHttpMessage.SendhttpmessageLong, DEFAULT_MESSAGE), new CommandLineArgument(OPTION_MESSAGE_PARAMETER_NAME, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpmessageparameternameShort, Strings.SendHttpMessage.SendhttpmessageparameternameLong, DEFAULT_MESSAGE_PARAMETER_NAME), new CommandLineArgument(OPTION_EXTRA_PARAMETERS, CommandLineArgument.ArgumentType.String, Strings.SendHttpMessage.SendhttpextraparametersShort, Strings.SendHttpMessage.SendhttpextraparametersLong), new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.SendHttpMessage.SendhttplevelShort, Strings.SendHttpMessage.SendhttplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), - new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong) + new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong), + new CommandLineArgument(OPTION_RESULT_FORMAT, + CommandLineArgument.ArgumentType.Enumeration, + Strings.SendHttpMessage.ResultFormatShort, + Strings.SendHttpMessage.ResultFormatLong(resultOutputFormatOptions), + ResultExportFormat.Duplicati.ToString(), + null, + resultOutputFormatOptions) }); } } @@ -173,6 +190,19 @@ namespace Duplicati.Library.Modules.Builtin { m_options = commandlineOptions; + string tmpResultFormat; + ResultExportFormat resultFormat; + if (!commandlineOptions.TryGetValue(OPTION_RESULT_FORMAT, out tmpResultFormat)) + { + resultFormat = ResultExportFormat.Duplicati; + } + else if (!Enum.TryParse(tmpResultFormat, true, out resultFormat)) + { + resultFormat = ResultExportFormat.Duplicati; + } + + resultFormatSerializer = ResultFormatSerializerProvider.GetSerializer(resultFormat); + string tmp; commandlineOptions.TryGetValue(OPTION_SENDLEVEL, out tmp); if (!string.IsNullOrEmpty(tmp)) @@ -318,12 +348,12 @@ namespace Duplicati.Library.Modules.Builtin { input = Regex.Replace(input, "\\%REMOTEURL\\%", m_remoteurl ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); input = Regex.Replace(input, "\\%LOCALPATH\\%", m_localpath == null ? "" : string.Join(System.IO.Path.PathSeparator.ToString(), m_localpath), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); input = Regex.Replace(input, "\\%PARSEDRESULT\\%", m_parsedresultlevel ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) - using (TempFile tf = new TempFile()) - { - ResultSerializer.SerializeToFile(tf, result); - input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } + { + string resultExport = resultFormatSerializer.Serialize(result); + input = Regex.Replace(input, "\\%RESULT\\%", resultExport, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } foreach (KeyValuePair kv in m_options) input = Regex.Replace(input, "\\%" + kv.Key + "\\%", kv.Value ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); diff --git a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs index 04866310b..2ec35d583 100644 --- a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs @@ -11,8 +11,8 @@ using System.Text.RegularExpressions; using DnsLib; using Duplicati.Library.Interface; using Duplicati.Library.Logging; -using Duplicati.Library.Utility; using System.Net.NetworkInformation; +using Duplicati.Library.Modules.Builtin.ResultSerialization; namespace Duplicati.Library.Modules.Builtin { @@ -49,6 +49,10 @@ namespace Duplicati.Library.Modules.Builtin /// Option used to specify if reports are sent for other operations than backups /// private const string OPTION_SENDALL = "send-xmpp-any-operation"; + /// + /// Option used to specify what format the result is sent in. + /// + private const string OPTION_RESULT_FORMAT = "send-xmpp-result-output-format"; #endregion @@ -85,6 +89,10 @@ namespace Duplicati.Library.Modules.Builtin /// The parsed result level if /// private string m_parsedresultlevel = string.Empty; + /// + /// Serializer to use when serializing the message. + /// + private IResultFormatSerializer resultFormatSerializer; /// /// The server username @@ -145,6 +153,7 @@ namespace Duplicati.Library.Modules.Builtin { get { + string[] resultOutputFormatOptions = new string[] { ResultExportFormat.Duplicati.ToString(), ResultExportFormat.Json.ToString() }; return new List(new ICommandLineArgument[] { new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpptoShort, Strings.SendJabberMessage.SendxmpptoLong), new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppmessageShort, Strings.SendJabberMessage.SendxmppmessageLong, DEFAULT_MESSAGE), @@ -152,6 +161,13 @@ namespace Duplicati.Library.Modules.Builtin new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmpppasswordShort, Strings.SendJabberMessage.SendxmpppasswordLong), new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.Enumeration, Strings.SendJabberMessage.SendxmpplevelShort, Strings.SendJabberMessage.SendxmpplevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string[] { "All" } ).ToArray()), new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendJabberMessage.SendxmppanyoperationShort, Strings.SendJabberMessage.SendxmppanyoperationLong), + new CommandLineArgument(OPTION_RESULT_FORMAT, + CommandLineArgument.ArgumentType.Enumeration, + Strings.SendJabberMessage.ResultFormatShort, + Strings.SendJabberMessage.ResultFormatLong(resultOutputFormatOptions), + ResultExportFormat.Duplicati.ToString(), + null, + resultOutputFormatOptions) }); } } @@ -172,6 +188,19 @@ namespace Duplicati.Library.Modules.Builtin commandlineOptions.TryGetValue(OPTION_MESSAGE, out m_body); m_options = commandlineOptions; + string tmpResultFormat; + ResultExportFormat resultFormat; + if (!commandlineOptions.TryGetValue(OPTION_RESULT_FORMAT, out tmpResultFormat)) + { + resultFormat = ResultExportFormat.Duplicati; + } + else if (!Enum.TryParse(tmpResultFormat, true, out resultFormat)) + { + resultFormat = ResultExportFormat.Duplicati; + } + + resultFormatSerializer = ResultFormatSerializerProvider.GetSerializer(resultFormat); + string tmp; commandlineOptions.TryGetValue(OPTION_SENDLEVEL, out tmp); if (!string.IsNullOrEmpty(tmp)) @@ -354,12 +383,12 @@ namespace Duplicati.Library.Modules.Builtin input = Regex.Replace(input, "\\%REMOTEURL\\%", m_remoteurl ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); input = Regex.Replace(input, "\\%LOCALPATH\\%", m_localpath == null ? "" : string.Join(System.IO.Path.PathSeparator.ToString(), m_localpath), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); input = Regex.Replace(input, "\\%PARSEDRESULT\\%", m_parsedresultlevel ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) - using (TempFile tf = new TempFile()) - { - ResultSerializer.SerializeToFile(tf, result); - input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } + { + string resultExport = resultFormatSerializer.Serialize(result); + input = Regex.Replace(input, "\\%RESULT\\%", resultExport, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } foreach (KeyValuePair kv in m_options) input = Regex.Replace(input, "\\%" + kv.Key + "\\%", kv.Value ?? "", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); diff --git a/Duplicati/Library/Modules/Builtin/SendMail.cs b/Duplicati/Library/Modules/Builtin/SendMail.cs index a40d96c75..0478dc623 100644 --- a/Duplicati/Library/Modules/Builtin/SendMail.cs +++ b/Duplicati/Library/Modules/Builtin/SendMail.cs @@ -14,6 +14,7 @@ using System.Net.NetworkInformation; using System.Net.Sockets; using MailKit.Net.Smtp; using MimeKit; +using Duplicati.Library.Modules.Builtin.ResultSerialization; namespace Duplicati.Library.Modules.Builtin { @@ -62,6 +63,10 @@ namespace Duplicati.Library.Modules.Builtin /// Option used to specify if reports are sent for other operations than backups /// private const string OPTION_SENDALL = "send-mail-any-operation"; + /// + /// Option used to specify what format the result is sent in. + /// + private const string OPTION_RESULT_FORMAT = "send-mail-result-output-format"; #endregion @@ -105,6 +110,10 @@ namespace Duplicati.Library.Modules.Builtin /// The parsed result level /// private string m_parsedresultlevel = string.Empty; + /// + /// Serializer to use when serializing the message. + /// + private IResultFormatSerializer resultFormatSerializer; /// /// The server url to use @@ -177,6 +186,7 @@ namespace Duplicati.Library.Modules.Builtin { get { + string[] resultOutputFormatOptions = new string[] { ResultExportFormat.Duplicati.ToString(), ResultExportFormat.Json.ToString() }; return new List(new ICommandLineArgument[] { new CommandLineArgument(OPTION_RECIPIENT, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionRecipientShort, Strings.SendMail.OptionRecipientLong), new CommandLineArgument(OPTION_SENDER, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSenderShort, Strings.SendMail.OptionSenderLong, DEFAULT_SENDER), @@ -187,6 +197,14 @@ namespace Duplicati.Library.Modules.Builtin new CommandLineArgument(OPTION_PASSWORD, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionPasswordShort, Strings.SendMail.OptionPasswordLong), new CommandLineArgument(OPTION_SENDLEVEL, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionSendlevelShort, Strings.SendMail.OptionSendlevelLong(ParsedResultType.Success.ToString(), ParsedResultType.Warning.ToString(), ParsedResultType.Error.ToString(), ParsedResultType.Fatal.ToString(), "All"), DEFAULT_LEVEL, null, Enum.GetNames(typeof(ParsedResultType)).Union(new string [] { "All" }).ToArray()), new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendMail.OptionSendallShort, Strings.SendMail.OptionSendallLong), + new CommandLineArgument(OPTION_SENDALL, CommandLineArgument.ArgumentType.Boolean, Strings.SendHttpMessage.SendhttpanyoperationShort, Strings.SendHttpMessage.SendhttpanyoperationLong), + new CommandLineArgument(OPTION_RESULT_FORMAT, + CommandLineArgument.ArgumentType.Enumeration, + Strings.SendMail.ResultFormatShort, + Strings.SendMail.ResultFormatLong(resultOutputFormatOptions), + ResultExportFormat.Duplicati.ToString(), + null, + resultOutputFormatOptions) }); } } @@ -210,6 +228,20 @@ namespace Duplicati.Library.Modules.Builtin commandlineOptions.TryGetValue(OPTION_BODY, out m_body); m_options = commandlineOptions; + + string tmpResultFormat; + ResultExportFormat resultFormat; + if (!commandlineOptions.TryGetValue(OPTION_RESULT_FORMAT, out tmpResultFormat)) + { + resultFormat = ResultExportFormat.Duplicati; + } + else if (!Enum.TryParse(tmpResultFormat, true, out resultFormat)) + { + resultFormat = ResultExportFormat.Duplicati; + } + + resultFormatSerializer = ResultFormatSerializerProvider.GetSerializer(resultFormat); + string tmp; commandlineOptions.TryGetValue(OPTION_SENDLEVEL, out tmp); if (!string.IsNullOrEmpty(tmp)) @@ -458,11 +490,10 @@ namespace Duplicati.Library.Modules.Builtin else { if (input.IndexOf("%RESULT%", StringComparison.OrdinalIgnoreCase) >= 0) - using(TempFile tf = new TempFile()) - { - ResultSerializer.SerializeToFile(tf, result); - input = Regex.Replace(input, "\\%RESULT\\%", System.IO.File.ReadAllText(tf), RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); - } + { + string resultExport = resultFormatSerializer.Serialize(result); + input = Regex.Replace(input, "\\%RESULT\\%", resultExport, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); + } } foreach (KeyValuePair kv in m_options) diff --git a/Duplicati/Library/Modules/Builtin/Strings.cs b/Duplicati/Library/Modules/Builtin/Strings.cs index 60f86ddd5..292d02c46 100644 --- a/Duplicati/Library/Modules/Builtin/Strings.cs +++ b/Duplicati/Library/Modules/Builtin/Strings.cs @@ -1,6 +1,6 @@ using Duplicati.Library.Localization.Short; using System; - +using System.Collections; namespace Duplicati.Library.Modules.Builtin.Strings { internal static class ConsolePasswordInput { @@ -55,6 +55,8 @@ namespace Duplicati.Library.Modules.Builtin.Strings { public static string InvalidExitCodeError(string script, int exitcode) { return LC.L(@"The script ""{0}"" returned with exit code {1}", script, exitcode); } public static string RequiredoptionLong { get { return LC.L(@"Executes a script before performing an operation. The operation will block until the script has completed or timed out. If the script returns a non-zero error code or times out, the operation will be aborted."); } } public static string RequiredoptionShort { get { return LC.L(@"Run a required script on startup"); } } + public static string ResultFormatShort { get { return LC.L(@"Selects the output format for results"); } } + public static string ResultFormatLong(IEnumerable options) { return LC.L(@"Selects the output format for results. Available formats: {0}", string.Join(", ", options)); } public static string ScriptExecuteError(string script, string message) { return LC.L(@"Error while executing script ""{0}"": {1}", script, message); } public static string ScriptTimeoutError(string script) { return LC.L(@"Execution of the script ""{0}"" timed out", script); } public static string StartupoptionLong { get { return LC.L(@"Executes a script before performing an operation. The operation will block until the script has completed or timed out."); } } @@ -102,6 +104,8 @@ To enable SMTP over SSL, use the format smtps://example.com. To enable SMTP STAR public static string OptionServerShort { get { return LC.L(@"SMTP Url"); } } public static string OptionSubjectLong(string optionname) { return LC.L(@"This setting supplies the email subject. Values are replaced as described in the description for --{0}.", optionname); } public static string OptionSubjectShort { get { return LC.L(@"The email subject"); } } + public static string ResultFormatShort { get { return LC.L(@"Selects the output format for results"); } } + public static string ResultFormatLong(IEnumerable options) { return LC.L(@"Selects the output format for results. Available formats: {0}", string.Join(", ", options)); } public static string OptionUsernameLong { get { return LC.L(@"The username used to authenticate with the SMTP server if required."); } } public static string OptionUsernameShort { get { return LC.L(@"SMTP Username"); } } public static string SendMailFailedError(string message) { return LC.L(@"Failed to send email: {0}", message); } @@ -122,6 +126,8 @@ In the message, certain tokens are replaced: %PARSEDRESULT% - The parsed result, if the operation is a backup. Possible values are: Error, Warning, Success All command line options are also reported within %value%, e.g. %volsize%. Any unknown/unset value is removed."); } } + public static string ResultFormatShort { get { return LC.L(@"Selects the output format for results"); } } + public static string ResultFormatLong(IEnumerable options) { return LC.L(@"Selects the output format for results. Available formats: {0}", string.Join(", ", options)); } public static string SendxmppusernameShort { get { return LC.L(@"The XMPP username"); } } public static string SendxmppusernameLong { get { return LC.L(@"The username for the account that will send the message, including the hostname. I.e. ""account@jabber.org/Home"""); } } public static string SendxmpppasswordShort { get { return LC.L(@"The XMPP password"); } } @@ -156,6 +162,8 @@ All command line options are also reported within %value%, e.g. %volsize%. Any u public static string SendhttpmessageparameternameLong { get { return LC.L(@"The name of the parameter to send the message as."); } } public static string SendhttpextraparametersShort { get { return LC.L(@"Extra parameters to add to the http message"); } } public static string SendhttpextraparametersLong { get { return LC.L(@"Extra parameters to add to the http message. I.e. ""parameter1=value1¶meter2=value2"""); } } + public static string ResultFormatShort { get { return LC.L(@"Selects the output format for results"); } } + public static string ResultFormatLong(IEnumerable options) { return LC.L(@"Selects the output format for results. Available formats: {0}", string.Join(", ", options)); } public static string SendhttplevelShort { get { return LC.L(@"The messages to send"); } } public static string SendhttplevelLong(string success, string warning, string error, string fatal, string all) { return LC.L(@"You can specify one of ""{0}"", ""{1}"", ""{2}"", ""{3}"". You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The special value ""{4}"" is a shorthand for ""{0},{1},{2},{3}"" and will cause all backup operations to send a message.", success, warning, error, fatal, all); } From b53e62ddfc0aa0d4ce2b6a2afaafb8e3acc4a651 Mon Sep 17 00:00:00 2001 From: Stephen Gregory Date: Wed, 11 Apr 2018 22:37:14 -0230 Subject: [PATCH 11/12] Ignore non-meaningful result properties when serializing to JSON --- Duplicati/Library/Main/ResultClasses.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Duplicati/Library/Main/ResultClasses.cs b/Duplicati/Library/Main/ResultClasses.cs index b08da90f2..e7b004a4d 100644 --- a/Duplicati/Library/Main/ResultClasses.cs +++ b/Duplicati/Library/Main/ResultClasses.cs @@ -21,6 +21,7 @@ using System.Collections.Generic; using Duplicati.Library.Main.Database; using Duplicati.Library.Logging; using System.Linq; +using Newtonsoft.Json; namespace Duplicati.Library.Main { @@ -228,6 +229,8 @@ namespace Duplicati.Library.Main protected Library.Utility.FileBackedStringList m_retryAttempts; protected IMessageSink m_messageSink; + + [JsonIgnore] public IMessageSink MessageSink { get { return m_messageSink; } @@ -385,6 +388,7 @@ namespace Duplicati.Library.Main } } + [JsonIgnore] public IBackendWriter BackendWriter { get { return (IBackendWriter)this.BackendStatistics; } } public event Action StateChangedEvent; From c30cca6a46335397110ad3e35a9c100ab47b3f5a Mon Sep 17 00:00:00 2001 From: Kenneth Skovhede Date: Thu, 12 Apr 2018 09:38:26 +0200 Subject: [PATCH 12/12] Updated cleanup script to also enforce Windows-style line endings --- tabs-to-spaces.sh | 2 ++ unix2dos.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 unix2dos.py diff --git a/tabs-to-spaces.sh b/tabs-to-spaces.sh index 0f9ddd0d2..1e52ad850 100644 --- a/tabs-to-spaces.sh +++ b/tabs-to-spaces.sh @@ -3,3 +3,5 @@ find Duplicati -type f -name *.js -exec sed -i '' $'s/\t/ /g' {} + find Duplicati -type f -name *.css -exec sed -i '' $'s/\t/ /g' {} + find Duplicati -type f -name *.cs -exec sed -i '' $'s/\t/ /g' {} + find Duplicati -type f -name *.txt -exec sed -i '' $'s/\t/ /g' {} + + +find Duplicati -type f -name *.cs | xargs python unix2dos.py diff --git a/unix2dos.py b/unix2dos.py new file mode 100644 index 000000000..517955040 --- /dev/null +++ b/unix2dos.py @@ -0,0 +1,15 @@ +#!/usr/bin/env python +import sys + +for fname in sys.argv[1:]: + infile = open( fname, "rb" ) + instr = infile.read() + infile.close() + outstr = instr.replace( "\r\n", "\n" ).replace( "\r", "\n" ).replace( "\n", "\r\n" ) + + if len(outstr) == len(instr): + continue + + outfile = open( fname, "wb" ) + outfile.write( outstr ) + outfile.close()