diff --git a/CrispyWaffle.sln.DotSettings b/CrispyWaffle.sln.DotSettings new file mode 100644 index 00000000..3c536788 --- /dev/null +++ b/CrispyWaffle.sln.DotSettings @@ -0,0 +1,2 @@ + + 3 \ No newline at end of file diff --git a/Src/CrispyWaffle/Extensions/ConversionExtensions.cs b/Src/CrispyWaffle/Extensions/ConversionExtensions.cs index 425c2a58..74019c6d 100644 --- a/Src/CrispyWaffle/Extensions/ConversionExtensions.cs +++ b/Src/CrispyWaffle/Extensions/ConversionExtensions.cs @@ -94,12 +94,15 @@ public static DateTime ToDateTime(this string input) } } + + /// /// Tries to convert string to date time. /// /// The input string a valid DateTime format. /// The DateTime value. /// True if success, false otherwise + // ReSharper disable once MethodTooLong public static bool TryToDateTime(this string input, out DateTime value) { value = DateTime.MinValue; @@ -110,30 +113,39 @@ public static bool TryToDateTime(this string input, out DateTime value) { case "agora": case "now": + value = DateTime.Now; + return true; + case "hoje": case "today": + value = DateTime.Today; + return true; + case "ontem": case "yesterday": + value = DateTime.Today.AddDays(-1); + return true; + case "amanhã": case "amanha": case "tomorrow": + value = DateTime.Today.AddDays(1); + return true; + default: + if (DateTime.TryParse(input, out value)) return true; - return input.Length == 10 && - DateTime.TryParseExact(input, - @"dd/MM/yyyy", - CultureInfo.InvariantCulture, - DateTimeStyles.None, - out value); + + return input.Length == 10 && DateTime.TryParseExact(input, @"dd/MM/yyyy", CultureInfo.InvariantCulture, DateTimeStyles.None, out value); } } @@ -146,7 +158,9 @@ public static int ToInt32(this string input) { if (string.IsNullOrWhiteSpace(input)) return 0; + var success = int.TryParse(input, NumberStyles.Number, StringExtensions.Culture, out var result); + return success ? result : 0; } @@ -159,7 +173,9 @@ public static long ToInt64(this string input) { if (string.IsNullOrWhiteSpace(input)) return 0; + var success = long.TryParse(input, NumberStyles.Number, StringExtensions.Culture, out var result); + return success ? result : 0; } @@ -172,6 +188,7 @@ public static decimal ToDecimal(this string input) { if (string.IsNullOrWhiteSpace(input)) return 0M; + return decimal.TryParse(input, NumberStyles.Number, StringExtensions.Culture, out var result) ? result : 0M; @@ -232,8 +249,10 @@ public static DateTime FromUnixTimeStamp(this int epochTime) public static PhoneNumber ParseBrazilianPhoneNumber(this string number) { var result = new PhoneNumber(0, 0, 0); + if (number.TryParseBrazilianPhoneNumber(ref result)) return result; + throw new InvalidTelephoneNumberException(number.RemoveNonNumeric()); } @@ -262,11 +281,16 @@ public static bool TryParseBrazilianPhoneNumber(this string number, ref PhoneNum (dirtyLength == 11 || dirtyLength == 12) ? dirty.Substring(1, 2) : dirty.Substring(0, 2); + var hasNineDigits = dirty.Substring(dirtyLength - 9, 1) .Equals(@"9", StringExtensions.Comparison); + var allowedDigits = hasNineDigits ? 9 : 8; + var telephoneNumber = dirty.Substring(dirtyLength - allowedDigits, allowedDigits); + result = new PhoneNumber(55, prefix.ToInt32(), telephoneNumber.ToInt64()); + return true; } @@ -297,6 +321,7 @@ public static string ToIdentString(this XmlDocument document) { if (document == null) return null; + var builder = new StringBuilder(); var settings = new XmlWriterSettings @@ -350,6 +375,16 @@ public static int ToModule10(this string input) return digit; } + /// + /// The ordinal suffix + /// + private static readonly Dictionary OrdinalSuffix = new Dictionary + { + {1,"st"}, + {2,"nd"}, + {3,"rd"} + }; + /// /// To the ordinal. /// @@ -359,20 +394,18 @@ public static string ToOrdinal(this long number) { if (number < 0) return number.ToString(); + var rem = number % 100; + if (rem >= 11 && rem <= 13) return $"{number}th"; - switch (number % 10) - { - case 1: - return $"{number}st"; - case 2: - return $"{number}nd"; - case 3: - return $"{number}rd"; - default: - return $"{number}th"; - } + + + var key = (int)number % 10; + if (OrdinalSuffix.ContainsKey(key)) + return $"{number}{OrdinalSuffix[key]}"; + + return $"{number}th"; } /// @@ -427,27 +460,45 @@ public static T DeepClone(this T instance, bool useNonPublic = true) var arguments = new List(); var parameters = ctor.GetParameters(); + foreach (var parameter in parameters) { - var property = type.GetProperty(parameter.Name, BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance); + ParseParameters(instance, useNonPublic, type, parameter, arguments); + } - if (property == null && !useNonPublic) - continue; + return (T)ctor.Invoke(arguments.ToArray()); + } - if (property == null) - { - property = type.GetProperty(parameter.Name, BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Instance); + /// + /// Parses the parameters. + /// + /// + /// The instance. + /// if set to true [use non public]. + /// The type. + /// The parameter. + /// The arguments. + private static void ParseParameters(T instance, bool useNonPublic, Type type, ParameterInfo parameter, List arguments) + { + var property = type.GetProperty(parameter.Name, + BindingFlags.Public | BindingFlags.IgnoreCase | BindingFlags.Instance); - if (property == null) - continue; - } + if (property == null && !useNonPublic) + return; - if (property.PropertyType != parameter.ParameterType) - continue; + if (property == null) + { + property = type.GetProperty(parameter.Name, + BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Instance); - arguments.Add(property.GetValue(instance)); + if (property == null) + return; } - return (T)ctor.Invoke(arguments.ToArray()); + + if (property.PropertyType != parameter.ParameterType) + return; + + arguments.Add(property.GetValue(instance)); } } } diff --git a/Src/CrispyWaffle/Extensions/StringExtensions.cs b/Src/CrispyWaffle/Extensions/StringExtensions.cs index 655e0bd0..85cf6296 100644 --- a/Src/CrispyWaffle/Extensions/StringExtensions.cs +++ b/Src/CrispyWaffle/Extensions/StringExtensions.cs @@ -282,6 +282,7 @@ public static string ToValidFileName(this string fileName) /// The string to compare. /// An Int32. [Pure] + // ReSharper disable once MethodTooLong public static int Levenshtein(this string input, string inputToCompare) { var n = input.Length; diff --git a/Src/CrispyWaffle/Extensions/TypeExtensions.cs b/Src/CrispyWaffle/Extensions/TypeExtensions.cs index 1f2b5246..6d2b22c4 100644 --- a/Src/CrispyWaffle/Extensions/TypeExtensions.cs +++ b/Src/CrispyWaffle/Extensions/TypeExtensions.cs @@ -121,7 +121,7 @@ public static bool IsSimpleType(this Type type) /// /// The primitive numeric types /// - private static HashSet PrimitiveNumericTypes = new HashSet + private static readonly HashSet PrimitiveNumericTypes = new HashSet { TypeCode.Byte, TypeCode.SByte, TypeCode.UInt16, TypeCode.UInt32, TypeCode.UInt64, TypeCode.Int16, TypeCode.Int32, TypeCode.Int64, TypeCode.Decimal, TypeCode.Double, TypeCode.Single @@ -132,6 +132,7 @@ public static bool IsSimpleType(this Type type) /// /// The type. /// Boolean. + // ReSharper disable once CognitiveComplexity public static bool IsNumericType(this Type type) { while (true) diff --git a/Src/CrispyWaffle/Log/Adapters/EventLogAdapter.cs b/Src/CrispyWaffle/Log/Adapters/EventLogAdapter.cs new file mode 100644 index 00000000..f5cf40f6 --- /dev/null +++ b/Src/CrispyWaffle/Log/Adapters/EventLogAdapter.cs @@ -0,0 +1,383 @@ +using CrispyWaffle.Log.Providers; +using CrispyWaffle.Serialization; +using System; +using System.Diagnostics; + +namespace CrispyWaffle.Log.Adapters +{ + /// + /// Class EventLogAdapter. This class cannot be inherited. + /// Implements the + /// + /// + public sealed class EventLogAdapter : ILogAdapter + { + #region Consts + + /// + /// The application log name + /// + private const string ApplicationLogName = "Application"; + /// + /// The maximum payload length chars + /// + const int MaximumPayloadLengthChars = 31839; + + /// + /// The maximum source name length chars + /// + const int MaximumSourceNameLengthChars = 212; + + /// + /// The source moved event identifier + /// + const int SourceMovedEventId = 3; + + /// + /// The event identifier provider + /// + readonly IEventIdProvider _eventIdProvider; + + /// + /// The log + /// + readonly EventLog _log; + + /// + /// The level + /// + private LogLevel _level; + + #endregion + + #region ~Ctors + + /// + /// Initializes a new instance of the class. + /// + /// The source. + /// Name of the log. + /// Name of the machine. + /// if set to true [manage event source]. + /// The event identifier provider. + /// source + /// eventIdProvider + public EventLogAdapter(string source, string logName, string machineName, bool manageEventSource, + IEventIdProvider eventIdProvider) + { + if (source == null) throw new ArgumentNullException(nameof(source)); + + if (source.Length > MaximumSourceNameLengthChars) + { + source = source.Substring(0, MaximumSourceNameLengthChars); + } + + source = source.Replace("<", "_"); + source = source.Replace(">", "_"); + + _eventIdProvider = eventIdProvider ?? throw new ArgumentNullException(nameof(eventIdProvider)); + + _log = new EventLog(string.IsNullOrWhiteSpace(logName) ? ApplicationLogName : logName, machineName); + + if (manageEventSource) + { + ConfigureSource(_log, source); + } + else + { + _log.Source = source; + } + } + + #endregion + + #region Private methods + + /// + /// Configures the source. + /// + /// The log. + /// The source. + private static void ConfigureSource(EventLog log, string source) + { + var sourceData = new EventSourceCreationData(source, log.Log) { MachineName = log.MachineName }; + + string oldLogName = null; + + if (EventLog.SourceExists(source, log.MachineName)) + { + var existingLogWithSourceName = EventLog.LogNameFromSourceName(source, log.MachineName); + + if (!string.IsNullOrWhiteSpace(existingLogWithSourceName) && + !log.Log.Equals(existingLogWithSourceName, StringComparison.OrdinalIgnoreCase)) + { + // Remove the source from the previous log so we can associate it with the current log name + EventLog.DeleteEventSource(source, log.MachineName); + oldLogName = existingLogWithSourceName; + } + } + else + { + EventLog.CreateEventSource(sourceData); + } + + NotifyLogSourceChange(log, source, oldLogName); + + log.Source = source; + } + + /// + /// Notifies the log source change. + /// + /// The log. + /// The source. + /// Old name of the log. + private static void NotifyLogSourceChange(EventLog log, string source, string oldLogName) + { + if (oldLogName != null) + { + var metaSource = $"serilog-{log.Log}"; + if (!EventLog.SourceExists(metaSource, log.MachineName)) + EventLog.CreateEventSource(new EventSourceCreationData(metaSource, log.Log) + { + MachineName = log.MachineName + }); + + log.Source = metaSource; + log.WriteEntry( + $"Event source {source} was previously registered in log {oldLogName}. " + + $"The source has been registered with this log, {log.Log}, however a computer restart may be required " + + $"before event logs will appear in {log.Log} with source {source}. Until then, messages may be logged to {oldLogName}.", + EventLogEntryType.Warning, + SourceMovedEventId); + } + } + + + /// + /// Levels the type of to event log entry. + /// + /// The level. + /// EventLogEntryType. + private static EventLogEntryType LevelToEventLogEntryType(LogLevel level) + { + switch (level) + { + case LogLevel.DEBUG: + case LogLevel.TRACE: + return EventLogEntryType.Information; + + case LogLevel.WARNING: + return EventLogEntryType.Warning; + + case LogLevel.ERROR: + case LogLevel.FATAL: + return EventLogEntryType.Error; + + default: + return EventLogEntryType.Information; + } + } + + /// + /// Writes the internal. + /// + /// The level. + /// The message. + private void WriteInternal(LogLevel level, string message) + { + if (!_level.HasFlag(level)) + return; + + var type = LevelToEventLogEntryType(level); + + if (message.Length > MaximumPayloadLengthChars) + { + message = message.Substring(0, MaximumPayloadLengthChars); + } + + _log.WriteEntry(message, type, _eventIdProvider.ComputeEventId(message)); + } + + /// + /// Writes the internal. + /// + /// The level. + /// The exception. + private void WriteInternal(LogLevel level, Exception exception) + { + + if (!_level.HasFlag(level)) + return; + + var type = LevelToEventLogEntryType(level); + + var message = GetMessageFromException(exception); + + if (message.Length > MaximumPayloadLengthChars) + { + message = message.Substring(0, MaximumPayloadLengthChars); + } + + _log.WriteEntry(message, type, _eventIdProvider.ComputeEventId(message)); + + } + + /// + /// Gets the message from exception. + /// + /// The exception. + /// System.String. + private static string GetMessageFromException(Exception exception) + { + var message = string.Empty; + + do + { + message += exception.Message; + message += "\r\n"; + message += exception.StackTrace; + message += "\r\n"; + + exception = exception.InnerException; + } while (exception != null); + + return message; + } + + #endregion + + #region Implementation of IDisposable + + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + public void Dispose() + { } + + #endregion + + #region Implementation of ILogAdapter + + /// + /// Change the LogLevel of Log Adapter instance. + /// + /// The new level of the instance + public void SetLevel(LogLevel level) + { + _level = level; + } + + /// + /// Save the serializer version of in the file , + /// using default SerializerFormat, or a custom serializer format provided by . + /// + /// The type of the parameter + /// The object/instance of a class to be serialized and saved in a disk file + /// The file name to be persisted to disk with the content + /// Whatever or not to use a custom Serializer adapter different that one that is default for type + /// Requires LogLevel.DEBUG flag + public void Debug(T content, string identifier, SerializerFormat customFormat = SerializerFormat.NONE) + where T : class + { + if (!_level.HasFlag(LogLevel.DEBUG)) + return; + + var contentAsString = customFormat == SerializerFormat.NONE + ? content.GetSerializer() + : content.GetCustomSerializer(customFormat); + + Debug((string)contentAsString, identifier); + } + + /// + /// Save the string into a file with name + /// + /// The file content + /// The file name + /// Requires LogLevel.DEBUG flag + public void Debug(string content, string fileName) + { + WriteInternal(LogLevel.DEBUG, $"{fileName}: {content}"); + } + + /// + /// Logs a message as DEBUG level + /// + /// The message to be logged. + /// Requires LogLevel.DEBUG flag. + public void Debug(string message) + { + WriteInternal(LogLevel.DEBUG, message); + } + + /// + /// Logs the exception with trace level. + /// + /// The exception. + public void Trace(Exception exception) + { + WriteInternal(LogLevel.TRACE, exception); + } + + /// + /// Logs the message with trace level and shows exception details. + /// + /// The message to be logged. + /// The exception. + public void Trace(string message, Exception exception) + { + WriteInternal(LogLevel.TRACE, message); + WriteInternal(LogLevel.TRACE, exception); + } + + /// + /// Logs a message as TRACE level + /// + /// The message to be logged. + /// Requires LogLevel.TRACE flag. + public void Trace(string message) + { + WriteInternal(LogLevel.TRACE, message); + } + + /// + /// Logs a message as INFO level + /// + /// The message to be logged. + /// Requires LogLevel.INFO flag. + public void Info(string message) + { + WriteInternal(LogLevel.INFO, message); + } + + /// + /// Logs a message as WARNING level. + /// + /// The message to be logged. + /// Requires LogLevel.WARNING flag. + public void Warning(string message) + { + WriteInternal(LogLevel.WARNING, message); + } + + /// + /// Logs a message as ERROR level. + /// + /// The message to be logged. + /// Requires LogLevel.ERROR flag. + public void Error(string message) + { + WriteInternal(LogLevel.ERROR, message); + } + + /// + /// Logs a message as FATAL level. + /// + /// The message. + public void Fatal(string message) + { + WriteInternal(LogLevel.FATAL, message); + } + + #endregion + } +} diff --git a/Src/CrispyWaffle/Log/Providers/EventIdProvider.cs b/Src/CrispyWaffle/Log/Providers/EventIdProvider.cs new file mode 100644 index 00000000..60d65592 --- /dev/null +++ b/Src/CrispyWaffle/Log/Providers/EventIdProvider.cs @@ -0,0 +1,56 @@ +using System; + +namespace CrispyWaffle.Log.Providers +{ + /// + /// Class EventIdProvider. + /// Implements the + /// + /// + public class EventIdProvider : IEventIdProvider + { + /// + /// Compute a 32-bit hash of the provided . The + /// resulting hash value can be uses as an event id in lieu of transmitting the + /// full template string. + /// + /// A message template. + /// A 32-bit hash of the template. + static int Compute(string message) + { + if (message == null) throw new ArgumentNullException(nameof(message)); + + // Jenkins one-at-a-time https://en.wikipedia.org/wiki/Jenkins_hash_function + unchecked + { + uint hash = 0; + + foreach (var t in message) + { + hash += t; + hash += (hash << 10); + hash ^= (hash >> 6); + } + + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + + //even though the api is type int, eventID must be between 0 and 65535 + //https://msdn.microsoft.com/en-us/library/d3159s0c(v=vs.110).aspx + return (ushort)hash; + } + } + + #region Implementation of IEventIdProvider + + /// + /// Computes the event identifier. + /// + /// The message. + /// System.UInt16. + public ushort ComputeEventId(string message) => (ushort)Compute(message); + + #endregion + } +} diff --git a/Src/CrispyWaffle/Log/Providers/EventLogProvider.cs b/Src/CrispyWaffle/Log/Providers/EventLogProvider.cs new file mode 100644 index 00000000..5643f6ed --- /dev/null +++ b/Src/CrispyWaffle/Log/Providers/EventLogProvider.cs @@ -0,0 +1,176 @@ +using CrispyWaffle.Log.Adapters; +using CrispyWaffle.Serialization; +using System; + +namespace CrispyWaffle.Log.Providers +{ + /// + /// Class EventLogProvider. This class cannot be inherited. + /// Implements the + /// + /// + public sealed class EventLogProvider : ILogProvider + { + /// + /// Event log provider. + /// + private readonly EventLogAdapter _adapter; + + #region ~Ctors + + /// + /// Initializes a new instance of the class. + /// + /// The source. + public EventLogProvider(string source) + { + _adapter = new EventLogAdapter(source, string.Empty, Environment.MachineName, false, new EventIdProvider()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The source. + /// Name of the log. + public EventLogProvider(string source, string logName) + { + _adapter = new EventLogAdapter(source, logName, Environment.MachineName, false, new EventIdProvider()); + } + + /// + /// Initializes a new instance of the class. + /// + /// The source. + /// Name of the log. + /// Name of the machine. + /// if set to true [manage event source]. + /// The event identifier provider. + public EventLogProvider(string source, string logName, string machineName, bool manageEventSource, + IEventIdProvider eventIdProvider) + { + var provider = eventIdProvider ?? new EventIdProvider(); + _adapter = new EventLogAdapter(source, logName, machineName, manageEventSource, provider); + } + + #endregion + + #region Implementation of ILogProvider + + /// + /// Sets the log level of the instance + /// + /// The log level + public void SetLevel(LogLevel level) + { + _adapter.SetLevel(level); + } + + /// + /// Logs the message with fatal level. + /// + /// The category. + /// The message. + public void Fatal(string category, string message) + { + _adapter.Fatal(message); + } + + /// + /// Logs the message with error level + /// + /// The category + /// The message to be logged + public void Error(string category, string message) + { + _adapter.Error(message); + } + + /// + /// Logs the message with warning level + /// + /// The category + /// The message to be logged + public void Warning(string category, string message) + { + _adapter.Warning(message); + } + + /// + /// Logs the message with info level + /// + /// The category + /// The message to be logged + public void Info(string category, string message) + { + _adapter.Info(message); + } + + /// + /// Logs the message with trace level + /// + /// The category + /// The message to be logged + public void Trace(string category, string message) + { + _adapter.Trace(message); + } + + /// + /// Logs the message with trace level and shows exception details. + /// + /// The category. + /// The message to be logged. + /// The exception. + public void Trace(string category, string message, Exception exception) + { + _adapter.Trace(message, exception); + } + + /// + /// Logs the exception details with trace level. + /// + /// The category. + /// The exception. + public void Trace(string category, Exception exception) + { + _adapter.Trace(exception); + } + + /// + /// Logs the message with debug level + /// + /// The category + /// The message to be logged + public void Debug(string category, string message) + { + _adapter.Debug(message); + } + + /// + /// Does nothing + /// + /// The category + /// Not used + /// Not used + public void Debug(string category, string content, string fileName) + { + _adapter.Debug(content, fileName); + } + + /// + /// Does nothing + /// + /// Not used + /// The category + /// Not used + /// Not used + /// Not used + public void Debug(string category, T content, string identifier, SerializerFormat customFormat = SerializerFormat.NONE) + where T : class, new() + { + _adapter.Debug(content, identifier, customFormat); + } + + #endregion + } +} diff --git a/Src/CrispyWaffle/Log/Providers/IEventIdProvider.cs b/Src/CrispyWaffle/Log/Providers/IEventIdProvider.cs new file mode 100644 index 00000000..6606aef3 --- /dev/null +++ b/Src/CrispyWaffle/Log/Providers/IEventIdProvider.cs @@ -0,0 +1,15 @@ +namespace CrispyWaffle.Log.Providers +{ + /// + /// Interface IEventIdProvider + /// + public interface IEventIdProvider + { + /// + /// Computes the event identifier. + /// + /// The message. + /// System.UInt16. + ushort ComputeEventId(string message); + } +} diff --git a/Src/CrispyWaffle/Serialization/Adapters/BinarySerializerAdapter.cs b/Src/CrispyWaffle/Serialization/Adapters/BinarySerializerAdapter.cs index ff86dc09..e099d7be 100644 --- a/Src/CrispyWaffle/Serialization/Adapters/BinarySerializerAdapter.cs +++ b/Src/CrispyWaffle/Serialization/Adapters/BinarySerializerAdapter.cs @@ -63,9 +63,8 @@ public T Load(string file) where T : class var fileName = Path.GetFileName(file); var folder = Path.GetDirectoryName(file); - var are = new AutoResetEvent(false); - var stream = LoadInternal(file, fileName, folder, are); + var stream = LoadInternal(file, fileName, folder); return Deserialize(stream); } @@ -76,41 +75,50 @@ public T Load(string file) where T : class /// The file. /// Name of the file. /// The folder. - /// The are. /// Stream. - private static Stream LoadInternal(string file, string fileName, string folder, AutoResetEvent are) + private static Stream LoadInternal(string file, string fileName, string folder) { - Stream stream; + var autoResetEvent = new AutoResetEvent(false); + while (true) { try { - stream = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None); - break; + return new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.None); } catch (IOException) { - FileSystemWatcher watcher = null; - try - { - watcher = new FileSystemWatcher - { - Filter = fileName, Path = folder, - NotifyFilter = NotifyFilters.Attributes | NotifyFilters.DirectoryName | NotifyFilters.FileName | - NotifyFilters.LastWrite | NotifyFilters.Size, - EnableRaisingEvents = true - }; - watcher.Changed += (o, e) => are.Set(); - are.WaitOne(new TimeSpan(0, 0, 0, 30)); - } - finally - { - watcher?.Dispose(); - } + HandleLoadIoException(fileName, folder, autoResetEvent); } } + } - return stream; + /// + /// Handles the load io exception. + /// + /// Name of the file. + /// The folder. + /// The automatic reset event. + private static void HandleLoadIoException(string fileName, string folder, AutoResetEvent autoResetEvent) + { + FileSystemWatcher watcher = null; + try + { + watcher = new FileSystemWatcher + { + Filter = fileName, + Path = folder, + NotifyFilter = NotifyFilters.Attributes | NotifyFilters.DirectoryName | NotifyFilters.FileName | + NotifyFilters.LastWrite | NotifyFilters.Size, + EnableRaisingEvents = true + }; + watcher.Changed += (o, e) => autoResetEvent.Set(); + autoResetEvent.WaitOne(new TimeSpan(0, 0, 0, 30)); + } + finally + { + watcher?.Dispose(); + } } ///