diff --git a/Copy/Clients/Exchange.cs b/Copy/Clients/Exchange.cs index f47cf44..e2847bd 100644 --- a/Copy/Clients/Exchange.cs +++ b/Copy/Clients/Exchange.cs @@ -35,7 +35,7 @@ public bool DoFileExist(string path) throw new NotImplementedException(); } - public string[] ListFiles(string path) + public string[] ListFiles(string path, CopyFilter filter) { throw new NotImplementedException(); } diff --git a/Copy/Clients/FTP.cs b/Copy/Clients/FTP.cs index d5c53f9..151500b 100644 --- a/Copy/Clients/FTP.cs +++ b/Copy/Clients/FTP.cs @@ -1,6 +1,7 @@ using Copy.Types; using FluentFTP; using System.Net; +using System.Text.RegularExpressions; namespace Copy.Clients { @@ -37,7 +38,7 @@ public bool DoFileExist(string path) return FtpClient.FileExists(path); } - public string[] ListFiles(string path) + public string[] ListFiles(string path, CopyFilter filter) { if (!FtpClient.DirectoryExists(path)) { @@ -45,7 +46,12 @@ public string[] ListFiles(string path) throw new DirectoryNotFoundException($"Directory {path} does not exist"); } - FtpListItem[] files = FtpClient.GetListing(path); + Regex nameRegex = new(filter.Name); + Regex authorRegex = new(filter.Author); + + FtpListItem[] files = FtpClient.GetListing(path) + .Where(f => nameRegex.IsMatch(f.Name) && authorRegex.IsMatch(f.RawOwner) && f.Created >= filter.CreatedAfter && (ulong)f.Size <= filter.MaxSize && (ulong)f.Size >= filter.MinSize) + .ToArray(); return files.Select(f => Path.Combine(path, f.Name)).ToArray(); } diff --git a/Copy/Clients/Local.cs b/Copy/Clients/Local.cs index 1835dd6..64f6372 100644 --- a/Copy/Clients/Local.cs +++ b/Copy/Clients/Local.cs @@ -1,4 +1,7 @@ using Copy.Types; +using System.Runtime.Versioning; +using System.Security.Principal; +using System.Text.RegularExpressions; namespace Copy.Clients { @@ -28,7 +31,8 @@ public bool DoFileExist(string path) return File.Exists(path); } - public string[] ListFiles(string path) + [SupportedOSPlatform("windows")] + public string[] ListFiles(string path, CopyFilter filter) { if (!Directory.Exists(path)) { @@ -36,7 +40,17 @@ public string[] ListFiles(string path) throw new DirectoryNotFoundException($"Directory {path} does not exist"); } - return Directory.GetFiles(path); + Regex nameRegex = new(filter.Name); + Regex authorRegex = new(filter.Author); + + return Directory.GetFiles(path) + .Where(f => + { + FileInfo fileInfo = new(f); + bool authorMatch = authorRegex.IsMatch(fileInfo.GetAccessControl().GetOwner(typeof(NTAccount))?.Value ?? throw new FileOwnerNotFoundException($"Impossible to get owner of {f}")); + return nameRegex.IsMatch(Path.GetFileName(f)) && authorMatch && File.GetCreationTime(f) >= filter.CreatedAfter && (ulong)fileInfo.Length <= filter.MaxSize && (ulong)fileInfo.Length >= filter.MinSize; + }) + .ToArray(); } public Stream GetFile(string path) diff --git a/Copy/Clients/SFTP.cs b/Copy/Clients/SFTP.cs index 80c004e..d057a70 100644 --- a/Copy/Clients/SFTP.cs +++ b/Copy/Clients/SFTP.cs @@ -1,5 +1,6 @@ using Copy.Types; using Renci.SshNet; +using System.Text.RegularExpressions; namespace Copy.Clients { @@ -23,7 +24,7 @@ public SFTP(Client credentials) SftpClient.Connect(); } - public string[] ListFiles(string path) + public string[] ListFiles(string path, CopyFilter filter) { if (!SftpClient.Exists(path)) { @@ -31,7 +32,13 @@ public string[] ListFiles(string path) throw new DirectoryNotFoundException($"Directory {path} does not exist"); } - var files = SftpClient.ListDirectory(path); + if (filter.Author != ".*") Logger.Warn($"Filtering by author is not supported by SFTP"); + + Regex nameRegex = new(filter.Name); + Regex authorRegex = new(filter.Author); + + var files = SftpClient.ListDirectory(path) + .Where(f => nameRegex.IsMatch(f.Name) && f.LastWriteTime >= filter.CreatedAfter && (ulong)f.Length <= filter.MaxSize && (ulong)f.Length >= filter.MinSize); return files.Select(f => f.FullName.Remove(0, 1)).ToArray(); } diff --git a/Copy/Config.cs b/Copy/Config.cs index 42c07de..093f2ba 100644 --- a/Copy/Config.cs +++ b/Copy/Config.cs @@ -111,7 +111,10 @@ public static string GetDefault() Source = "source", Destination = "destination", Client = "SFTP", - Filter = "*.txt" + Filter = new CopyFilter() + { + Name = ".*\\.txt" + } }, new CopyTask() { @@ -119,7 +122,10 @@ public static string GetDefault() Destination = "destination", Client = "Local", Delete = true, - Filter = "*.txt" + Filter = new CopyFilter() + { + Name = ".*\\.txt" + } }, new CopyTask() { diff --git a/Copy/Program.cs b/Copy/Program.cs index 05eae99..9fcc73b 100644 --- a/Copy/Program.cs +++ b/Copy/Program.cs @@ -1,6 +1,5 @@ using Copy.Clients; using Copy.Types; -using System.Text.RegularExpressions; namespace Copy { @@ -63,10 +62,9 @@ public static void Main(string[] args) throw new ClientNotFoundException($"Client {task.Source} not found"); } - Regex filter = new(task.Filter); - IEnumerable files = client.ListFiles(task.Source).Where(f => filter.IsMatch(f)); + string[] files = client.ListFiles(task.Source, task.Filter); - Logger.Info($"Treating {files.Count()} files"); + Logger.Info($"Treating {files.Length} files"); foreach (string file in files) { @@ -86,7 +84,7 @@ public static void Main(string[] args) Logger.Debug($"Copied {file} to {task.Destination}"); } - Logger.Info($"Copied {files.Count()} files from {task.Source} to {task.Destination} using {task.Client}"); + Logger.Info($"Copied {files.Length} files from {task.Source} to {task.Destination} using {task.Client}"); } Logger.Info("All tasks executed"); diff --git a/Copy/Types/CopyFilter.cs b/Copy/Types/CopyFilter.cs new file mode 100644 index 0000000..4dc25a1 --- /dev/null +++ b/Copy/Types/CopyFilter.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json; + +namespace Copy.Types +{ + /// + /// Filter for files to copy. + /// + public class CopyFilter + { + /// + /// Pattern the name of the files must match. + /// + [JsonProperty(PropertyName = "Name", Required = Required.DisallowNull)] + public string Name { get; set; } = ".*"; + /// + /// Pattern the author of the files must match. + /// + [JsonProperty(PropertyName = "Author", Required = Required.DisallowNull)] + public string Author { get; set; } = ".*"; + /// + /// Date the files must be created after. + /// + [JsonProperty(PropertyName = "CreatedAfter", Required = Required.DisallowNull)] + public DateTime CreatedAfter { get; set; } = DateTime.MinValue; + /// + /// Size the files must not exceed. + /// + [JsonProperty(PropertyName = "MaxSize", Required = Required.DisallowNull)] + public ulong MaxSize { get; set; } = ulong.MaxValue; + /// + /// Size the files must exceed. + /// + [JsonProperty(PropertyName = "MinSize", Required = Required.DisallowNull)] + public ulong MinSize { get; set; } = 0; + } +} diff --git a/Copy/Types/CopyTask.cs b/Copy/Types/CopyTask.cs index 75cb2e9..b95ced9 100644 --- a/Copy/Types/CopyTask.cs +++ b/Copy/Types/CopyTask.cs @@ -31,6 +31,6 @@ public class CopyTask /// Filter for files to copy. /// [JsonProperty(PropertyName = "Filter", Required = Required.DisallowNull)] - public string Filter { get; set; } = ".*"; + public CopyFilter Filter { get; set; } = new(); } } \ No newline at end of file diff --git a/Copy/Types/Exceptions.cs b/Copy/Types/Exceptions.cs index b40414f..fb70a2a 100644 --- a/Copy/Types/Exceptions.cs +++ b/Copy/Types/Exceptions.cs @@ -7,4 +7,12 @@ internal class ClientNotFoundException(string message) : Exception(message) { } + + /// + /// Exception thrown when a file owner is not found. + /// + /// The message to display. + internal class FileOwnerNotFoundException(string message) : Exception(message) + { + } } diff --git a/Copy/Types/IClient.cs b/Copy/Types/IClient.cs index 8b5a3fd..bd0a0a7 100644 --- a/Copy/Types/IClient.cs +++ b/Copy/Types/IClient.cs @@ -13,8 +13,9 @@ internal interface IClient : IDisposable /// List files in a directory. /// /// Path to the directory. + /// Filter for files to list. /// Array of file names. - string[] ListFiles(string path); + string[] ListFiles(string path, CopyFilter filter); /// /// Check if a file exists. ///