using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; namespace ZeroLevel.Services.FileSystem { public sealed class PeriodicFileSystemWatcher : IDisposable { private long _updateSourceTaskKey; private readonly string _sourceFolder; private readonly string _temporaryFolder; private readonly TimeSpan _period; private readonly Action _callback; private readonly HashSet _extensions; public event Action OnStartMovingFilesToTemporary = delegate { }; public event Action OnMovingFileToTemporary = delegate { }; public event Action OnCompleteMovingFilesToTemporary = delegate { }; private readonly bool _autoRemoveTempFileAfterCallback = false; private readonly bool _useSubdirectories = false; public PeriodicFileSystemWatcher(TimeSpan period, string watch_folder, string temp_folder, Action callback , IEnumerable extensions = null , bool removeTempFileAfterCallback = false , bool useSubdirectories = false) { if (string.IsNullOrWhiteSpace(watch_folder)) { throw new ArgumentNullException(nameof(watch_folder)); } if (callback == null) { throw new ArgumentNullException(nameof(callback)); } _extensions = new HashSet(extensions?.Select(e => e.ToLowerInvariant()) ?? Enumerable.Empty()); _useSubdirectories = useSubdirectories; _autoRemoveTempFileAfterCallback = removeTempFileAfterCallback; _callback = callback; _sourceFolder = watch_folder; _temporaryFolder = temp_folder; _period = period; if (Path.IsPathRooted(_temporaryFolder) == false) { _temporaryFolder = Path.Combine(Configuration.BaseDirectory, _temporaryFolder); } if (false == Directory.Exists(_temporaryFolder)) { Directory.CreateDirectory(_temporaryFolder); } } public void Start() { _updateSourceTaskKey = Sheduller.RemindEvery(_period, CheckSourceFolder); } public void Stop() { Sheduller.Remove(_updateSourceTaskKey); } private void CheckSourceFolder() { string[] files = null; try { files = GetFilesFromSource(); } catch (Exception ex) { Log.SystemError(ex, $"[PeriodicFileSystemWatcher.CheckSourceFolder] Failed to process input directory '{_sourceFolder}'"); } if (files == null || files.Length == 0) { return; } foreach (Action startFilesMoveingCallback in OnStartMovingFilesToTemporary.GetInvocationList()) startFilesMoveingCallback.BeginInvoke(files.Length, null, null); foreach (var file in files) { if (!File.Exists(file)) { Log.Warning($"[PeriodicFileSystemWatcher.CheckSourceFolder] Find '{file}' does not exists"); continue; } Log.Debug($"[PeriodicFileSystemWatcher.CheckSourceFolder] Find new file {file}"); if (FSUtils.IsFileLocked(new FileInfo(file))) { continue; } string tempFile; try { tempFile = MoveToTemporary(file); if (string.IsNullOrWhiteSpace(tempFile)) { Log.SystemWarning($"[PeriodicFileSystemWatcher.CheckSourceFolder] Failed to move file '{file}' to temporary directory '{_temporaryFolder}'. Without system error!"); continue; } foreach (Action moveFileToTempCallback in OnMovingFileToTemporary.GetInvocationList()) moveFileToTempCallback.BeginInvoke(file, tempFile, null, null); } catch (Exception ex) { Log.SystemError(ex, $"[PeriodicFileSystemWatcher.CheckSourceFolder] Failed to attempt to move file '{file}' to temporary directory '{_temporaryFolder}'"); continue; } Log.Debug($"[PeriodicFileSystemWatcher.CheckSourceFolder] Handle file {file}"); try { _callback.Invoke(new FileMeta(Path.GetFileName(file), tempFile)); } catch (Exception ex) { Log.SystemError(ex, $"[PeriodicFileSystemWatcher.CheckSourceFolder] Fault callback for file '{tempFile}'"); } try { if (_autoRemoveTempFileAfterCallback) { File.Delete(tempFile); } } catch (Exception ex) { Log.SystemError(ex, $"[PeriodicFileSystemWatcher.CheckSourceFolder] Fault delete file {tempFile}"); } } foreach (Action completeFileMovingCallback in OnCompleteMovingFilesToTemporary.GetInvocationList()) completeFileMovingCallback.BeginInvoke(null, null); } /// /// Moving a file to a temporary directory /// private string MoveToTemporary(string from) { if (from == null) { throw new ArgumentException("from"); } string tempFile = Path.Combine(_temporaryFolder, Path.GetFileName(from)); if (File.Exists(tempFile)) { tempFile = TrySolveCollision(tempFile); } File.Copy(from, tempFile, false); if (File.Exists(tempFile)) { File.Delete(from); return tempFile; } return null; } /// /// Resolving collisions in filenames in the temporary directory /// private static string TrySolveCollision(string file) { if (file == null) { throw new ArgumentNullException("file"); } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(file); string extension = Path.GetExtension(file); string directoryName = Path.GetDirectoryName(file); if (directoryName != null) { int num = 0; do { num++; } while (File.Exists(Path.Combine(directoryName, fileNameWithoutExtension + "_" + num.ToString(CultureInfo.CurrentCulture) + extension))); return Path.Combine(directoryName, fileNameWithoutExtension + "_" + num.ToString(CultureInfo.CurrentCulture) + extension); } throw new ArgumentException("folder"); } /// /// Getting a list of files from the input directory /// private string[] GetFilesFromSource() { string[] files; if (_extensions.Count > 0) { files = Directory.GetFiles(_sourceFolder, "*.*", _useSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly) ?.Where(f => _extensions.Contains(Path.GetExtension(f).ToLowerInvariant())) ?.ToArray(); } else { files = Directory.GetFiles(_sourceFolder, "*.*", _useSubdirectories ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); } if (files != null) { Array.Sort(files, FileNameSortCompare); } return files; } /// /// File Name Comparison /// private static int FileNameSortCompare(string x, string y) { int num; int num2; if (int.TryParse(Path.GetFileNameWithoutExtension(x), out num) && int.TryParse(Path.GetFileNameWithoutExtension(y), out num2)) { return num - num2; } return string.Compare(x, y, StringComparison.InvariantCultureIgnoreCase); } public void Dispose() { Sheduller.Remove(_updateSourceTaskKey); } } }