11extern alias References ;
22
33using Oxide . Core . Libraries ;
4- using References ::Mono . Posix ;
54using System ;
65using System . Collections . Generic ;
76using System . IO ;
8- #if ! NETSTANDARD
9- using System . Security . Permissions ;
10- #endif
117using System . Text . RegularExpressions ;
8+ using Oxide . IO ;
129
1310namespace Oxide . Core . Plugins . Watchers
1411{
1512 /// <summary>
1613 /// Represents a file system watcher
1714 /// </summary>
18- public sealed class FSWatcher : PluginChangeWatcher
15+ public sealed class FSWatcher : PluginChangeWatcher , IObserver < FileSystemEvent >
1916 {
2017 private class QueuedChange
2118 {
2219 internal WatcherChangeTypes type ;
2320 internal Timer . TimerInstance timer ;
2421 }
2522
26- // The filesystem watcher
27- private FileSystemWatcher watcher ;
28-
2923 // The plugin list
3024 private ICollection < string > watchedPlugins ;
25+ private Timer timers ;
26+ private readonly string _directory ;
27+ private readonly Regex filter ;
28+ private readonly IFileSystemWatcher watcher ;
29+ private readonly IDisposable subscription ;
3130
32- // Changes are buffered briefly to avoid duplicate events
33- private Dictionary < string , QueuedChange > changeQueue ;
31+ public FSWatcher ( IFileSystemWatcher watcher , string watchedDirectory , string fileFilter = "*" )
32+ {
33+ if ( ! watchedDirectory . StartsWith ( watcher . Directory , StringComparison . InvariantCulture ) )
34+ {
35+ throw new ArgumentException ( $ "Path must be begin with { watcher . Directory } ", nameof ( watchedDirectory ) ) ;
36+ }
3437
35- private Timer timers ;
38+ if ( string . IsNullOrEmpty ( fileFilter ) )
39+ {
40+ fileFilter = "*" ;
41+ }
3642
37- private Dictionary < string , FileSystemWatcher > m_symlinkWatchers = new Dictionary < string , FileSystemWatcher > ( ) ;
43+ _directory = watchedDirectory ;
44+ fileFilter = Regex . Escape ( fileFilter )
45+ . Replace ( "\\ *" , ".*" )
46+ . Replace ( "\\ ?" , "." ) ;
47+ fileFilter = "^" + fileFilter + "$" ;
48+ filter = new Regex ( fileFilter , RegexOptions . Compiled | RegexOptions . IgnoreCase ) ;
49+ this . watcher = watcher ;
3850
39- /// <summary>
40- /// Initializes a new instance of the FSWatcher class
41- /// </summary>
42- /// <param name="directory"></param>
43- /// <param name="filter"></param>
44- public FSWatcher ( string directory , string filter )
45- {
4651 watchedPlugins = new HashSet < string > ( ) ;
47- changeQueue = new Dictionary < string , QueuedChange > ( ) ;
4852 timers = Interface . Oxide . GetLibrary < Timer > ( ) ;
4953
5054 if ( Interface . Oxide . Config . Options . PluginWatchers )
5155 {
52- LoadWatcher ( directory , filter ) ;
53-
54- // Watch symlinked files
55- if ( Environment . OSVersion . Platform == PlatformID . Unix )
56- {
57- foreach ( FileInfo fileInfo in new DirectoryInfo ( directory ) . GetFiles ( filter ) )
58- {
59- if ( IsFileSymlink ( fileInfo . FullName ) )
60- {
61- LoadWatcherSymlink ( fileInfo . FullName ) ;
62- }
63- }
64- }
56+ subscription = this . watcher . Subscribe ( this ) ;
6557 }
6658 else
6759 {
6860 Interface . Oxide . LogWarning ( "Automatic plugin reloading and unloading has been disabled" ) ;
6961 }
7062 }
7163
72- private bool IsFileSymlink ( string path )
73- {
74- return ( File . GetAttributes ( path ) & FileAttributes . ReparsePoint ) > 0 ;
75- }
76-
77-
78- #if ! NETSTANDARD
79- [ PermissionSet ( SecurityAction . Demand , Name = "FullTrust" ) ]
80- #endif
81- private void LoadWatcherSymlink ( string path )
82- {
83- string realPath = Syscall . readlink ( path ) ;
84- string realDirName = Path . GetDirectoryName ( realPath ) ;
85- string realFileName = Path . GetFileName ( realPath ) ;
86-
87- void symlinkTarget_Changed ( object sender , FileSystemEventArgs e ) => watcher_Changed ( sender , e ) ;
88-
89- FileSystemWatcher watcher = new FileSystemWatcher ( realDirName , realFileName ) ;
90- m_symlinkWatchers [ path ] = watcher ;
91- watcher . Changed += symlinkTarget_Changed ;
92- watcher . Created += symlinkTarget_Changed ;
93- watcher . Deleted += symlinkTarget_Changed ;
94- watcher . Error += watcher_Error ;
95- watcher . NotifyFilter = NotifyFilters . LastWrite ;
96- watcher . IncludeSubdirectories = false ;
97- watcher . EnableRaisingEvents = true ;
98- }
99-
10064 /// <summary>
101- /// Loads the filesystem watcher
65+ /// Initializes a new instance of the FSWatcher class
10266 /// </summary>
10367 /// <param name="directory"></param>
10468 /// <param name="filter"></param>
105- #if ! NETSTANDARD
106- [ PermissionSet ( SecurityAction . Demand , Name = "FullTrust" ) ]
107- #endif
108- private void LoadWatcher ( string directory , string filter )
69+ public FSWatcher ( string directory , string filter = "*" ) : this ( Interface . Oxide . FileWatcher , directory , filter )
10970 {
110- // Create the watcher
111- watcher = new FileSystemWatcher ( directory , filter ) ;
112- watcher . Changed += watcher_Changed ;
113- watcher . Created += watcher_Changed ;
114- watcher . Deleted += watcher_Changed ;
115- watcher . Error += watcher_Error ;
116- watcher . NotifyFilter = NotifyFilters . LastWrite | NotifyFilters . FileName ;
117- watcher . IncludeSubdirectories = true ;
118- watcher . EnableRaisingEvents = true ;
119- GC . KeepAlive ( watcher ) ;
12071 }
12172
12273 /// <summary>
@@ -136,126 +87,77 @@ private void LoadWatcher(string directory, string filter)
13687 /// </summary>
13788 /// <param name="sender"></param>
13889 /// <param name="e"></param>
139- private void watcher_Changed ( object sender , FileSystemEventArgs e )
90+ private void watcher_Changed ( object sender , FileSystemEvent e )
14091 {
141- FileSystemWatcher watcher = ( FileSystemWatcher ) sender ;
142- int length = e . FullPath . Length - watcher . Path . Length - Path . GetExtension ( e . Name ) . Length - 1 ;
143- string subPath = e . FullPath . Substring ( watcher . Path . Length + 1 , length ) ;
144-
145- if ( ! changeQueue . TryGetValue ( subPath , out QueuedChange change ) )
146- {
147- change = new QueuedChange ( ) ;
148- changeQueue [ subPath ] = change ;
149- }
150- change . timer ? . Destroy ( ) ;
151- change . timer = null ;
152-
153- switch ( e . ChangeType )
154- {
155- case WatcherChangeTypes . Changed :
156- if ( change . type != WatcherChangeTypes . Created )
157- {
158- change . type = WatcherChangeTypes . Changed ;
159- }
160- break ;
161-
162- case WatcherChangeTypes . Created :
163- if ( change . type == WatcherChangeTypes . Deleted )
164- {
165- change . type = WatcherChangeTypes . Changed ;
166- }
167- else
168- {
169- change . type = WatcherChangeTypes . Created ;
170- }
171- break ;
172-
173- case WatcherChangeTypes . Deleted :
174- if ( change . type == WatcherChangeTypes . Created )
175- {
176- changeQueue . Remove ( subPath ) ;
177- return ;
178- }
179-
180- change . type = WatcherChangeTypes . Deleted ;
181- break ;
182- }
92+ string fullPath = Path . Combine ( e . Directory , e . Name ) ;
93+ int length = fullPath . Length - _directory . Length - Path . GetExtension ( e . Name ) . Length - 1 ;
94+ string subPath = fullPath . Substring ( _directory . Length + 1 , length ) ;
18395
18496 Interface . Oxide . NextTick ( ( ) =>
18597 {
186- if ( Environment . OSVersion . Platform == PlatformID . Unix )
98+ if ( Regex . Match ( subPath , @"include\\" , RegexOptions . IgnoreCase ) . Success )
18799 {
188- switch ( e . ChangeType )
100+ if ( e . Event == NotifyMask . OnCreated || e . Event == NotifyMask . OnModified )
189101 {
190- case WatcherChangeTypes . Created :
191- if ( IsFileSymlink ( e . FullPath ) )
192- {
193- LoadWatcherSymlink ( e . FullPath ) ;
194- }
195- break ;
196-
197- case WatcherChangeTypes . Deleted :
198- if ( m_symlinkWatchers . ContainsKey ( e . FullPath ) )
199- {
200- m_symlinkWatchers . TryGetValue ( e . FullPath , out FileSystemWatcher symlinkWatcher ) ;
201- symlinkWatcher ? . Dispose ( ) ;
202- m_symlinkWatchers . Remove ( e . FullPath ) ;
203- }
204- break ;
102+ FirePluginSourceChanged ( subPath ) ;
205103 }
104+
105+ return ;
206106 }
207107
208- change . timer ? . Destroy ( ) ;
209- change . timer = timers . Once ( .2f , ( ) =>
108+ switch ( e . Event )
210109 {
211- change . timer = null ;
212- changeQueue . Remove ( subPath ) ;
213-
214- if ( Regex . Match ( subPath , @"include\\" , RegexOptions . IgnoreCase ) . Success )
215- {
216- if ( change . type == WatcherChangeTypes . Created || change . type == WatcherChangeTypes . Changed )
110+ case NotifyMask . OnModified :
111+ if ( watchedPlugins . Contains ( subPath ) )
217112 {
218113 FirePluginSourceChanged ( subPath ) ;
219114 }
220-
221- return ;
222- }
223-
224- switch ( change . type )
225- {
226- case WatcherChangeTypes . Changed :
227- if ( watchedPlugins . Contains ( subPath ) )
228- {
229- FirePluginSourceChanged ( subPath ) ;
230- }
231- else
232- {
233- FirePluginAdded ( subPath ) ;
234- }
235- break ;
236-
237- case WatcherChangeTypes . Created :
115+ else
116+ {
238117 FirePluginAdded ( subPath ) ;
239- break ;
118+ }
119+ break ;
240120
241- case WatcherChangeTypes . Deleted :
242- if ( watchedPlugins . Contains ( subPath ) )
243- {
244- FirePluginRemoved ( subPath ) ;
245- }
246- break ;
247- }
248- } ) ;
121+ case NotifyMask . OnCreated :
122+ FirePluginAdded ( subPath ) ;
123+ break ;
124+
125+ case NotifyMask . OnDeleted :
126+ if ( watchedPlugins . Contains ( subPath ) )
127+ {
128+ FirePluginRemoved ( subPath ) ;
129+ }
130+ break ;
131+ }
249132 } ) ;
250133 }
251134
252- private void watcher_Error ( object sender , ErrorEventArgs e )
135+ public void OnNext ( FileSystemEvent value )
253136 {
254- Interface . Oxide . NextTick ( ( ) =>
137+ if ( ! value . Directory . StartsWith ( _directory , StringComparison . InvariantCulture ) || ! filter . IsMatch ( value . Name ) )
255138 {
256- Interface . Oxide . LogError ( "FSWatcher error: {0}" , e . GetException ( ) ) ;
257- RemoteLogger . Exception ( "FSWatcher error" , e . GetException ( ) ) ;
258- } ) ;
139+ return ;
140+ }
141+
142+ if ( ( value . Event & NotifyMask . DirectoryOnly ) == NotifyMask . DirectoryOnly )
143+ {
144+ return ;
145+ }
146+
147+ #if DEBUG
148+ Interface . Oxide . LogDebug ( $ "Processing { value . Event } : { Path . Combine ( value . Directory , value . Name ) } ") ;
149+ #endif
150+ watcher_Changed ( watcher , value ) ;
151+ }
152+
153+ public void OnError ( Exception error )
154+ {
155+ Interface . Oxide . LogError ( "FSWatcher error: {0}" , error ) ;
156+ RemoteLogger . Exception ( "FSWatcher error" , error ) ;
157+ }
158+
159+ public void OnCompleted ( )
160+ {
259161 }
260162 }
261163}
0 commit comments