[System.IO] Integrate FileSystemWatchers from CoreFX
[mono-project.git] / mcs / class / System / System.IO / DefaultWatcher.cs
blob28186f8451b1c97525270aa79a9970d8577613e6
1 //
2 // System.IO.DefaultWatcher.cs: default IFileWatcher
3 //
4 // Authors:
5 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
6 //
7 // (c) 2004 Novell, Inc. (http://www.novell.com)
8 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 //
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 //
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.IO;
35 using System.Threading;
37 namespace System.IO {
38 class DefaultWatcherData {
39 public FileSystemWatcher FSW;
40 public string Directory;
41 public string FileMask; // If NoWildcards, contains the full path to the file.
42 public bool IncludeSubdirs;
43 public bool Enabled;
44 public bool NoWildcards;
45 public DateTime DisabledTime;
47 public object FilesLock = new object ();
48 public Hashtable Files;
51 class FileData {
52 public string Directory;
53 public FileAttributes Attributes;
54 public bool NotExists;
55 public DateTime CreationTime;
56 public DateTime LastWriteTime;
59 class DefaultWatcher : IFileWatcher
61 static DefaultWatcher instance;
62 static Thread thread;
63 static Hashtable watches;
65 private DefaultWatcher ()
69 // Locked by caller
70 public static bool GetInstance (out IFileWatcher watcher)
72 if (instance != null) {
73 watcher = instance;
74 return true;
77 instance = new DefaultWatcher ();
78 watcher = instance;
79 return true;
82 public void StartDispatching (object handle)
84 var fsw = handle as FileSystemWatcher;
85 DefaultWatcherData data;
86 lock (this) {
87 if (watches == null)
88 watches = new Hashtable ();
90 if (thread == null) {
91 thread = new Thread (new ThreadStart (Monitor));
92 thread.IsBackground = true;
93 thread.Start ();
97 lock (watches) {
98 data = (DefaultWatcherData) watches [fsw];
99 if (data == null) {
100 data = new DefaultWatcherData ();
101 data.Files = new Hashtable ();
102 watches [fsw] = data;
105 data.FSW = fsw;
106 data.Directory = fsw.FullPath;
107 data.NoWildcards = !fsw.Pattern.HasWildcard;
108 if (data.NoWildcards)
109 data.FileMask = Path.Combine (data.Directory, fsw.MangledFilter);
110 else
111 data.FileMask = fsw.MangledFilter;
113 data.IncludeSubdirs = fsw.IncludeSubdirectories;
114 data.Enabled = true;
115 data.DisabledTime = DateTime.MaxValue;
116 UpdateDataAndDispatch (data, false);
120 public void StopDispatching (object handle)
122 var fsw = handle as FileSystemWatcher;
123 DefaultWatcherData data;
124 lock (this) {
125 if (watches == null) return;
128 lock (watches) {
129 data = (DefaultWatcherData) watches [fsw];
130 if (data != null) {
131 data.Enabled = false;
132 data.DisabledTime = DateTime.UtcNow;
137 public void Dispose (object handle)
139 // does nothing
143 void Monitor ()
145 int zeroes = 0;
147 while (true) {
148 Thread.Sleep (750);
150 Hashtable my_watches;
151 lock (watches) {
152 if (watches.Count == 0) {
153 if (++zeroes == 20)
154 break;
155 continue;
158 my_watches = (Hashtable) watches.Clone ();
161 if (my_watches.Count != 0) {
162 zeroes = 0;
163 foreach (DefaultWatcherData data in my_watches.Values) {
164 bool remove = UpdateDataAndDispatch (data, true);
165 if (remove)
166 lock (watches)
167 watches.Remove (data.FSW);
172 lock (this) {
173 thread = null;
177 bool UpdateDataAndDispatch (DefaultWatcherData data, bool dispatch)
179 if (!data.Enabled) {
180 return (data.DisabledTime != DateTime.MaxValue &&
181 (DateTime.UtcNow - data.DisabledTime).TotalSeconds > 5);
184 DoFiles (data, data.Directory, dispatch);
185 return false;
188 static void DispatchEvents (FileSystemWatcher fsw, FileAction action, string filename)
190 RenamedEventArgs renamed = null;
192 lock (fsw) {
193 fsw.DispatchEvents (action, filename, ref renamed);
194 if (fsw.Waiting) {
195 fsw.Waiting = false;
196 System.Threading.Monitor.PulseAll (fsw);
201 static string [] NoStringsArray = new string [0];
202 void DoFiles (DefaultWatcherData data, string directory, bool dispatch)
204 bool direxists = Directory.Exists (directory);
205 if (direxists && data.IncludeSubdirs) {
206 foreach (string d in Directory.GetDirectories (directory))
207 DoFiles (data, d, dispatch);
210 string [] files = null;
211 if (!direxists) {
212 files = NoStringsArray;
213 } else if (!data.NoWildcards) {
214 files = Directory.GetFileSystemEntries (directory, data.FileMask);
215 } else {
216 // The pattern does not have wildcards
217 if (File.Exists (data.FileMask) || Directory.Exists (data.FileMask))
218 files = new string [] { data.FileMask };
219 else
220 files = NoStringsArray;
223 lock (data.FilesLock) {
224 IterateAndModifyFilesData (data, directory, dispatch, files);
228 void IterateAndModifyFilesData (DefaultWatcherData data, string directory, bool dispatch, string[] files)
230 /* Set all as untested */
231 foreach (string filename in data.Files.Keys) {
232 FileData fd = (FileData) data.Files [filename];
233 if (fd.Directory == directory)
234 fd.NotExists = true;
237 /* New files */
238 foreach (string filename in files) {
239 FileData fd = (FileData) data.Files [filename];
240 if (fd == null) {
241 try {
242 data.Files.Add (filename, CreateFileData (directory, filename));
243 } catch {
244 // The file might have been removed in the meanwhile
245 data.Files.Remove (filename);
246 continue;
249 if (dispatch)
250 DispatchEvents (data.FSW, FileAction.Added, filename);
251 } else if (fd.Directory == directory) {
252 fd.NotExists = false;
256 if (!dispatch) // We only initialize the file list
257 return;
259 /* Removed files */
260 List<string> removed = null;
261 foreach (string filename in data.Files.Keys) {
262 FileData fd = (FileData) data.Files [filename];
263 if (fd.NotExists) {
264 if (removed == null)
265 removed = new List<string> ();
267 removed.Add (filename);
268 DispatchEvents (data.FSW, FileAction.Removed, filename);
272 if (removed != null) {
273 foreach (string filename in removed)
274 data.Files.Remove (filename);
276 removed = null;
279 /* Changed files */
280 foreach (string filename in data.Files.Keys) {
281 FileData fd = (FileData) data.Files [filename];
282 DateTime creation, write;
283 try {
284 creation = File.GetCreationTime (filename);
285 write = File.GetLastWriteTime (filename);
286 } catch {
287 /* Deleted */
288 if (removed == null)
289 removed = new List<string> ();
291 removed.Add (filename);
292 DispatchEvents (data.FSW, FileAction.Removed, filename);
293 continue;
296 if (creation != fd.CreationTime || write != fd.LastWriteTime) {
297 fd.CreationTime = creation;
298 fd.LastWriteTime = write;
299 DispatchEvents (data.FSW, FileAction.Modified, filename);
303 if (removed != null) {
304 foreach (string filename in removed)
305 data.Files.Remove (filename);
310 static FileData CreateFileData (string directory, string filename)
312 FileData fd = new FileData ();
313 string fullpath = Path.Combine (directory, filename);
314 fd.Directory = directory;
315 fd.Attributes = File.GetAttributes (fullpath);
316 fd.CreationTime = File.GetCreationTime (fullpath);
317 fd.LastWriteTime = File.GetLastWriteTime (fullpath);
318 return fd;