diff --git a/FileWatcher/Configuration/Actions/Action.cs b/FileWatcher/Configuration/Actions/Action.cs index 637ea7e..9db0d44 100644 --- a/FileWatcher/Configuration/Actions/Action.cs +++ b/FileWatcher/Configuration/Actions/Action.cs @@ -1,5 +1,4 @@ -using System.Text.RegularExpressions; -using System.Xml.Serialization; +using System.Xml.Serialization; using TE.FileWatcher.Logging; using TEFS = TE.FileWatcher.FileSystem; @@ -10,22 +9,6 @@ namespace TE.FileWatcher.Configuration.Actions /// public class Action : RunnableBase { - // The regular expresson pattern for extracting the date type and the - // specified date format to be used - const string PATTERN = @"\[(?.*):(?.*)\]"; - - // The created date placeholder value - const string CREATED_DATE = "createddate"; - - // The modified date placholder value - const string MODIFIED_DATE = "modifieddate"; - - // The current date placeholder value - const string CURRENT_DATE = "currentdate"; - - // The regular expression - private readonly Regex _regex; - /// /// The type of action to perform. /// @@ -77,12 +60,10 @@ public enum ActionType public bool Verify { get; set; } /// - /// Creates an instance of the class. + /// Gets or sets the keep timestamps flag. /// - public Action() - { - _regex = new Regex(PATTERN, RegexOptions.Compiled); - } + [XmlElement(ElementName = "keepTimestamps", DataType = "boolean")] + public bool KeepTimestamps { get; set; } /// /// Runs the action. @@ -145,8 +126,8 @@ public override void Run(string watchPath, string fullPath, TriggerType trigger) return; } - TEFS.File.Copy(source, destination, Verify); - Logger.WriteLine($"Copied {source} to {destination}."); + TEFS.File.Copy(source, destination, Verify, KeepTimestamps); + Logger.WriteLine($"Copied {source} to {destination}. Verify: {Verify}. Keep timestamps: {KeepTimestamps}."); break; case ActionType.Move: @@ -158,8 +139,8 @@ public override void Run(string watchPath, string fullPath, TriggerType trigger) return; } - TEFS.File.Move(source, destination, Verify); - Logger.WriteLine($"Moved {source} to {destination}."); + TEFS.File.Move(source, destination, Verify, KeepTimestamps); + Logger.WriteLine($"Moved {source} to {destination}. Verify: {Verify}. Keep timestamps: {KeepTimestamps}."); break; case ActionType.Delete: @@ -195,83 +176,6 @@ public override void Run(string watchPath, string fullPath, TriggerType trigger) }; } - /// - /// Gets the date value for the specified date type using the full path - /// of the changed file. - /// - /// - /// The type of date. - /// - /// - /// The full path to the changed file. - /// - /// - /// The value for the type, otherwise null. - /// - /// - /// Thrown when the date could not be determined. - /// - private static DateTime? GetDate(string dateType, string fullPath) - { - // Determine the type of date type, and then get - // the value for the date - return dateType switch - { - CREATED_DATE => TEFS.File.GetCreatedDate(fullPath), - MODIFIED_DATE => TEFS.File.GetModifiedDate(fullPath), - CURRENT_DATE => DateTime.Now, - _ => null - }; - } - - /// - /// Gets the date string value using the specified date and format. - /// - /// - /// The date to be formatted. - /// - /// - /// The format string. - /// - /// - /// The formatted string value - /// - /// - /// Thrown when the date string value can not be created. - /// - private static string? GetDateString(DateTime date, string format) - { - if (string.IsNullOrEmpty(format)) - { - Logger.WriteLine("The date format was not provided."); - return null; - } - - try - { - // Format the date, or return null if there is an - // issue trying to format the date - string? dateString = date.ToString(format); - if (string.IsNullOrWhiteSpace(dateString)) - { - // There was an issue formatting the date, and - // the date string value was null or contained - // no value, so write a log message, and then - // continue to the next match - throw new FileWatcherException( - $"The date could not be formatted. Format: {format}, date: {date}."); - } - - return dateString; - } - catch (Exception ex) - when (ex is ArgumentException || ex is FormatException) - { - throw new FileWatcherException( - $"The date could not be formatted properly using '{format}'. Reason: {ex.Message}"); - } - } - /// /// Gets the destination value by replacing any placeholders with the /// actual string values. @@ -319,84 +223,16 @@ public override void Run(string watchPath, string fullPath, TriggerType trigger) { return null; } - - return ReplacePlaceholders(Source, watchPath, fullPath); - } - - /// - /// Replaces the date placeholders in a string with the actual values. - /// - /// - /// The value containing the placeholders. - /// - /// - /// The watch path. - /// - /// - /// The full path of the changed file. - /// - /// - /// The value with the placeholders replaced with the actual strings, - /// otherwise null. - /// - private string? ReplaceDatePlaceholders(string value, string watchPath, string fullPath) - { - // Re - if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(watchPath) || string.IsNullOrWhiteSpace(fullPath)) + + string? source = ReplacePlaceholders(Source, watchPath, fullPath); + if (!string.IsNullOrWhiteSpace(source)) { - return null; + source = ReplaceDatePlaceholders(source, watchPath, fullPath); } - string replacedValue = value; - - if (_regex.IsMatch(value)) - { - // Find all the regex matches that are in the string since there - // could be multiple date matches - MatchCollection matches = _regex.Matches(value); - if (matches.Count > 0) - { - // Loop through each of the matches so the placeholder can - // be replaced with the actual date values - foreach (Match match in matches) - { - // Store the date type (createddate, modifieddate, - // or currentdate) and change it to lowercase so it can - // be easily compared later - string dateType = match.Groups["datetype"].Value.ToLower(); - // Store the specified date format - string format = match.Groups["format"].Value; - - try - { - // Get the date for the specified date type - DateTime? date = GetDate(dateType, fullPath); - if (date != null) - { - // The string value for the date time using the date type - // and format - string? dateString = GetDateString((DateTime)date, format); + return source; + } - // Replace the date placeholder with the formatted date - // value - replacedValue = replacedValue.Replace(match.Value, dateString); - } else - { - Logger.WriteLine( - $"The date value is null. Date type: {dateType}, changed: {fullPath}, value: {value}, watch path: {watchPath}.", - LogLevel.WARNING); - } - } - catch (FileWatcherException ex) - { - Logger.WriteLine(ex.Message, LogLevel.ERROR); - continue; - } - } - } - } - return replacedValue; - } } } diff --git a/FileWatcher/Configuration/Commands/Command.cs b/FileWatcher/Configuration/Commands/Command.cs index b34382d..6ce225b 100644 --- a/FileWatcher/Configuration/Commands/Command.cs +++ b/FileWatcher/Configuration/Commands/Command.cs @@ -38,7 +38,7 @@ public class Command : RunnableBase public Triggers Triggers { get; set; } = new Triggers(); /// - /// Runs the command. + /// Queues the command process to be run. /// /// /// The watch path. @@ -114,6 +114,9 @@ public override void Run(string watchPath, string fullPath, TriggerType trigger) } } + /// + /// Executes the next command process from the queue. + /// private void Execute() { // If the queue is null or empty, then no command is waiting to nbe @@ -193,7 +196,13 @@ private void Execute() return null; } - return ReplacePlaceholders(Arguments, watchPath, fullPath); + string? arguments = ReplacePlaceholders(Arguments, watchPath, fullPath); + if (!string.IsNullOrWhiteSpace(arguments)) + { + arguments = ReplaceDatePlaceholders(arguments, watchPath, fullPath); + } + + return arguments; } /// @@ -216,7 +225,13 @@ private void Execute() return null; } - return ReplacePlaceholders(Path, watchPath, fullPath); + string? path = ReplacePlaceholders(Path, watchPath, fullPath); + if (!string.IsNullOrWhiteSpace(path)) + { + path = ReplaceDatePlaceholders(path, watchPath, fullPath); + } + + return path; } /// diff --git a/FileWatcher/Configuration/RunnableBase.cs b/FileWatcher/Configuration/RunnableBase.cs index 183caf8..35d2a13 100644 --- a/FileWatcher/Configuration/RunnableBase.cs +++ b/FileWatcher/Configuration/RunnableBase.cs @@ -1,4 +1,6 @@ -using IO = System.IO; +using System.Text.RegularExpressions; +using IO = System.IO; +using TE.FileWatcher.Logging; using TEFS = TE.FileWatcher.FileSystem; namespace TE.FileWatcher.Configuration @@ -27,6 +29,30 @@ public abstract class RunnableBase // The file extension placeholder protected const string PLACEHOLDER_EXTENSION = "[extension]"; + // The created date placeholder value + protected const string PLACEHOLDER_CREATED_DATE = "createddate"; + + // The modified date placholder value + protected const string PLACEHOLDER_MODIFIED_DATE = "modifieddate"; + + // The current date placeholder value + protected const string PLACEHOLDER_CURRENT_DATE = "currentdate"; + + // The regular expresson pattern for extracting the date type and the + // specified date format to be used + const string PATTERN = @"\[(?.*):(?.*)\]"; + + // The regular expression + private readonly Regex _regex; + + /// + /// Initializes an instance of the class. + /// + public RunnableBase() + { + _regex = new Regex(PATTERN, RegexOptions.Compiled); + } + /// /// The abstract method to Run. /// @@ -80,6 +106,160 @@ public abstract class RunnableBase return replacedValue; } + /// + /// Replaces the date placeholders in a string with the actual values. + /// + /// + /// The value containing the placeholders. + /// + /// + /// The watch path. + /// + /// + /// The full path of the changed file. + /// + /// + /// The value with the placeholders replaced with the actual strings, + /// otherwise null. + /// + protected string? ReplaceDatePlaceholders(string value, string watchPath, string fullPath) + { + // Re + if (string.IsNullOrWhiteSpace(value) || string.IsNullOrWhiteSpace(watchPath) || string.IsNullOrWhiteSpace(fullPath)) + { + return null; + } + + string replacedValue = value; + + if (_regex.IsMatch(value)) + { + // Find all the regex matches that are in the string since there + // could be multiple date matches + MatchCollection matches = _regex.Matches(value); + if (matches.Count > 0) + { + // Loop through each of the matches so the placeholder can + // be replaced with the actual date values + foreach (Match match in matches) + { + // Store the date type (createddate, modifieddate, + // or currentdate) and change it to lowercase so it can + // be easily compared later + string dateType = match.Groups["datetype"].Value.ToLower(); + // Store the specified date format + string format = match.Groups["format"].Value; + + try + { + // Get the date for the specified date type + DateTime? date = GetDate(dateType, fullPath); + if (date != null) + { + // The string value for the date time using the date type + // and format + string? dateString = GetDateString((DateTime)date, format); + + // Replace the date placeholder with the formatted date + // value + replacedValue = replacedValue.Replace(match.Value, dateString); + } + else + { + Logger.WriteLine( + $"The date value is null. Date type: {dateType}, changed: {fullPath}, value: {value}, watch path: {watchPath}.", + LogLevel.WARNING); + } + } + catch (FileWatcherException ex) + { + Logger.WriteLine(ex.Message, LogLevel.ERROR); + continue; + } + } + } + } + + return replacedValue; + } + + /// + /// Gets the date value for the specified date type using the full path + /// of the changed file. + /// + /// + /// The type of date. + /// + /// + /// The full path to the changed file. + /// + /// + /// The value for the type, otherwise null. + /// + /// + /// Thrown when the date could not be determined. + /// + private static DateTime? GetDate(string dateType, string fullPath) + { + // Determine the type of date type, and then get + // the value for the date + return dateType switch + { + PLACEHOLDER_CREATED_DATE => TEFS.File.GetCreatedDate(fullPath), + PLACEHOLDER_MODIFIED_DATE => TEFS.File.GetModifiedDate(fullPath), + PLACEHOLDER_CURRENT_DATE => DateTime.Now, + _ => null + }; + } + + /// + /// Gets the date string value using the specified date and format. + /// + /// + /// The date to be formatted. + /// + /// + /// The format string. + /// + /// + /// The formatted string value + /// + /// + /// Thrown when the date string value can not be created. + /// + private static string? GetDateString(DateTime date, string format) + { + if (string.IsNullOrEmpty(format)) + { + Logger.WriteLine("The date format was not provided."); + return null; + } + + try + { + // Format the date, or return null if there is an + // issue trying to format the date + string? dateString = date.ToString(format); + if (string.IsNullOrWhiteSpace(dateString)) + { + // There was an issue formatting the date, and + // the date string value was null or contained + // no value, so write a log message, and then + // continue to the next match + throw new FileWatcherException( + $"The date could not be formatted. Format: {format}, date: {date}."); + } + + return dateString; + } + catch (Exception ex) + when (ex is ArgumentException || ex is FormatException) + { + throw new FileWatcherException( + $"The date could not be formatted properly using '{format}'. Reason: {ex.Message}"); + } + } + /// /// Gets the relative path from the watch path using the full path. /// diff --git a/FileWatcher/FileSystem/File.cs b/FileWatcher/FileSystem/File.cs index 4b0d1b9..78f7b69 100644 --- a/FileWatcher/FileSystem/File.cs +++ b/FileWatcher/FileSystem/File.cs @@ -11,9 +11,6 @@ public static class File // The number of times to retry a file action private const int RETRIES = 5; - // The hash algorithm to use when verifying the files - //private const string HASH_ALGORITHM = "SHA256"; - /// /// Gets the hash of the file. /// @@ -118,7 +115,7 @@ private static void WaitForFile(string path) /// /// Thrown when the file could not be copied to the destination. /// - public static void Copy(string source, string destination, bool verify) + public static void Copy(string source, string destination, bool verify, bool keepTimestamp) { if (string.IsNullOrWhiteSpace(source)) { @@ -159,6 +156,14 @@ public static void Copy(string source, string destination, bool verify) attempts++; } } + + if (keepTimestamp) + { + // Set the time of the destination file to match the source file + // because the file was moved and not a new copy + SetDestinationCreationTime(source, destination); + SetDestinationModifiedTime(source, destination); + } } catch (Exception ex) { @@ -301,6 +306,10 @@ public static bool IsValid(string path) /// /// Verify the file after the copy has completed. /// + /// + /// Flag indicating the created and modified timestamps of the source + /// file will be applied to the destination file. + /// /// /// true if the file was moved successfully, otherwise false. /// @@ -313,7 +322,7 @@ public static bool IsValid(string path) /// /// Thrown when the file could not be moved to the destination. /// - public static void Move(string source, string destination, bool verify) + public static void Move(string source, string destination, bool verify, bool keepTimestamp) { if (string.IsNullOrWhiteSpace(source)) { @@ -325,7 +334,7 @@ public static void Move(string source, string destination, bool verify) throw new ArgumentNullException(nameof(destination)); } - Copy(source, destination, verify); + Copy(source, destination, verify, keepTimestamp); Delete(source); } @@ -376,5 +385,83 @@ public static void Delete(string source) throw new FileWatcherException("The file could not be deleted.", ex); } } + + /// + /// Set the creation time on the destionation file to match the source + /// file. + /// + /// + /// Full path to the source file. + /// + /// + /// Full path to the destination file. + /// + private static void SetDestinationCreationTime(string source, string destination) + { + if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(destination)) + { + return; + } + + if (!IsValid(source) || !IsValid(destination)) + { + return; + } + + DateTime? sourceTime = GetCreatedDate(source); + if (sourceTime == null) + { + return; + } + + try + { + IO.File.SetCreationTime(destination, (DateTime)sourceTime); + } + catch + { + // Just swallow the exception as we are just setting the time + return; + } + } + + /// + /// Set the modified time on the destionation file to match the source + /// file. + /// + /// + /// Full path to the source file. + /// + /// + /// Full path to the destination file. + /// + private static void SetDestinationModifiedTime(string source, string destination) + { + if (string.IsNullOrWhiteSpace(source) || string.IsNullOrWhiteSpace(destination)) + { + return; + } + + if (!IsValid(source) || !IsValid(destination)) + { + return; + } + + DateTime? sourceTime = GetModifiedDate(source); + if (sourceTime == null) + { + return; + } + + try + { + IO.File.SetLastWriteTime(destination, (DateTime)sourceTime); + } + catch + { + // Just swallow the exception as we are just setting the time + return; + } + } } } diff --git a/FileWatcher/FileWatcher.csproj b/FileWatcher/FileWatcher.csproj index 7a24dad..a35c2b7 100644 --- a/FileWatcher/FileWatcher.csproj +++ b/FileWatcher/FileWatcher.csproj @@ -7,7 +7,7 @@ enable TE.FileWatcher fw - 1.3.0 + 1.4.0 true true true @@ -18,8 +18,8 @@ Monitor folders and files on the local system for changes. ©2022 https://github.com/TechieGuy12/FileWatcher - 1.3.1.0 - 1.3.1.0 + 1.4.0.0 + 1.4.0.0 filesystem file-monitoring folder-monitoring MIT README.md diff --git a/FileWatcher/Logging/Message.cs b/FileWatcher/Logging/Message.cs index 5832cd0..ebd5077 100644 --- a/FileWatcher/Logging/Message.cs +++ b/FileWatcher/Logging/Message.cs @@ -22,17 +22,13 @@ public string LevelString { get { - switch (Level) + return Level switch { - case LogLevel.WARNING: - return "WARN"; - case LogLevel.ERROR: - return "ERROR"; - case LogLevel.FATAL: - return "FATAL"; - default: - return "INFO"; - } + LogLevel.WARNING => "WARN", + LogLevel.ERROR => "ERROR", + LogLevel.FATAL => "FATAL", + _ => "INFO", + }; } } diff --git a/FileWatcher/Templates/config-template.xml b/FileWatcher/Templates/config-template.xml index a8cf712..d8c1d70 100644 --- a/FileWatcher/Templates/config-template.xml +++ b/FileWatcher/Templates/config-template.xml @@ -232,7 +232,15 @@ true false (default) --> - + + +