diff --git a/Duplicati/CommandLine/Commands.cs b/Duplicati/CommandLine/Commands.cs index 95ecf7065..711a0c58a 100644 --- a/Duplicati/CommandLine/Commands.cs +++ b/Duplicati/CommandLine/Commands.cs @@ -573,8 +573,12 @@ namespace Duplicati.CommandLine if (result.PartialBackup) return 50; + //Completed with errors = 3 + if (result.ParsedResult == Library.Interface.ParsedResultType.Error) + return 3; + //Completed with warnings = 2 - if (result.Warnings.Count() > 0 || result.Errors.Count() > 0) + if (result.ParsedResult == Library.Interface.ParsedResultType.Warning) return 2; //Success, but no upload = 1 diff --git a/Duplicati/Library/Interface/ResultInterfaces.cs b/Duplicati/Library/Interface/ResultInterfaces.cs index f49d51079..abdb11cbb 100644 --- a/Duplicati/Library/Interface/ResultInterfaces.cs +++ b/Duplicati/Library/Interface/ResultInterfaces.cs @@ -20,6 +20,15 @@ using System.Collections.Generic; namespace Duplicati.Library.Interface { + public enum ParsedResultType + { + Unknown, + Success, + Warning, + Error, + Fatal + } + public interface IBasicResults { DateTime BeginTime { get; } @@ -29,6 +38,7 @@ namespace Duplicati.Library.Interface IEnumerable Errors { get; } IEnumerable Warnings { get; } IEnumerable Messages { get; } + ParsedResultType ParsedResult { get; } } public interface IBackendStatstics diff --git a/Duplicati/Library/Main/ResultClasses.cs b/Duplicati/Library/Main/ResultClasses.cs index ae3b755c7..469e21b0c 100644 --- a/Duplicati/Library/Main/ResultClasses.cs +++ b/Duplicati/Library/Main/ResultClasses.cs @@ -20,6 +20,7 @@ using Duplicati.Library.Interface; using System.Collections.Generic; using Duplicati.Library.Main.Database; using Duplicati.Library.Logging; +using System.Linq; namespace Duplicati.Library.Main { @@ -244,7 +245,20 @@ namespace Duplicati.Library.Main m_verboseErrors = value; } } - + + public virtual ParsedResultType ParsedResult + { + get + { + if (Errors != null && Errors.Any()) + return ParsedResultType.Error; + else if (Warnings != null && Warnings.Any()) + return ParsedResultType.Warning; + else + return ParsedResultType.Success; + } + } + public DateTime EndTime { get; set; } public DateTime BeginTime { get; set; } public TimeSpan Duration { get { return EndTime.Ticks == 0 ? new TimeSpan(0) : EndTime - BeginTime; } } @@ -754,7 +768,7 @@ namespace Duplicati.Library.Main } } - + internal class BackupResults : BasicResults, IBackupResults { public long DeletedFiles { get; internal set; } @@ -777,13 +791,26 @@ namespace Duplicati.Library.Main public long DeletedSymlinks { get; internal set; } public bool PartialBackup { get; internal set; } public bool Dryrun { get; internal set; } - + public override OperationMode MainOperation { get { return OperationMode.Backup; } } - + public ICompactResults CompactResults { get; internal set; } public IDeleteResults DeleteResults { get; internal set; } - public IRepairResults RepairResults { get; internal set; } - public ITestResults TestResults { get; internal set; } + public IRepairResults RepairResults { get; internal set; } + public ITestResults TestResults { get; internal set; } + + public override ParsedResultType ParsedResult + { + get + { + if ((Errors != null && Errors.Any()) || FilesWithError > 0) + return ParsedResultType.Error; + else if ((Warnings != null && Warnings.Any()) || PartialBackup) + return ParsedResultType.Warning; + else + return ParsedResultType.Success; + } + } } internal class RestoreResults : BasicResults, Library.Interface.IRestoreResults @@ -800,6 +827,19 @@ namespace Duplicati.Library.Main public override OperationMode MainOperation { get { return OperationMode.Restore; } } public IRecreateDatabaseResults RecreateDatabaseResults { get; internal set; } + + public override ParsedResultType ParsedResult + { + get + { + if (Errors != null && Errors.Any()) + return ParsedResultType.Error; + else if ((Warnings != null && Warnings.Any()) || FilesRestored == 0) + return ParsedResultType.Warning; + else + return ParsedResultType.Success; + } + } } internal class ListResultFile : Duplicati.Library.Interface.IListResultFile diff --git a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs index a692d8c53..4ccfb5886 100644 --- a/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs +++ b/Duplicati/Library/Modules/Builtin/SendJabberMessage.cs @@ -51,29 +51,13 @@ namespace Duplicati.Library.Modules.Builtin /// /// The default mail level /// - private const ReportLevels DEFAULT_LEVEL = ReportLevels.All; + private const string DEFAULT_LEVEL = "all"; /// - /// The default mail body + /// The default message body /// private readonly string DEFAULT_MESSAGE = string.Format("Duplicati %OPERATIONNAME% report for %backup-name%{0}{0}%RESULT%", Environment.NewLine); - /// - /// The default mail sender - /// - private const string DEFAULT_SENDER = "no-reply"; #endregion - /// - /// The allowed mail levels - /// - [Flags] - private enum ReportLevels - { - Success = 0x1, - Warning = 0x2, - Error = 0x4, - All = Success | Warning | Error - } - #region Private variables /// @@ -116,7 +100,7 @@ namespace Duplicati.Library.Modules.Builtin /// /// The XMPP send level /// - private ReportLevels m_level; + private string[] m_levels; /// /// True to send all operations /// @@ -161,7 +145,7 @@ namespace Duplicati.Library.Modules.Builtin new CommandLineArgument(OPTION_MESSAGE, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppmessageShort, Strings.SendJabberMessage.SendxmppmessageLong, DEFAULT_MESSAGE), new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendJabberMessage.SendxmppusernameShort, Strings.SendJabberMessage.SendxmppusernameLong), 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(ReportLevels.Success.ToString(), ReportLevels.Warning.ToString(), ReportLevels.Error.ToString(), ReportLevels.All.ToString()), DEFAULT_LEVEL.ToString(), null, Enum.GetNames(typeof(ReportLevels))), + 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), }); } @@ -183,23 +167,23 @@ namespace Duplicati.Library.Modules.Builtin commandlineOptions.TryGetValue(OPTION_MESSAGE, out m_body); m_options = commandlineOptions; - m_level = 0; - string tmp; commandlineOptions.TryGetValue(OPTION_SENDLEVEL, out tmp); if (!string.IsNullOrEmpty(tmp)) - foreach(var s in tmp.Split(new string[] {","}, StringSplitOptions.RemoveEmptyEntries)) - { - if (string.IsNullOrEmpty(s)) - continue; + m_levels = + tmp + .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .ToArray(); - ReportLevels m; - if (Enum.TryParse(s.Trim(), true, out m)) - m_level |= m; - } - - if (m_level == 0) - m_level = DEFAULT_LEVEL; + if (m_levels == null || m_levels.Length == 0) + m_levels = + DEFAULT_LEVEL + .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .ToArray(); m_sendAll = Utility.Utility.ParseBoolOption(commandlineOptions, OPTION_SENDALL); @@ -240,20 +224,20 @@ namespace Duplicati.Library.Modules.Builtin if (string.Equals(m_operationname, "Backup", StringComparison.InvariantCultureIgnoreCase)) { - ReportLevels level; + ParsedResultType level; if (result is Exception) - level = ReportLevels.Error; - else if (result != null && result is Library.Interface.IBackupResults && (result as Library.Interface.IBackupResults).Errors.Count() > 0) - level = ReportLevels.Warning; + level = ParsedResultType.Fatal; + else if (result != null && result is Library.Interface.IBasicResults) + level = ((IBasicResults)result).ParsedResult; else - level = ReportLevels.Success; + level = ParsedResultType.Error; m_parsedresultlevel = level.ToString(); - if (m_level != ReportLevels.All) + if (!m_levels.Any(x => string.Equals(x, "all", StringComparison.OrdinalIgnoreCase))) { //Check if this level should send mail - if ((m_level & level) == 0) + if (!m_levels.Any(x => string.Equals(x, level.ToString(), StringComparison.OrdinalIgnoreCase))) return; } } diff --git a/Duplicati/Library/Modules/Builtin/SendMail.cs b/Duplicati/Library/Modules/Builtin/SendMail.cs index 5b1599a7e..607183964 100644 --- a/Duplicati/Library/Modules/Builtin/SendMail.cs +++ b/Duplicati/Library/Modules/Builtin/SendMail.cs @@ -68,7 +68,7 @@ namespace Duplicati.Library.Modules.Builtin /// /// The default mail level /// - private const MailLevels DEFAULT_LEVEL = MailLevels.All; + private const string DEFAULT_LEVEL = "all"; /// /// The default mail body /// @@ -79,20 +79,7 @@ namespace Duplicati.Library.Modules.Builtin private const string DEFAULT_SENDER = "no-reply"; #endregion - /// - /// The allowed mail levels - /// - [Flags] - private enum MailLevels - { - Success = 0x1, - Warning = 0x2, - Error = 0x4, - All = Success | Warning | Error - } - #region Private variables - /// /// The cached name of the operation /// @@ -145,7 +132,7 @@ namespace Duplicati.Library.Modules.Builtin /// /// The mail send level /// - private MailLevels m_level; + private string[] m_levels; /// /// True to send all operations /// @@ -193,7 +180,7 @@ namespace Duplicati.Library.Modules.Builtin new CommandLineArgument(OPTION_SERVER, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionServerShort, Strings.SendMail.OptionServerLong), new CommandLineArgument(OPTION_USERNAME, CommandLineArgument.ArgumentType.String, Strings.SendMail.OptionUsernameShort, Strings.SendMail.OptionUsernameLong), 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(MailLevels.Success.ToString(), MailLevels.Warning.ToString(), MailLevels.Error.ToString(), MailLevels.All.ToString()), DEFAULT_LEVEL.ToString(), null, Enum.GetNames(typeof(MailLevels))), + 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), }); } @@ -218,24 +205,24 @@ namespace Duplicati.Library.Modules.Builtin commandlineOptions.TryGetValue(OPTION_BODY, out m_body); m_options = commandlineOptions; - m_level = 0; - string tmp; commandlineOptions.TryGetValue(OPTION_SENDLEVEL, out tmp); if (!string.IsNullOrEmpty(tmp)) - foreach(var s in tmp.Split(new string[] {","}, StringSplitOptions.RemoveEmptyEntries)) - { - if (string.IsNullOrEmpty(s)) - continue; - - MailLevels m; - if (Enum.TryParse(s.Trim(), true, out m)) - m_level |= m; - } - - if (m_level == 0) - m_level = DEFAULT_LEVEL; + m_levels = + tmp + .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .ToArray(); + if (m_levels == null || m_levels.Length == 0) + m_levels = + DEFAULT_LEVEL + .Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries) + .Where(x => !string.IsNullOrWhiteSpace(x)) + .Select(x => x.Trim()) + .ToArray(); + m_sendAll = Utility.Utility.ParseBoolOption(commandlineOptions, OPTION_SENDALL); if (string.IsNullOrEmpty(m_subject)) @@ -279,20 +266,20 @@ namespace Duplicati.Library.Modules.Builtin if (string.Equals(m_operationname, "Backup", StringComparison.InvariantCultureIgnoreCase)) { - MailLevels level; + ParsedResultType level; if (result is Exception) - level = MailLevels.Error; - else if (result != null && result is Library.Interface.IBackupResults && (result as Library.Interface.IBackupResults).Errors.Count() > 0) - level = MailLevels.Warning; + level = ParsedResultType.Fatal; + else if (result != null && result is Library.Interface.IBasicResults) + level = ((IBasicResults)result).ParsedResult; else - level = MailLevels.Success; + level = ParsedResultType.Error; m_parsedresultlevel = level.ToString(); - if (m_level != MailLevels.All) + if (!m_levels.Any(x => string.Equals(x, "all", StringComparison.OrdinalIgnoreCase))) { //Check if this level should send mail - if ((m_level & level) == 0) + if (!m_levels.Any(x => string.Equals(x, level.ToString(), StringComparison.OrdinalIgnoreCase))) return; } } diff --git a/Duplicati/Library/Modules/Builtin/Strings.cs b/Duplicati/Library/Modules/Builtin/Strings.cs index 0bbc78583..3324c4bcf 100644 --- a/Duplicati/Library/Modules/Builtin/Strings.cs +++ b/Duplicati/Library/Modules/Builtin/Strings.cs @@ -87,7 +87,7 @@ sender@example.com Mail Sender Mail Sender "); } } public static string OptionSenderShort { get { return LC.L(@"Email sender"); } } - public static string OptionSendlevelLong(string success, string warning, string error, string all) { return LC.L(@"You can specify one of ""{0}"", ""{1}"", ""{2}"". You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The special value ""{3}"" is a shorthand for ""{0},{1},{2}"" and will cause all backup operations to send an email.", success, warning, error, all); } + public static string OptionSendlevelLong(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 an email.", success, warning, error, fatal, all); } public static string OptionSendlevelShort { get { return LC.L(@"The messages to send"); } } public static string OptionServerLong { get { return LC.L(@"A url for the SMTP server, e.g. smtp://example.com:25. Multiple servers can be supplied in a prioritized list, separated with semicolon. If a server fails, the next server in the list is tried, until the message has been sent. If no server is supplied, a DNS lookup is performed to find the first recipient's MX record, and all SMTP servers are tried in their priority order until the message is sent. @@ -121,8 +121,8 @@ All command line options are also reported within %value%, e.g. %volsize%. Any u public static string SendxmpppasswordShort { get { return LC.L(@"The XMPP password"); } } public static string SendxmpppasswordLong { get { return LC.L(@"The password for the account that will send the message"); } } public static string SendxmpplevelShort { get { return LC.L(@"The messages to send"); } } - public static string SendxmpplevelLong(string success, string warning, string error, string all) { return LC.L(@"You can specify one of ""{0}"", ""{1}"", ""{2}"". -You can supply multiple options with a comma separator, e.g. ""{0},{1}"". The special value ""{3}"" is a shorthand for ""{0},{1},{2}"" and will cause all backup operations to send a message.", success, warning, error, all); } + public static string SendxmpplevelLong(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); } public static string SendxmppanyoperationShort { get { return LC.L(@"Send messages for all operations"); } } public static string SendxmppanyoperationLong { get { return LC.L(@"By default, messages will only be sent after a Backup operation. Use this option to send messages for all operations"); } } public static string DisplayName { get { return LC.L(@"XMPP report module"); } } diff --git a/Duplicati/Server/Runner.cs b/Duplicati/Server/Runner.cs index 390e5ef08..1226de5f1 100644 --- a/Duplicati/Server/Runner.cs +++ b/Duplicati/Server/Runner.cs @@ -600,7 +600,7 @@ namespace Duplicati.Server if (r.BackendStatistics is Duplicati.Library.Interface.IParsedBackendStatistics) UpdateMetadata(backup, (Duplicati.Library.Interface.IParsedBackendStatistics)r.BackendStatistics); } - + if (o is Duplicati.Library.Interface.IBackupResults) { var r = (Duplicati.Library.Interface.IBackupResults)o; @@ -609,21 +609,22 @@ namespace Duplicati.Server backup.Metadata["SourceSizeString"] = Duplicati.Library.Utility.Utility.FormatSizeString(r.SizeOfExaminedFiles); backup.Metadata["LastBackupStarted"] = Library.Utility.Utility.SerializeDateTime(((Duplicati.Library.Interface.IBasicResults)o).BeginTime.ToUniversalTime()); backup.Metadata["LastBackupFinished"] = Library.Utility.Utility.SerializeDateTime(((Duplicati.Library.Interface.IBasicResults)o).EndTime.ToUniversalTime()); - - if (r.FilesWithError > 0 || r.Warnings.Any()) + + if (r.FilesWithError > 0 || r.Warnings.Any() || r.Errors.Any()) { Program.DataConnection.RegisterNotification( - NotificationType.Error, - backup.IsTemporary ? + NotificationType.Error, + backup.IsTemporary ? "Warning" : string.Format("Warning while running {0}", backup.Name), - r.FilesWithError > 0 ? + r.FilesWithError > 0 ? string.Format("Errors affected {0} file(s) ", r.FilesWithError) : string.Format("Got {0} warning(s) ", r.Warnings.Count()) , null, backup.ID, "backup:show-log", - (n, a) => { + (n, a) => + { var existing = (a.Where(x => x.BackupID == backup.ID)).FirstOrDefault(); if (existing == null) return n; @@ -636,6 +637,36 @@ namespace Duplicati.Server ); } } + else if (o is Duplicati.Library.Interface.IBasicResults) + { + var r = (Duplicati.Library.Interface.IBasicResults)o; + if (r.ParsedResult != Library.Interface.ParsedResultType.Success) + { + var type = r.ParsedResult == Library.Interface.ParsedResultType.Warning + ? NotificationType.Warning + : NotificationType.Error; + + var title = r.ParsedResult == Library.Interface.ParsedResultType.Warning + ? (backup.IsTemporary ? + "Warning" : string.Format("Warning while running {0}", backup.Name)) + : (backup.IsTemporary ? + "Error" : string.Format("Error while running {0}", backup.Name)); + + var message = r.ParsedResult == Library.Interface.ParsedResultType.Warning + ? string.Format("Got {0} warning(s) ", r.Warnings.Count()) + : string.Format("Got {0} error(s) ", r.Errors.Count()); + + Program.DataConnection.RegisterNotification( + type, + title, + message, + null, + backup.ID, + "backup:show-log", + (n, a) => n + ); + } + } if (!backup.IsTemporary) Program.DataConnection.SetMetadata(backup.Metadata, long.Parse(backup.ID), null);