In .:
[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                 static bool use_gamin;
81                 
82                 private FAMWatcher ()
83                 {
84                 }
85                 
86                 // Locked by caller
87                 public static bool GetInstance (out IFileWatcher watcher, bool gamin)
88                 {
89                         if (failed == true) {
90                                 watcher = null;
91                                 return false;
92                         }
93
94                         if (instance != null) {
95                                 watcher = instance;
96                                 return true;
97                         }
98
99                         use_gamin = gamin;
100                         watches = Hashtable.Synchronized (new Hashtable ());
101                         requests = Hashtable.Synchronized (new Hashtable ());
102                         if (FAMOpen (out conn) == -1) {
103                                 failed = true;
104                                 watcher = null;
105                                 return false;
106                         }
107
108                         instance = new FAMWatcher ();
109                         watcher = instance;
110                         return true;
111                 }
112                 
113                 public void StartDispatching (FileSystemWatcher fsw)
114                 {
115                         FAMData data;
116                         lock (this) {
117                                 if (thread == null) {
118                                         thread = new Thread (new ThreadStart (Monitor));
119                                         thread.IsBackground = true;
120                                         thread.Start ();
121                                 }
122
123                                 data = (FAMData) watches [fsw];
124                         }
125
126                         if (data == null) {
127                                 data = new FAMData ();
128                                 data.FSW = fsw;
129                                 data.Directory = fsw.FullPath;
130                                 data.FileMask = fsw.MangledFilter;
131                                 data.IncludeSubdirs = fsw.IncludeSubdirectories;
132                                 if (data.IncludeSubdirs)
133                                         data.SubDirs = new Hashtable ();
134
135                                 data.Enabled = true;
136                                 StartMonitoringDirectory (data);
137                                 lock (this) {
138                                         watches [fsw] = data;
139                                         requests [data.Request.ReqNum] = data;
140                                         stop = false;
141                                 }
142                         }
143                 }
144
145                 static void StartMonitoringDirectory (FAMData data)
146                 {
147                         FAMRequest fr;
148                         if (FAMMonitorDirectory (ref conn, data.Directory, out fr, IntPtr.Zero) == -1)
149                                 throw new Win32Exception ();
150
151                         data.Request = fr;
152                         if (!data.IncludeSubdirs)
153                                 return;
154
155                         foreach (string directory in Directory.GetDirectories (data.Directory)) {
156                                 FAMData fd = new FAMData ();
157                                 fd.FSW = data.FSW;
158                                 fd.Directory = directory;
159                                 fd.FileMask = data.FSW.MangledFilter;
160                                 fd.IncludeSubdirs = true;
161                                 fd.SubDirs = new Hashtable ();
162                                 fd.Enabled = true;
163
164                                 StartMonitoringDirectory (fd);
165                                 data.SubDirs [directory] = fd;
166                                 requests [fd.Request.ReqNum] = fd;
167                         }
168                 }
169
170                 public void StopDispatching (FileSystemWatcher fsw)
171                 {
172                         FAMData data;
173                         lock (this) {
174                                 data = (FAMData) watches [fsw];
175                                 if (data == null)
176                                         return;
177
178                                 StopMonitoringDirectory (data);
179                                 watches.Remove (fsw);
180                                 requests.Remove (data.Request.ReqNum);
181                                 if (watches.Count == 0)
182                                         stop = true;
183
184                                 if (!data.IncludeSubdirs)
185                                         return;
186
187                                 foreach (FAMData fd in data.SubDirs.Values) {
188                                         StopMonitoringDirectory (fd);
189                                         requests.Remove (fd.Request.ReqNum);
190                                 }
191                         }
192                 }
193
194                 static void StopMonitoringDirectory (FAMData data)
195                 {
196                         if (FAMCancelMonitor (ref conn, ref data.Request) == -1)
197                                 throw new Win32Exception ();
198                 }
199
200                 void Monitor ()
201                 {
202                         while (!stop) {
203                                 int haveEvents;
204                                 lock (this) {
205                                         haveEvents = FAMPending (ref conn);
206                                 }
207
208                                 if (haveEvents > 0) {
209                                         ProcessEvents ();
210                                 } else {
211                                         Thread.Sleep (500);
212                                 }
213                         }
214
215                         lock (this) {
216                                 thread = null;
217                                 stop = false;
218                         }
219                 }
220
221                 const NotifyFilters changed =   NotifyFilters.Attributes |
222                                                 NotifyFilters.LastAccess |
223                                                 NotifyFilters.Size      |
224                                                 NotifyFilters.LastWrite;
225
226                 void ProcessEvents ()
227                 {
228                         ArrayList newdirs = null;
229                         lock (this) {
230                                 do {
231                                         int code;
232                                         string filename;
233                                         int requestNumber;
234                                         FileSystemWatcher fsw;
235
236                                         if (InternalFAMNextEvent (ref conn, out filename,
237                                                                   out code, out requestNumber) != 1)
238                                                 return;
239
240                                         bool found = false;
241                                         switch ((FAMCodes) code) {
242                                         case FAMCodes.Changed:
243                                         case FAMCodes.Deleted:
244                                         case FAMCodes.Created:
245                                                 found = requests.ContainsKey (requestNumber);
246                                                 break;
247                                         case FAMCodes.Moved:
248                                         case FAMCodes.StartExecuting:
249                                         case FAMCodes.StopExecuting:
250                                         case FAMCodes.Acknowledge:
251                                         case FAMCodes.Exists:
252                                         case FAMCodes.EndExist:
253                                         default:
254                                                 found = false;
255                                                 break;
256                                         }
257
258                                         if (!found)
259                                                 continue;
260                                         
261                                         FAMData data = (FAMData) requests [requestNumber];
262                                         if (!data.Enabled)
263                                                 continue;
264
265                                         fsw = data.FSW;
266                                         NotifyFilters flt = fsw.NotifyFilter;
267                                         RenamedEventArgs renamed = null;
268                                         FileAction fa = 0;
269                                         if (code == (int) FAMCodes.Changed && (flt & changed) != 0)
270                                                 fa = FileAction.Modified;
271                                         else if (code == (int) FAMCodes.Deleted)
272                                                 fa = FileAction.Removed;
273                                         else if (code == (int) FAMCodes.Created)
274                                                 fa = FileAction.Added;
275
276                                         if (fa == 0)
277                                                 continue;
278
279                                         if (fsw.IncludeSubdirectories) {
280                                                 string full = fsw.FullPath;
281                                                 string datadir = data.Directory;
282                                                 if (datadir != full) {
283                                                         string reldir = datadir.Substring (full.Length + 1);
284                                                         datadir = Path.Combine (datadir, filename);
285                                                         filename = Path.Combine (reldir, filename);
286                                                 } else {
287                                                         datadir = Path.Combine (fsw.FullPath, filename);
288                                                 }
289
290                                                 if (fa == FileAction.Added && Directory.Exists (datadir)) {
291                                                         if (newdirs == null)
292                                                                 newdirs = new ArrayList (4);
293
294                                                         FAMData fd = new FAMData ();
295                                                         fd.FSW = fsw;
296                                                         fd.Directory = datadir;
297                                                         fd.FileMask = fsw.MangledFilter;
298                                                         fd.IncludeSubdirs = true;
299                                                         fd.SubDirs = new Hashtable ();
300                                                         fd.Enabled = true;
301                                                         newdirs.Add (fd);
302                                                         newdirs.Add (data);
303                                                         requests [fd.Request.ReqNum] = fd;
304                                                 }
305                                         }
306
307                                         if (filename != data.Directory && !fsw.Pattern.IsMatch (filename))
308                                                 continue;
309
310                                         lock (fsw) {
311                                                 fsw.DispatchEvents (fa, filename, ref renamed);
312                                                 if (fsw.Waiting) {
313                                                         fsw.Waiting = false;
314                                                         System.Threading.Monitor.PulseAll (fsw);
315                                                 }
316                                         }
317                                 } while (FAMPending (ref conn) > 0);
318                         }
319
320
321                         if (newdirs != null) {
322                                 int count = newdirs.Count;
323                                 for (int n = 0; n < count; n += 2) {
324                                         FAMData newdir = (FAMData) newdirs [n];
325                                         FAMData parent = (FAMData) newdirs [n + 1];
326                                         StartMonitoringDirectory (newdir);
327                                         lock (parent) {
328                                                 parent.SubDirs [newdir.Directory] = newdir;
329                                         }
330                                 }
331                                 newdirs.Clear ();
332                         }
333                 }
334
335                 ~FAMWatcher ()
336                 {
337                         FAMClose (ref conn);
338                 }
339
340                 static int FAMOpen (out FAMConnection fc)
341                 {
342                         if (use_gamin)
343                                 return gamin_Open (out fc);
344                         return fam_Open (out fc);
345                 }
346
347                 static int FAMClose (ref FAMConnection fc)
348                 {
349                         if (use_gamin)
350                                 return gamin_Close (ref fc);
351                         return fam_Close (ref fc);
352                 }
353
354                 static int FAMMonitorDirectory (ref FAMConnection fc, string filename, out FAMRequest fr, IntPtr user_data)
355                 {
356                         if (use_gamin)
357                                 return gamin_MonitorDirectory (ref fc, filename, out fr, user_data);
358                         return fam_MonitorDirectory (ref fc, filename, out fr, user_data);
359                 }
360
361                 static int FAMCancelMonitor (ref FAMConnection fc, ref FAMRequest fr)
362                 {
363                         if (use_gamin)
364                                 return gamin_CancelMonitor (ref fc, ref fr);
365                         return fam_CancelMonitor (ref fc, ref fr);
366                 }
367
368                 static int FAMPending (ref FAMConnection fc)
369                 {
370                         if (use_gamin)
371                                 return gamin_Pending (ref fc);
372                         return fam_Pending (ref fc);
373                 }
374
375
376                 
377                 [DllImport ("libfam.so.0", EntryPoint="FAMOpen")]
378                 extern static int fam_Open (out FAMConnection fc);
379
380                 [DllImport ("libfam.so.0", EntryPoint="FAMClose")]
381                 extern static int fam_Close (ref FAMConnection fc);
382
383                 [DllImport ("libfam.so.0", EntryPoint="FAMMonitorDirectory")]
384                 extern static int fam_MonitorDirectory (ref FAMConnection fc, string filename,
385                                                         out FAMRequest fr, IntPtr user_data);
386
387                 [DllImport ("libfam.so.0", EntryPoint="FAMCancelMonitor")]
388                 extern static int fam_CancelMonitor (ref FAMConnection fc, ref FAMRequest fr);
389
390                 [DllImport ("libfam.so.0", EntryPoint="FAMPending")]
391                 extern static int fam_Pending (ref FAMConnection fc);
392
393                 [DllImport ("libgamin-1.so.0", EntryPoint="FAMOpen")]
394                 extern static int gamin_Open (out FAMConnection fc);
395
396                 [DllImport ("libgamin-1.so.0", EntryPoint="FAMClose")]
397                 extern static int gamin_Close (ref FAMConnection fc);
398
399                 [DllImport ("libgamin-1.so.0", EntryPoint="FAMMonitorDirectory")]
400                 extern static int gamin_MonitorDirectory (ref FAMConnection fc, string filename,
401                                                         out FAMRequest fr, IntPtr user_data);
402
403                 [DllImport ("libgamin-1.so.0", EntryPoint="FAMCancelMonitor")]
404                 extern static int gamin_CancelMonitor (ref FAMConnection fc, ref FAMRequest fr);
405
406                 [DllImport ("libgamin-1.so.0", EntryPoint="FAMPending")]
407                 extern static int gamin_Pending (ref FAMConnection fc);
408
409                 [MethodImplAttribute(MethodImplOptions.InternalCall)]
410                 extern static int InternalFAMNextEvent (ref FAMConnection fc, out string filename,
411                                                         out int code, out int reqnum);
412
413         }
414 }
415