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