Skip to content

Commit fd1dcb3

Browse files
committed
Add abstract FileWatchers
1 parent 15e0164 commit fd1dcb3

2 files changed

Lines changed: 496 additions & 0 deletions

File tree

src/IO/BaseFileSystemWatcher.cs

Lines changed: 366 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,366 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Text.RegularExpressions;
6+
using Oxide.Core;
7+
8+
namespace Oxide.IO
9+
{
10+
internal abstract class BaseFileSystemWatcher : IFileSystemWatcher
11+
{
12+
private sealed class GuidDisposable : IDisposable
13+
{
14+
public Guid Id { get; }
15+
private BaseFileSystemWatcher Watcher { get; }
16+
17+
public GuidDisposable(Guid id, BaseFileSystemWatcher watcher)
18+
{
19+
Id = id;
20+
Watcher = watcher;
21+
}
22+
23+
~GuidDisposable() => Dispose();
24+
25+
public void Dispose() => Watcher.Unsubscribe(Id);
26+
}
27+
28+
public string Directory { get; }
29+
public bool IncludeSubDirectories { get; }
30+
public NotifyMask Filter { get; }
31+
private List<Regex> filters { get; }
32+
private readonly Dictionary<Guid, IObserver<FileSystemEvent>> registeredWatchers;
33+
private readonly Dictionary<int, string> subscribedDirectories;
34+
35+
protected IFileSystem FileSystem { get; }
36+
protected StringComparison CompareMethod { get; }
37+
38+
#region Initialization
39+
40+
protected BaseFileSystemWatcher(string directory, bool subDirs, NotifyMask filter, IFileSystem fs, StringComparison comparer)
41+
{
42+
CompareMethod = comparer;
43+
registeredWatchers = new Dictionary<Guid, IObserver<FileSystemEvent>>();
44+
subscribedDirectories = new Dictionary<int, string>();
45+
FileSystem = fs;
46+
Directory = fs.ResolvePath(directory);
47+
IncludeSubDirectories = subDirs;
48+
Filter = filter;
49+
filters = new List<Regex>();
50+
}
51+
52+
public virtual void BeginInit()
53+
{
54+
LogDebug("Initializing FileSystem Watcher. . .");
55+
}
56+
57+
public virtual void EndInit()
58+
{
59+
WatchDirectory(Directory, IncludeSubDirectories);
60+
Interface.Oxide.LogInfo($"Initialized FileSystem {GetType().Name} Watcher!");
61+
}
62+
63+
#endregion
64+
65+
public IDisposable Subscribe(IObserver<FileSystemEvent> observer)
66+
{
67+
if (observer == null)
68+
{
69+
throw new ArgumentNullException(nameof(observer));
70+
}
71+
72+
lock (registeredWatchers)
73+
{
74+
if (registeredWatchers.Values.Contains(observer))
75+
{
76+
throw new ArgumentException("Observer already is subscribed", nameof(observer));
77+
}
78+
79+
GuidDisposable @guid = new GuidDisposable(Guid.NewGuid(), this);
80+
registeredWatchers.Add(@guid.Id, observer);
81+
return @guid;
82+
}
83+
}
84+
85+
private void Unsubscribe(Guid guid)
86+
{
87+
lock (registeredWatchers)
88+
{
89+
registeredWatchers.Remove(guid);
90+
}
91+
}
92+
93+
protected void WatchDirectory(string directory, bool recurse = false)
94+
{
95+
directory = FileSystem.ResolvePath(directory);
96+
97+
if (!directory.StartsWith(Directory, CompareMethod))
98+
{
99+
LogDebug("Failed to watch directory, path does not inherit parent.");
100+
return;
101+
}
102+
103+
lock (filters)
104+
{
105+
foreach (Regex filter in filters)
106+
{
107+
if (filter.IsMatch(directory))
108+
{
109+
LogDebug("Failed to watch directory, Ignore pattern found");
110+
return;
111+
}
112+
}
113+
}
114+
115+
lock (subscribedDirectories)
116+
{
117+
if (InternalIsMonitoredDirectory(directory))
118+
{
119+
LogDebug("Failed to watch directory, directory is already watched.");
120+
return;
121+
}
122+
123+
int id = SubscribeTo(directory);
124+
subscribedDirectories[id] = directory;
125+
}
126+
127+
if (!recurse)
128+
{
129+
return;
130+
}
131+
132+
string[] dirs = System.IO.Directory.GetDirectories(directory, "*", SearchOption.TopDirectoryOnly);
133+
134+
for (int i = 0; i < dirs.Length; i++)
135+
{
136+
LogDebug($"Recursively adding subdirectory: {dirs[i]}");
137+
WatchDirectory(dirs[i], recurse);
138+
}
139+
}
140+
141+
public IFileSystemWatcher ClearFilters()
142+
{
143+
lock (filters)
144+
{
145+
filters.Clear();
146+
}
147+
return this;
148+
}
149+
150+
public IFileSystemWatcher Ignore(string pattern)
151+
{
152+
if (string.IsNullOrEmpty(pattern))
153+
{
154+
return this;
155+
}
156+
157+
pattern = Regex.Escape(pattern)
158+
.Replace("\\*", ".+")
159+
.Replace("\\?", ".");
160+
161+
pattern = "^" + pattern + "$";
162+
163+
lock (filters)
164+
{
165+
foreach (Regex reg in filters)
166+
{
167+
if (reg.ToString() == pattern)
168+
{
169+
return this;
170+
}
171+
}
172+
173+
filters.Add(new Regex(pattern, RegexOptions.Compiled | RegexOptions.IgnoreCase));
174+
}
175+
176+
return this;
177+
}
178+
179+
protected abstract int SubscribeTo(string directory);
180+
181+
protected abstract bool UnsubscribeFrom(int id);
182+
183+
public bool IsMonitoredDirectory(int id)
184+
{
185+
lock (subscribedDirectories)
186+
{
187+
return subscribedDirectories.ContainsKey(id);
188+
}
189+
}
190+
191+
public bool IsMonitoredDirectory(string directory)
192+
{
193+
directory = FileSystem.ResolvePath(directory);
194+
lock (subscribedDirectories)
195+
{
196+
return InternalIsMonitoredDirectory(directory);
197+
}
198+
}
199+
200+
private bool InternalIsMonitoredDirectory(string directory)
201+
{
202+
return subscribedDirectories.ContainsValue(directory);
203+
}
204+
205+
private List<int> removeBuffer = new List<int>();
206+
protected bool UnwatchDirectory(string directory)
207+
{
208+
bool removed = false;
209+
lock (subscribedDirectories)
210+
{
211+
foreach (KeyValuePair<int,string> pair in subscribedDirectories)
212+
{
213+
if (pair.Value.StartsWith(directory, CompareMethod))
214+
{
215+
UnsubscribeFrom(pair.Key);
216+
removeBuffer.Add(pair.Key);
217+
removed = true;
218+
}
219+
}
220+
221+
foreach (int i in removeBuffer)
222+
{
223+
subscribedDirectories.Remove(i);
224+
}
225+
226+
removeBuffer.Clear();
227+
}
228+
229+
return removed;
230+
}
231+
232+
protected string GetDirectoryById(int id)
233+
{
234+
lock (subscribedDirectories)
235+
{
236+
return subscribedDirectories.TryGetValue(id, out string dir) ? dir : null;
237+
}
238+
}
239+
240+
public void Dispose()
241+
{
242+
lock (registeredWatchers)
243+
{
244+
foreach (IObserver<FileSystemEvent> observer in registeredWatchers.Values)
245+
{
246+
try
247+
{
248+
observer.OnCompleted();
249+
}
250+
catch
251+
{
252+
// Ignored
253+
}
254+
}
255+
256+
registeredWatchers.Clear();
257+
}
258+
259+
lock (subscribedDirectories)
260+
{
261+
foreach (KeyValuePair<int,string> directory in subscribedDirectories)
262+
{
263+
UnsubscribeFrom(directory.Key);
264+
}
265+
266+
subscribedDirectories.Clear();
267+
}
268+
269+
OnDispose();
270+
}
271+
272+
protected virtual void OnDispose()
273+
{
274+
}
275+
276+
protected virtual bool ShouldIgnore(string directory, string name, NotifyMask mask)
277+
{
278+
string path = Path.Combine(directory, name);
279+
lock (filters)
280+
{
281+
foreach (Regex regex in filters)
282+
{
283+
if (regex.IsMatch(path))
284+
{
285+
return true;
286+
}
287+
}
288+
}
289+
290+
return false;
291+
}
292+
293+
protected virtual void OnFileSystemEvent(string directory, string name, NotifyMask mask)
294+
{
295+
switch (mask)
296+
{
297+
case NotifyMask.OnMovedFrom | NotifyMask.DirectoryOnly:
298+
case NotifyMask.OnDeleted | NotifyMask.DirectoryOnly:
299+
UnwatchDirectory(Path.Combine(directory, name));
300+
break;
301+
302+
case NotifyMask.OnMovedTo | NotifyMask.DirectoryOnly:
303+
WatchDirectory(Path.Combine(directory, name), IncludeSubDirectories);
304+
break;
305+
306+
case NotifyMask.OnCreated | NotifyMask.DirectoryOnly:
307+
WatchDirectory(Path.Combine(directory, name), IncludeSubDirectories);
308+
break;
309+
}
310+
311+
if (ShouldIgnore(directory, name, mask))
312+
{
313+
return;
314+
}
315+
316+
FileSystemEvent evt = new FileSystemEvent(directory, name, mask);
317+
318+
lock (registeredWatchers)
319+
{
320+
foreach (IObserver<FileSystemEvent> observer in registeredWatchers.Values)
321+
{
322+
try
323+
{
324+
observer.OnNext(evt);
325+
}
326+
catch
327+
{
328+
// Ignored
329+
}
330+
}
331+
}
332+
}
333+
334+
protected virtual void OnFileSystemError(Exception exception)
335+
{
336+
lock (registeredWatchers)
337+
{
338+
foreach (IObserver<FileSystemEvent> observer in registeredWatchers.Values)
339+
{
340+
try
341+
{
342+
observer.OnError(exception);
343+
}
344+
catch
345+
{
346+
// Ignored
347+
}
348+
}
349+
}
350+
}
351+
352+
protected void LogDebug(string message)
353+
{
354+
#if DEBUG
355+
Interface.Oxide.LogDebug($"[{GetType().Name}] {message}");
356+
#endif
357+
}
358+
359+
protected static void CleanName(string fullPath, out string directory, out string name)
360+
{
361+
fullPath = fullPath.TrimEnd(Path.DirectorySeparatorChar);
362+
directory = Path.GetDirectoryName(fullPath);
363+
name = Path.GetFileName(fullPath);
364+
}
365+
}
366+
}

0 commit comments

Comments
 (0)