merge -r 53370:58178
[mono.git] / mcs / class / System / System.IO / FAMWatcher.cs
1 // 
2 // System.IO.FAM.cs: interface with libfam
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.ComponentModel;
34 using System.Runtime.CompilerServices;
35 using System.Runtime.InteropServices;
36 using System.Text;
37 using System.Threading;
38
39 namespace System.IO {
40         struct FAMConnection {
41                 public int FD;
42                 public IntPtr opaque;
43         }
44
45         struct FAMRequest {
46                 public int ReqNum;
47         }
48
49         enum FAMCodes {
50                 Changed = 1,
51                 Deleted = 2,
52                 StartExecuting = 3, 
53                 StopExecuting = 4,
54                 Created = 5,
55                 Moved = 6, 
56                 Acknowledge = 7,
57                 Exists = 8,
58                 EndExist = 9
59         };
60
61         class FAMData {
62                 public FileSystemWatcher FSW;
63                 public string Directory;
64                 public string FileMask;
65                 public bool IncludeSubdirs;
66                 public bool Enabled;
67                 public FAMRequest Request;
68                 public Hashtable SubDirs;
69         }
70
71         class FAMWatcher : IFileWatcher
72         {
73                 static bool failed;
74                 static FAMWatcher instance;
75                 static Hashtable watches;
76                 static Hashtable requests;
77                 static FAMConnection conn;
78                 static Thread thread;
79                 static bool stop;
80                 
81                 private FAMWatcher ()
82                 {
83                 }
84                 
85                 // Locked by caller
86                 public static bool GetInstance (out IFileWatcher watcher)
87                 {
88                         if (failed == true) {
89                                 watcher = null;
90                                 return false;
91                         }
92
93                         if (instance != null) {
94                                 watcher = instance;
95                                 return true;
96                         }
97
98                         watches = Hashtable.Synchronized (new Hashtable ());
99                         requests = Hashtable.Synchronized (new Hashtable ());
100                         if (FAMOpen (out conn) == -1) {
101                                 failed = true;
102                                 watcher = null;
103                                 return false;
104                         }
105
106                         instance = new FAMWatcher ();
107                         watcher = instance;
108                         return true;
109                 }
110                 
111                 public void StartDispatching (FileSystemWatcher fsw)
112                 {
113                         FAMData data;
114                         lock (this) {
115                                 if (thread == null) {
116                                         thread = new Thread (new ThreadStart (Monitor));
117                                         thread.IsBackground = true;
118                                         thread.Start ();
119                                 }
120
121                                 data = (FAMData) watches [fsw];
122                         }
123
124                         if (data == null) {
125                                 data = new FAMData ();
126                                 data.FSW = fsw;
127                                 data.Directory = fsw.FullPath;
128                                 data.FileMask = fsw.MangledFilter;
129                                 data.IncludeSubdirs = fsw.IncludeSubdirectories;
130                                 if (data.IncludeSubdirs)
131                                         data.SubDirs = new Hashtable ();
132
133                                 data.Enabled = true;
134                                 StartMonitoringDirectory (data);
135                                 lock (this) {
136                                         watches [fsw] = data;
137                                         requests [data.Request.ReqNum] = data;
138                                         stop = false;
139                                 }
140                         }
141                 }
142
143                 static void StartMonitoringDirectory (FAMData data)
144                 {
145                         FAMRequest fr;
146                         if (FAMMonitorDirectory (ref conn, data.Directory, out fr, IntPtr.Zero) == -1)
147                                 throw new Win32Exception ();
148
149                         data.Request = fr;
150                         if (!data.IncludeSubdirs)
151                                 return;
152
153                         foreach (string directory in Directory.GetDirectories (data.Directory)) {
154                                 FAMData fd = new FAMData ();
155                                 fd.FSW = data.FSW;
156                                 fd.Directory = directory;
157                                 fd.FileMask = data.FSW.MangledFilter;
158                                 fd.IncludeSubdirs = true;
159                                 fd.SubDirs = new Hashtable ();
160                                 fd.Enabled = true;
161
162                                 StartMonitoringDirectory (fd);
163                                 data.SubDirs [directory] = fd;
164                                 requests [fd.Request.ReqNum] = fd;
165                         }
166                 }
167
168                 public void StopDispatching (FileSystemWatcher fsw)
169                 {
170                         FAMData data;
171                         lock (this) {
172                                 data = (FAMData) watches [fsw];
173                                 if (data == null)
174                                         return;
175
176                                 StopMonitoringDirectory (data);
177                                 watches.Remove (fsw);
178                                 requests.Remove (data.Request.ReqNum);
179                                 if (watches.Count == 0)
180                                         stop = true;
181
182                                 if (!data.IncludeSubdirs)
183                                         return;
184
185                                 foreach (FAMData fd in data.SubDirs.Values) {
186                                         StopMonitoringDirectory (fd);
187                                         requests.Remove (fd.Request.ReqNum);
188                                 }
189                         }
190                 }
191
192                 static void StopMonitoringDirectory (FAMData data)
193                 {
194                         if (FAMCancelMonitor (ref conn, ref data.Request) == -1)
195                                 throw new Win32Exception ();
196                 }
197
198                 void Monitor ()
199                 {
200                         while (!stop) {
201                                 int haveEvents;
202                                 lock (this) {
203                                         haveEvents = FAMPending (ref conn);
204                                 }
205
206                                 if (haveEvents > 0) {
207                                         ProcessEvents ();
208                                 } else {
209                                         Thread.Sleep (500);
210                                 }
211                         }
212
213                         lock (this) {
214                                 thread = null;
215                                 stop = false;
216                         }
217                 }
218
219                 const NotifyFilters changed =   NotifyFilters.Attributes |
220                                                 NotifyFilters.LastAccess |
221                                                 NotifyFilters.Size      |
222                                                 NotifyFilters.LastWrite;
223
224                 void ProcessEvents ()
225                 {
226                         ArrayList newdirs = null;
227                         lock (this) {
228                                 do {
229                                         int code;
230                                         string filename;
231                                         int requestNumber;
232                                         FileSystemWatcher fsw;
233
234                                         if (InternalFAMNextEvent (ref conn, out filename,
235                                                                   out code, out requestNumber) != 1)
236                                                 return;
237
238                                         bool found = false;
239                                         switch ((FAMCodes) code) {
240                                         case FAMCodes.Changed:
241                                         case FAMCodes.Deleted:
242                                         case FAMCodes.Created:
243                                                 found = requests.ContainsKey (requestNumber);
244                                                 break;
245                                         case FAMCodes.Moved:
246                                         case FAMCodes.StartExecuting:
247                                         case FAMCodes.StopExecuting:
248                                         case FAMCodes.Acknowledge:
249                                         case FAMCodes.Exists:
250                                         case FAMCodes.EndExist:
251                                         default:
252                                                 found = false;
253                                                 break;
254                                         }
255
256                                         if (!found)
257                                                 continue;
258                                         
259                                         FAMData data = (FAMData) requests [requestNumber];
260                                         if (!data.Enabled)
261                                                 continue;
262
263                                         fsw = data.FSW;
264                                         NotifyFilters flt = fsw.NotifyFilter;
265                                         RenamedEventArgs renamed = null;
266                                         FileAction fa = 0;
267                                         if (code == (int) FAMCodes.Changed && (flt & changed) != 0)
268                                                 fa = FileAction.Modified;
269                                         else if (code == (int) FAMCodes.Deleted)
270                                                 fa = FileAction.Removed;
271                                         else if (code == (int) FAMCodes.Created)
272                                                 fa = FileAction.Added;
273
274                                         if (fa == 0)
275                                                 continue;
276
277                                         if (fsw.IncludeSubdirectories) {
278                                                 string full = fsw.FullPath;
279                                                 string datadir = data.Directory;
280                                                 if (datadir != full) {
281                                                         string reldir = datadir.Substring (full.Length + 1);
282                                                         datadir = Path.Combine (datadir, filename);
283                                                         filename = Path.Combine (reldir, filename);
284                                                 } else {
285                                                         datadir = Path.Combine (fsw.FullPath, filename);
286                                                 }
287
288                                                 if (fa == FileAction.Added && Directory.Exists (datadir)) {
289                                                         if (newdirs == null)
290                                                                 newdirs = new ArrayList (4);
291
292                                                         FAMData fd = new FAMData ();
293                                                         fd.FSW = fsw;
294                                                         fd.Directory = datadir;
295                                                         fd.FileMask = fsw.MangledFilter;
296                                                         fd.IncludeSubdirs = true;
297                                                         fd.SubDirs = new Hashtable ();
298                                                         fd.Enabled = true;
299                                                         newdirs.Add (fd);
300                                                         newdirs.Add (data);
301                                                         requests [fd.Request.ReqNum] = fd;
302                                                 }
303                                         }
304
305                                         if (filename != data.Directory && !fsw.Pattern.IsMatch (filename))
306                                                 continue;
307
308                                         lock (fsw) {
309                                                 fsw.DispatchEvents (fa, filename, ref renamed);
310                                                 if (fsw.Waiting) {
311                                                         fsw.Waiting = false;
312                                                         System.Threading.Monitor.PulseAll (fsw);
313                                                 }
314                                         }
315                                 } while (FAMPending (ref conn) > 0);
316                         }
317
318
319                         if (newdirs != null) {
320                                 int count = newdirs.Count;
321                                 for (int n = 0; n < count; n++) {
322                                         FAMData newdir = (FAMData) newdirs [n];
323                                         FAMData parent = (FAMData) newdirs [n + 1];
324                                         StartMonitoringDirectory (newdir);
325                                         lock (parent) {
326                                                 parent.SubDirs [newdir.Directory] = newdir;
327                                         }
328                                 }
329                                 newdirs.Clear ();
330                         }
331                 }
332
333                 ~FAMWatcher ()
334                 {
335                         FAMClose (ref conn);
336                 }
337
338                 [DllImport ("libfam.so.0")]
339                 extern static int FAMOpen (out FAMConnection fc);
340
341                 [DllImport ("libfam.so.0")]
342                 extern static int FAMClose (ref FAMConnection fc);
343
344                 [DllImport ("libfam.so.0")]
345                 extern static int FAMMonitorDirectory (ref FAMConnection fc, string filename,
346                                                         out FAMRequest fr, IntPtr user_data);
347
348                 [DllImport ("libfam.so.0")]
349                 extern static int FAMCancelMonitor (ref FAMConnection fc, ref FAMRequest fr);
350
351                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
352                 extern static int InternalFAMNextEvent (ref FAMConnection fc, out string filename,
353                                                         out int code, out int reqnum);
354
355                 [DllImport ("libfam.so.0")]
356                 extern static int FAMPending (ref FAMConnection fc);
357         }
358 }
359