16 changed files with 252 additions and 6 deletions
@ -0,0 +1,15 @@ |
|||||||
|
namespace BitPlus.IO.Directory.Watcher; |
||||||
|
|
||||||
|
public sealed class DirectoryFileEventArgs : EventArgs |
||||||
|
{ |
||||||
|
public string FullPath { get; } |
||||||
|
public EventMode Mode { get; } |
||||||
|
public string? OldFullPath { get; } |
||||||
|
|
||||||
|
public DirectoryFileEventArgs(string fullPath, EventMode mode, string? oldFullPath = null) |
||||||
|
{ |
||||||
|
this.FullPath = fullPath; |
||||||
|
this.Mode = mode; |
||||||
|
this.OldFullPath = oldFullPath; |
||||||
|
} |
||||||
|
} |
||||||
@ -1,6 +1,125 @@ |
|||||||
namespace BitPlus.IO.Directory.Watcher; |
using System.Collections.Concurrent; |
||||||
|
using Dir = System.IO.Directory; |
||||||
|
|
||||||
public class DirectoryWatcher |
namespace BitPlus.IO.Directory.Watcher; |
||||||
|
|
||||||
|
public sealed class DirectoryWatcher : IDisposable |
||||||
{ |
{ |
||||||
|
private readonly FileSystemWatcher _watcher; |
||||||
|
private readonly ConcurrentDictionary<string, DateTimeOffset> _lastEventTimes = new(); |
||||||
|
private readonly TimeSpan _dedupeWindow = TimeSpan.FromMilliseconds(200); |
||||||
|
private bool _disposed; |
||||||
|
|
||||||
|
public string TargetDirectory { get; } |
||||||
|
public bool IncludeSubdirectories |
||||||
|
{ |
||||||
|
get => this._watcher.IncludeSubdirectories; |
||||||
|
set => this._watcher.IncludeSubdirectories = value; |
||||||
|
} |
||||||
|
|
||||||
|
public event EventHandler<DirectoryFileEventArgs>? FileCreated; |
||||||
|
public event EventHandler<DirectoryFileEventArgs>? FileChanged; |
||||||
|
public event EventHandler<DirectoryFileEventArgs>? FileDeleted; |
||||||
|
public event EventHandler<DirectoryFileEventArgs>? FileRenamed; |
||||||
|
public event EventHandler<ErrorEventArgs>? WatcherError; |
||||||
|
|
||||||
|
public DirectoryWatcher(string targetDirectory, string filter = "*.*") |
||||||
|
{ |
||||||
|
if (string.IsNullOrWhiteSpace(targetDirectory)) |
||||||
|
throw new ArgumentException("Target directory cannot be empty.", nameof(targetDirectory)); |
||||||
|
|
||||||
|
if (!Dir.Exists(targetDirectory)) |
||||||
|
throw new DirectoryNotFoundException(targetDirectory); |
||||||
|
|
||||||
|
this.TargetDirectory = targetDirectory; |
||||||
|
|
||||||
|
this._watcher = new FileSystemWatcher(this.TargetDirectory, filter) |
||||||
|
{ |
||||||
|
NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite | NotifyFilters.CreationTime, |
||||||
|
IncludeSubdirectories = false, |
||||||
|
EnableRaisingEvents = false |
||||||
|
}; |
||||||
|
|
||||||
|
this._watcher.Created += this.OnCreated; |
||||||
|
this._watcher.Changed += this.OnChanged; |
||||||
|
this._watcher.Deleted += this.OnDeleted; |
||||||
|
this._watcher.Renamed += this.OnRenamed; |
||||||
|
this._watcher.Error += this.OnError; |
||||||
|
} |
||||||
|
|
||||||
|
public void Start() |
||||||
|
{ |
||||||
|
this.ThrowIfDisposed(); |
||||||
|
this._watcher.EnableRaisingEvents = true; |
||||||
|
} |
||||||
|
|
||||||
|
public void Stop() |
||||||
|
{ |
||||||
|
this.ThrowIfDisposed(); |
||||||
|
this._watcher.EnableRaisingEvents = false; |
||||||
|
} |
||||||
|
|
||||||
|
private void OnCreated(object? sender, FileSystemEventArgs e) |
||||||
|
{ |
||||||
|
if (this.ShouldRaise(e.FullPath)) |
||||||
|
FileCreated?.Invoke(this, new DirectoryFileEventArgs(e.FullPath, EventMode.Created)); |
||||||
|
} |
||||||
|
|
||||||
|
private void OnChanged(object? sender, FileSystemEventArgs e) |
||||||
|
{ |
||||||
|
if (this.ShouldRaise(e.FullPath)) |
||||||
|
FileChanged?.Invoke(this, new DirectoryFileEventArgs(e.FullPath, EventMode.Changed)); |
||||||
|
} |
||||||
|
|
||||||
|
private void OnDeleted(object? sender, FileSystemEventArgs e) |
||||||
|
{ |
||||||
|
if (this.ShouldRaise(e.FullPath)) |
||||||
|
FileDeleted?.Invoke(this, new DirectoryFileEventArgs(e.FullPath, EventMode.Deleted)); |
||||||
|
} |
||||||
|
|
||||||
|
private void OnRenamed(object? sender, RenamedEventArgs e) |
||||||
|
{ |
||||||
|
if (this.ShouldRaise(e.FullPath)) |
||||||
|
{ |
||||||
|
var args = new DirectoryFileEventArgs(e.FullPath, EventMode.Renamed, e.OldFullPath); |
||||||
|
FileChanged?.Invoke(this, args); |
||||||
|
FileRenamed?.Invoke(this, args); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private void OnError(object? sender, ErrorEventArgs e) |
||||||
|
=> WatcherError?.Invoke(this, e); |
||||||
|
|
||||||
|
private bool ShouldRaise(string fullPath) |
||||||
|
{ |
||||||
|
var now = DateTimeOffset.UtcNow; |
||||||
|
|
||||||
|
if (this._lastEventTimes.TryGetValue(fullPath, out var last)) |
||||||
|
{ |
||||||
|
if (now - last < this._dedupeWindow) |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
this._lastEventTimes[fullPath] = now; |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
private void ThrowIfDisposed() |
||||||
|
{ |
||||||
|
if (this._disposed) |
||||||
|
throw new ObjectDisposedException(nameof(DirectoryWatcher)); |
||||||
|
} |
||||||
|
|
||||||
|
public void Dispose() |
||||||
|
{ |
||||||
|
if (this._disposed) return; |
||||||
|
this._disposed = true; |
||||||
|
|
||||||
|
this._watcher.Created -= this.OnCreated; |
||||||
|
this._watcher.Changed -= this.OnChanged; |
||||||
|
this._watcher.Deleted -= this.OnDeleted; |
||||||
|
this._watcher.Renamed -= this.OnRenamed; |
||||||
|
this._watcher.Error -= this.OnError; |
||||||
|
this._watcher.Dispose(); |
||||||
|
} |
||||||
} |
} |
||||||
@ -0,0 +1,9 @@ |
|||||||
|
namespace BitPlus.IO.Directory.Watcher; |
||||||
|
|
||||||
|
public enum EventMode |
||||||
|
{ |
||||||
|
Created, |
||||||
|
Changed, |
||||||
|
Deleted, |
||||||
|
Renamed |
||||||
|
} |
||||||
@ -0,0 +1,23 @@ |
|||||||
|
{ |
||||||
|
"runtimeTarget": { |
||||||
|
"name": ".NETCoreApp,Version=v10.0", |
||||||
|
"signature": "" |
||||||
|
}, |
||||||
|
"compilationOptions": {}, |
||||||
|
"targets": { |
||||||
|
".NETCoreApp,Version=v10.0": { |
||||||
|
"BitPlus.IO.Directory.Watcher/1.0.0": { |
||||||
|
"runtime": { |
||||||
|
"BitPlus.IO.Directory.Watcher.dll": {} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"libraries": { |
||||||
|
"BitPlus.IO.Directory.Watcher/1.0.0": { |
||||||
|
"type": "project", |
||||||
|
"serviceable": false, |
||||||
|
"sha512": "" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Binary file not shown.
Binary file not shown.
@ -1,7 +1,34 @@ |
|||||||
internal class Program |
using BitPlus.IO.Directory.Watcher; |
||||||
|
|
||||||
|
internal class Program |
||||||
{ |
{ |
||||||
private static void Main(string[] args) |
private static void Main(string[] args) |
||||||
{ |
{ |
||||||
Console.WriteLine("Hello, World!"); |
var path = args.Length > 0 ? args[0] : null; |
||||||
|
|
||||||
|
while (string.IsNullOrWhiteSpace(path) || !Directory.Exists(path)) |
||||||
|
{ |
||||||
|
Console.Write("감시할 폴더 경로를 입력하세요(Enter the folder path to monitor.): "); |
||||||
|
path = Console.ReadLine(); |
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(path)) |
||||||
|
Console.WriteLine("경로가 비어 있습니다.(The path is empty.)"); |
||||||
|
else if (!Directory.Exists(path)) |
||||||
|
Console.WriteLine("폴더가 존재하지 않습니다.(The folder does not exist.)"); |
||||||
|
} |
||||||
|
|
||||||
|
using var watcher = new DirectoryWatcher(path); |
||||||
|
watcher.FileCreated += (_, e) => Console.WriteLine($"[CREATE] {e.FullPath}"); |
||||||
|
watcher.FileChanged += (_, e) => Console.WriteLine($"[CHANGE] {e.FullPath}"); |
||||||
|
watcher.FileDeleted += (_, e) => Console.WriteLine($"[DELETE] {e.FullPath}"); |
||||||
|
watcher.FileRenamed += (_, e) => Console.WriteLine($"[RENAME] {e.OldFullPath} -> {e.FullPath}"); |
||||||
|
watcher.WatcherError += (_, e) => Console.WriteLine($"[ERROR] {e.GetException().Message}"); |
||||||
|
|
||||||
|
watcher.Start(); |
||||||
|
Console.WriteLine($"감시 시작(Start the surveillance): {path}"); |
||||||
|
Console.WriteLine("종료하려면 Enter를 누르세요...(Press Enter to exit)"); |
||||||
|
|
||||||
|
Console.ReadLine(); |
||||||
|
watcher.Stop(); |
||||||
} |
} |
||||||
} |
} |
||||||
Binary file not shown.
Binary file not shown.
@ -0,0 +1,39 @@ |
|||||||
|
{ |
||||||
|
"runtimeTarget": { |
||||||
|
"name": ".NETCoreApp,Version=v10.0", |
||||||
|
"signature": "" |
||||||
|
}, |
||||||
|
"compilationOptions": {}, |
||||||
|
"targets": { |
||||||
|
".NETCoreApp,Version=v10.0": { |
||||||
|
"Sample/1.0.0": { |
||||||
|
"dependencies": { |
||||||
|
"BitPlus.IO.Directory.Watcher": "1.0.0" |
||||||
|
}, |
||||||
|
"runtime": { |
||||||
|
"Sample.dll": {} |
||||||
|
} |
||||||
|
}, |
||||||
|
"BitPlus.IO.Directory.Watcher/1.0.0": { |
||||||
|
"runtime": { |
||||||
|
"BitPlus.IO.Directory.Watcher.dll": { |
||||||
|
"assemblyVersion": "1.0.0.0", |
||||||
|
"fileVersion": "1.0.0.0" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"libraries": { |
||||||
|
"Sample/1.0.0": { |
||||||
|
"type": "project", |
||||||
|
"serviceable": false, |
||||||
|
"sha512": "" |
||||||
|
}, |
||||||
|
"BitPlus.IO.Directory.Watcher/1.0.0": { |
||||||
|
"type": "project", |
||||||
|
"serviceable": false, |
||||||
|
"sha512": "" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,12 @@ |
|||||||
|
{ |
||||||
|
"runtimeOptions": { |
||||||
|
"tfm": "net10.0", |
||||||
|
"framework": { |
||||||
|
"name": "Microsoft.NETCore.App", |
||||||
|
"version": "10.0.0" |
||||||
|
}, |
||||||
|
"configProperties": { |
||||||
|
"System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
After Width: | Height: | Size: 76 KiB |
Loading…
Reference in new issue