[System] Don't use DateTime.Now for measuring elapsed time
[mono.git] / mcs / class / System / System.IO / DefaultWatcher.cs
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 //
9
10 //
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.
29 //
30
31 using System;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.IO;
35 using System.Threading;
36
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;
46
47                 public object FilesLock = new object ();
48                 public Hashtable Files;
49         }
50
51         class FileData {
52                 public string Directory;
53                 public FileAttributes Attributes;
54                 public bool NotExists;
55                 public DateTime CreationTime;
56                 public DateTime LastWriteTime;
57         }
58
59         class DefaultWatcher : IFileWatcher
60         {
61                 static DefaultWatcher instance;
62                 static Thread thread;
63                 static Hashtable watches;
64
65                 private DefaultWatcher ()
66                 {
67                 }
68                 
69                 // Locked by caller
70                 public static bool GetInstance (out IFileWatcher watcher)
71                 {
72                         if (instance != null) {
73                                 watcher = instance;
74                                 return true;
75                         }
76
77                         instance = new DefaultWatcher ();
78                         watcher = instance;
79                         return true;
80                 }
81                 
82                 public void StartDispatching (FileSystemWatcher fsw)
83                 {
84                         DefaultWatcherData data;
85                         lock (this) {
86                                 if (watches == null)
87                                         watches = new Hashtable ();
88
89                                 if (thread == null) {
90                                         thread = new Thread (new ThreadStart (Monitor));
91                                         thread.IsBackground = true;
92                                         thread.Start ();
93                                 }
94                         }
95
96                         lock (watches) {
97                                 data = (DefaultWatcherData) watches [fsw];
98                                 if (data == null) {
99                                         data = new DefaultWatcherData ();
100                                         data.Files = new Hashtable ();
101                                         watches [fsw] = data;
102                                 }
103
104                                 data.FSW = fsw;
105                                 data.Directory = fsw.FullPath;
106                                 data.NoWildcards = !fsw.Pattern.HasWildcard;
107                                 if (data.NoWildcards)
108                                         data.FileMask = Path.Combine (data.Directory, fsw.MangledFilter);
109                                 else
110                                         data.FileMask = fsw.MangledFilter;
111
112                                 data.IncludeSubdirs = fsw.IncludeSubdirectories;
113                                 data.Enabled = true;
114                                 data.DisabledTime = DateTime.MaxValue;
115                                 UpdateDataAndDispatch (data, false);
116                         }
117                 }
118
119                 public void StopDispatching (FileSystemWatcher fsw)
120                 {
121                         DefaultWatcherData data;
122                         lock (this) {
123                                 if (watches == null) return;
124                         }
125                         
126                         lock (watches) {
127                                 data = (DefaultWatcherData) watches [fsw];
128                                 if (data != null) {
129                                         data.Enabled = false;
130                                         data.DisabledTime = DateTime.UtcNow;
131                                 }
132                         }
133                 }
134
135
136                 void Monitor ()
137                 {
138                         int zeroes = 0;
139
140                         while (true) {
141                                 Thread.Sleep (750);
142                                 
143                                 Hashtable my_watches;
144                                 lock (watches) {
145                                         if (watches.Count == 0) {
146                                                 if (++zeroes == 20)
147                                                         break;
148                                                 continue;
149                                         }
150                                         
151                                         my_watches = (Hashtable) watches.Clone ();
152                                 }
153                                 
154                                 if (my_watches.Count != 0) {
155                                         zeroes = 0;
156                                         foreach (DefaultWatcherData data in my_watches.Values) {
157                                                 bool remove = UpdateDataAndDispatch (data, true);
158                                                 if (remove)
159                                                         lock (watches)
160                                                                 watches.Remove (data.FSW);
161                                         }
162                                 }
163                         }
164
165                         lock (this) {
166                                 thread = null;
167                         }
168                 }
169                 
170                 bool UpdateDataAndDispatch (DefaultWatcherData data, bool dispatch)
171                 {
172                         if (!data.Enabled) {
173                                 return (data.DisabledTime != DateTime.MaxValue &&
174                                         (DateTime.UtcNow - data.DisabledTime).TotalSeconds > 5);
175                         }
176
177                         DoFiles (data, data.Directory, dispatch);
178                         return false;
179                 }
180
181                 static void DispatchEvents (FileSystemWatcher fsw, FileAction action, string filename)
182                 {
183                         RenamedEventArgs renamed = null;
184
185                         lock (fsw) {
186                                 fsw.DispatchEvents (action, filename, ref renamed);
187                                 if (fsw.Waiting) {
188                                         fsw.Waiting = false;
189                                         System.Threading.Monitor.PulseAll (fsw);
190                                 }
191                         }
192                 }
193
194                 static string [] NoStringsArray = new string [0];
195                 void DoFiles (DefaultWatcherData data, string directory, bool dispatch)
196                 {
197                         bool direxists = Directory.Exists (directory);
198                         if (direxists && data.IncludeSubdirs) {
199                                 foreach (string d in Directory.GetDirectories (directory))
200                                         DoFiles (data, d, dispatch);
201                         }
202
203                         string [] files = null;
204                         if (!direxists) {
205                                 files = NoStringsArray;
206                         } else if (!data.NoWildcards) {
207                                 files = Directory.GetFileSystemEntries (directory, data.FileMask);
208                         } else {
209                                 // The pattern does not have wildcards
210                                 if (File.Exists (data.FileMask) || Directory.Exists (data.FileMask))
211                                         files = new string [] { data.FileMask };
212                                 else
213                                         files = NoStringsArray;
214                         }
215
216                         lock (data.FilesLock) {
217                                 IterateAndModifyFilesData (data, directory, dispatch, files);
218                         }
219                 }
220
221                 void IterateAndModifyFilesData (DefaultWatcherData data, string directory, bool dispatch, string[] files)
222                 {
223                         /* Set all as untested */
224                         foreach (string filename in data.Files.Keys) {
225                                 FileData fd = (FileData) data.Files [filename];
226                                 if (fd.Directory == directory)
227                                         fd.NotExists = true;
228                         }
229
230                         /* New files */
231                         foreach (string filename in files) {
232                                 FileData fd = (FileData) data.Files [filename];
233                                 if (fd == null) {
234                                         try {
235                                                 data.Files.Add (filename, CreateFileData (directory, filename));
236                                         } catch {
237                                                 // The file might have been removed in the meanwhile
238                                                 data.Files.Remove (filename);
239                                                 continue;
240                                         }
241                                         
242                                         if (dispatch)
243                                                 DispatchEvents (data.FSW, FileAction.Added, filename);
244                                 } else if (fd.Directory == directory) {
245                                         fd.NotExists = false;
246                                 }
247                         }
248
249                         if (!dispatch) // We only initialize the file list
250                                 return;
251
252                         /* Removed files */
253                         List<string> removed = null;
254                         foreach (string filename in data.Files.Keys) {
255                                 FileData fd = (FileData) data.Files [filename];
256                                 if (fd.NotExists) {
257                                         if (removed == null)
258                                                 removed = new List<string> ();
259
260                                         removed.Add (filename);
261                                         DispatchEvents (data.FSW, FileAction.Removed, filename);
262                                 }
263                         }
264
265                         if (removed != null) {
266                                 foreach (string filename in removed)
267                                         data.Files.Remove (filename);
268
269                                 removed = null;
270                         }
271
272                         /* Changed files */
273                         foreach (string filename in data.Files.Keys) {
274                                 FileData fd = (FileData) data.Files [filename];
275                                 DateTime creation, write;
276                                 try {
277                                         creation = File.GetCreationTime (filename);
278                                         write = File.GetLastWriteTime (filename);
279                                 } catch {
280                                         /* Deleted */
281                                         if (removed == null)
282                                                 removed = new List<string> ();
283
284                                         removed.Add (filename);
285                                         DispatchEvents (data.FSW, FileAction.Removed, filename);
286                                         continue;
287                                 }
288                                 
289                                 if (creation != fd.CreationTime || write != fd.LastWriteTime) {
290                                         fd.CreationTime = creation;
291                                         fd.LastWriteTime = write;
292                                         DispatchEvents (data.FSW, FileAction.Modified, filename);
293                                 }
294                         }
295
296                         if (removed != null) {
297                                 foreach (string filename in removed)
298                                         data.Files.Remove (filename);
299                         }
300
301                 }
302
303                 static FileData CreateFileData (string directory, string filename)
304                 {
305                         FileData fd = new FileData ();
306                         string fullpath = Path.Combine (directory, filename);
307                         fd.Directory = directory;
308                         fd.Attributes = File.GetAttributes (fullpath);
309                         fd.CreationTime = File.GetCreationTime (fullpath);
310                         fd.LastWriteTime = File.GetLastWriteTime (fullpath);
311                         return fd;
312                 }
313         }
314 }
315