* Filters/FilterPackage.cs, Filters/FilterRPM.cs,
[beagle.git] / Util / Inotify.cs
blob6e161c6e8d5292d1e198cd1263764e8d6e65ad21
1 //
2 // Inotify.cs
3 //
4 // Copyright (C) 2004 Novell, Inc.
5 //
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a
9 // copy of this software and associated documentation files (the "Software"),
10 // to deal in the Software without restriction, including without limitation
11 // the rights to use, copy, modify, merge, publish, distribute, sublicense,
12 // and/or sell copies of the Software, and to permit persons to whom the
13 // Software is furnished to do so, subject to the following conditions:
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24 // DEALINGS IN THE SOFTWARE.
27 // WARNING: This is not portable to Win32
29 using System;
30 using System.Collections;
31 using System.IO;
32 using System.Runtime.InteropServices;
33 using System.Text;
34 using System.Text.RegularExpressions;
35 using System.Threading;
37 namespace Beagle.Util {
39 public class Inotify {
41 public delegate void InotifyCallback (Watch watch, string path, string subitem, string srcpath, EventType type);
43 public interface Watch {
44 void Unsubscribe ();
45 void ChangeSubscription (EventType new_mask);
47 /////////////////////////////////////////////////////////////////////////////////////
49 [Flags]
50 public enum EventType : uint {
51 Access = 0x00000001, // File was accessed
52 Modify = 0x00000002, // File was modified
53 Attrib = 0x00000004, // File changed attributes
54 CloseWrite = 0x00000008, // Writable file was closed
55 CloseNoWrite = 0x00000010, // Non-writable file was close
56 Open = 0x00000020, // File was opened
57 MovedFrom = 0x00000040, // File was moved from X
58 MovedTo = 0x00000080, // File was moved to Y
59 Create = 0x00000100, // Subfile was created
60 Delete = 0x00000200, // Subfile was deleted
61 DeleteSelf = 0x00000400, // Self was deleted
63 Unmount = 0x00002000, // Backing fs was unmounted
64 QueueOverflow = 0x00004000, // Event queue overflowed
65 Ignored = 0x00008000, // File is no longer being watched
67 IsDirectory = 0x40000000, // Event is against a directory
68 OneShot = 0x80000000, // Watch is one-shot
70 // For forward compatibility, define these explicitly
71 All = (EventType.Access | EventType.Modify | EventType.Attrib |
72 EventType.CloseWrite | EventType.CloseNoWrite | EventType.Open |
73 EventType.MovedFrom | EventType.MovedTo | EventType.Create |
74 EventType.Delete | EventType.DeleteSelf)
77 // Events that we want internally, even if the handlers do not
78 static private EventType base_mask = EventType.MovedFrom | EventType.MovedTo;
80 /////////////////////////////////////////////////////////////////////////////////////
82 static private Logger log;
84 static public Logger Log {
85 get { return log; }
88 /////////////////////////////////////////////////////////////////////////////////////
90 [StructLayout (LayoutKind.Sequential)]
91 private struct inotify_event {
92 public int wd;
93 public EventType mask;
94 public uint cookie;
95 public uint len;
98 [DllImport ("libbeagleglue")]
99 static extern int inotify_glue_init ();
101 [DllImport ("libbeagleglue")]
102 static extern int inotify_glue_watch (int fd, string filename, EventType mask);
104 [DllImport ("libbeagleglue")]
105 static extern int inotify_glue_ignore (int fd, int wd);
107 [DllImport ("libbeagleglue")]
108 static extern unsafe void inotify_snarf_events (int fd,
109 out int nr,
110 out IntPtr buffer);
112 [DllImport ("libbeagleglue")]
113 static extern void inotify_snarf_cancel ();
115 /////////////////////////////////////////////////////////////////////////////////////
117 static public bool Verbose = false;
118 static private int inotify_fd = -1;
120 static Inotify ()
122 log = Logger.Get ("Inotify");
124 if (Environment.GetEnvironmentVariable ("BEAGLE_DISABLE_INOTIFY") != null) {
125 Logger.Log.Debug ("BEAGLE_DISABLE_INOTIFY is set");
126 return;
129 if (Environment.GetEnvironmentVariable ("BEAGLE_INOTIFY_VERBOSE") != null)
130 Inotify.Verbose = true;
132 try {
133 inotify_fd = inotify_glue_init ();
134 } catch (EntryPointNotFoundException) {
135 Logger.Log.Info ("Inotify not available on system.");
136 return;
139 if (inotify_fd == -1)
140 Logger.Log.Warn ("Could not initialize inotify");
143 static public bool Enabled {
144 get { return inotify_fd >= 0; }
147 /////////////////////////////////////////////////////////////////////////////////////
149 #if ! ENABLE_INOTIFY
151 // Stubs for systems where inotify is unavailable
153 static public Watch Subscribe (string path, InotifyCallback callback, EventType mask)
155 return null;
158 static public void Start ()
160 return;
163 static public void Stop ()
165 return;
168 #else // ENABLE_INOTIFY
170 /////////////////////////////////////////////////////////////////////////////////////
171 static private ArrayList event_queue = new ArrayList ();
173 private class QueuedEvent {
174 public int Wd;
175 public EventType Type;
176 public string Filename;
177 public uint Cookie;
179 public bool Analyzed;
180 public bool Dispatched;
181 public DateTime HoldUntil;
182 public QueuedEvent PairedMove;
184 // Measured in milliseconds; 57ms is totally random
185 public const double DefaultHoldTime = 57;
187 public QueuedEvent ()
189 // Set a default HoldUntil time
190 HoldUntil = DateTime.Now.AddMilliseconds (DefaultHoldTime);
193 public void AddMilliseconds (double x)
195 HoldUntil = HoldUntil.AddMilliseconds (x);
198 public void PairWith (QueuedEvent other)
200 this.PairedMove = other;
201 other.PairedMove = this;
203 if (this.HoldUntil < other.HoldUntil)
204 this.HoldUntil = other.HoldUntil;
205 other.HoldUntil = this.HoldUntil;
209 /////////////////////////////////////////////////////////////////////////////////////
211 private class WatchInternal : Watch {
212 private InotifyCallback callback;
213 private EventType mask;
214 private WatchInfo watchinfo;
215 private bool is_subscribed;
217 public InotifyCallback Callback {
218 get { return callback; }
221 public EventType Mask {
222 get { return mask; }
223 set { mask = value; }
226 public WatchInternal (InotifyCallback callback, EventType mask, WatchInfo watchinfo)
228 this.callback = callback;
229 this.mask = mask;
230 this.watchinfo = watchinfo;
231 this.is_subscribed = true;
234 public void Unsubscribe ()
236 if (!this.is_subscribed)
237 return;
239 Inotify.Unsubscribe (watchinfo, this);
240 this.is_subscribed = false;
243 public void ChangeSubscription (EventType mask)
245 if (! this.is_subscribed)
246 return;
248 this.mask = mask;
249 CreateOrModifyWatch (this.watchinfo);
254 private class WatchInfo {
255 public int Wd = -1;
256 public string Path;
257 public bool IsDirectory;
258 public EventType Mask;
260 public EventType FilterMask;
261 public EventType FilterSeen;
263 public ArrayList Children;
264 public WatchInfo Parent;
266 public ArrayList Subscribers;
269 static Hashtable watched_by_wd = new Hashtable ();
270 static Hashtable watched_by_path = new Hashtable ();
271 static WatchInfo last_watched = null;
273 private class PendingMove {
274 public WatchInfo Watch;
275 public string SrcName;
276 public DateTime Time;
277 public uint Cookie;
279 public PendingMove (WatchInfo watched, string srcname, DateTime time, uint cookie) {
280 Watch = watched;
281 SrcName = srcname;
282 Time = time;
283 Cookie = cookie;
287 static public int WatchCount {
288 get { return watched_by_wd.Count; }
291 static public bool IsWatching (string path)
293 path = Path.GetFullPath (path);
294 return watched_by_path.Contains (path);
297 // Filter WatchInfo items when we do the Lookup.
298 // We do the filtering here to avoid having to acquire
299 // the watched_by_wd lock yet again.
300 static private WatchInfo Lookup (int wd, EventType event_type)
302 lock (watched_by_wd) {
303 WatchInfo watched;
304 if (last_watched != null && last_watched.Wd == wd)
305 watched = last_watched;
306 else {
307 watched = watched_by_wd [wd] as WatchInfo;
308 if (watched != null)
309 last_watched = watched;
312 if (watched != null && (watched.FilterMask & event_type) != 0) {
313 watched.FilterSeen |= event_type;
314 watched = null;
317 return watched;
321 // The caller has to handle all locking itself
322 static private void Forget (WatchInfo watched)
324 if (last_watched == watched)
325 last_watched = null;
326 if (watched.Parent != null)
327 watched.Parent.Children.Remove (watched);
328 watched_by_wd.Remove (watched.Wd);
329 watched_by_path.Remove (watched.Path);
332 static public Watch Subscribe (string path, InotifyCallback callback, EventType mask, EventType initial_filter)
334 WatchInternal watch;
335 WatchInfo watched;
336 EventType mask_orig = mask;
338 if (!Path.IsPathRooted (path))
339 path = Path.GetFullPath (path);
341 bool is_directory = false;
342 if (Directory.Exists (path))
343 is_directory = true;
344 else if (! File.Exists (path))
345 throw new IOException (path);
347 lock (watched_by_wd) {
348 watched = watched_by_path [path] as WatchInfo;
350 if (watched == null) {
351 // We need an entirely new WatchInfo object
352 watched = new WatchInfo ();
353 watched.Path = path;
354 watched.IsDirectory = is_directory;
355 watched.Subscribers = new ArrayList ();
356 watched.Children = new ArrayList ();
357 DirectoryInfo dir = new DirectoryInfo (path);
358 if (dir.Parent != null)
359 watched.Parent = watched_by_path [dir.Parent.ToString ()] as WatchInfo;
360 if (watched.Parent != null)
361 watched.Parent.Children.Add (watched);
362 watched_by_path [watched.Path] = watched;
365 watched.FilterMask = initial_filter;
366 watched.FilterSeen = 0;
368 watch = new WatchInternal (callback, mask_orig, watched);
369 watched.Subscribers.Add (watch);
371 CreateOrModifyWatch (watched);
372 watched_by_wd [watched.Wd] = watched;
375 return watch;
378 static public Watch Subscribe (string path, InotifyCallback callback, EventType mask)
380 return Subscribe (path, callback, mask, 0);
383 static public EventType Filter (string path, EventType mask)
385 EventType seen = 0;
387 path = Path.GetFullPath (path);
389 lock (watched_by_wd) {
390 WatchInfo watched;
391 watched = watched_by_path [path] as WatchInfo;
393 seen = watched.FilterSeen;
394 watched.FilterMask = mask;
395 watched.FilterSeen = 0;
398 return seen;
401 static private void Unsubscribe (WatchInfo watched, WatchInternal watch)
403 watched.Subscribers.Remove (watch);
405 // Other subscribers might still be around
406 if (watched.Subscribers.Count > 0) {
407 // Minimize it
408 CreateOrModifyWatch (watched);
409 return;
412 int retval = inotify_glue_ignore (inotify_fd, watched.Wd);
413 if (retval < 0) {
414 string msg = String.Format ("Attempt to ignore {0} failed!", watched.Path);
415 throw new IOException (msg);
418 Forget (watched);
419 return;
422 // Ensure our watch exists, meets all the subscribers requirements,
423 // and isn't matching any other events that we don't care about.
424 static private void CreateOrModifyWatch (WatchInfo watched)
426 EventType new_mask = base_mask;
427 foreach (WatchInternal watch in watched.Subscribers)
428 new_mask |= watch.Mask;
430 if (watched.Wd >= 0 && watched.Mask == new_mask)
431 return;
433 // We rely on the behaviour that watching the same inode twice won't result
434 // in the wd value changing.
435 // (no need to worry about watched_by_wd being polluted with stale watches)
437 int wd = -1;
438 wd = inotify_glue_watch (inotify_fd, watched.Path, new_mask);
439 if (wd < 0) {
440 string msg = String.Format ("Attempt to watch {0} failed!", watched.Path);
441 throw new IOException (msg);
443 if (watched.Wd >= 0 && watched.Wd != wd) {
444 string msg = String.Format ("Watch handle changed unexpectedly!", watched.Path);
445 throw new IOException (msg);
448 watched.Wd = wd;
449 watched.Mask = new_mask;
452 /////////////////////////////////////////////////////////////////////////////////////
454 static Thread snarf_thread = null;
455 static bool running = false;
457 static public void Start ()
459 if (! Enabled)
460 return;
462 Logger.Log.Debug("Starting Inotify threads");
464 lock (event_queue) {
465 if (snarf_thread != null)
466 return;
468 running = true;
470 snarf_thread = ExceptionHandlingThread.Start (new ThreadStart (SnarfWorker));
471 ExceptionHandlingThread.Start (new ThreadStart (DispatchWorker));
475 static public void Stop ()
477 if (! Enabled)
478 return;
480 lock (event_queue) {
481 running = false;
482 Monitor.Pulse (event_queue);
485 inotify_snarf_cancel ();
488 static unsafe void SnarfWorker ()
490 Encoding filename_encoding = Encoding.UTF8;
491 int event_size = Marshal.SizeOf (typeof (inotify_event));
493 while (running) {
495 // We get much better performance if we wait a tiny bit
496 // between reads in order to let events build up.
497 // FIXME: We need to be smarter here to avoid queue overflows.
498 Thread.Sleep (15);
500 IntPtr buffer;
501 int nr;
503 // Will block while waiting for events, but with a 1s timeout.
504 inotify_snarf_events (inotify_fd,
505 out nr,
506 out buffer);
508 if (!running)
509 break;
511 if (nr == 0)
512 continue;
514 ArrayList new_events = new ArrayList ();
516 bool saw_overflow = false;
517 while (nr > 0) {
519 // Read the low-level event struct from the buffer.
520 inotify_event raw_event;
521 raw_event = (inotify_event) Marshal.PtrToStructure (buffer, typeof (inotify_event));
522 buffer = (IntPtr) ((long) buffer + event_size);
524 if ((raw_event.mask & EventType.QueueOverflow) != 0)
525 saw_overflow = true;
527 // Now we convert our low-level event struct into a nicer object.
528 QueuedEvent qe = new QueuedEvent ();
529 qe.Wd = raw_event.wd;
530 qe.Type = raw_event.mask;
531 qe.Cookie = raw_event.cookie;
533 // Extract the filename payload (if any) from the buffer.
534 byte [] filename_bytes = new byte[raw_event.len];
535 Marshal.Copy (buffer, filename_bytes, 0, (int) raw_event.len);
536 buffer = (IntPtr) ((long) buffer + raw_event.len);
537 int n_chars = 0;
538 while (n_chars < filename_bytes.Length && filename_bytes [n_chars] != 0)
539 ++n_chars;
540 qe.Filename = "";
541 if (n_chars > 0)
542 qe.Filename = filename_encoding.GetString (filename_bytes, 0, n_chars);
544 new_events.Add (qe);
545 nr -= event_size + (int) raw_event.len;
548 if (saw_overflow)
549 Logger.Log.Warn ("Inotify queue overflow!");
551 lock (event_queue) {
552 event_queue.AddRange (new_events);
553 Monitor.Pulse (event_queue);
559 // Update the watched_by_path hash and the path stored inside the watch
560 // in response to a move event.
561 static private void MoveWatch (WatchInfo watch, string name)
563 lock (watched_by_wd) {
565 watched_by_path.Remove (watch.Path);
566 watch.Path = name;
567 watched_by_path [watch.Path] = watch;
570 if (Verbose)
571 Console.WriteLine ("*** inotify: Moved Watch to {0}", watch.Path);
574 // A directory we are watching has moved. We need to fix up its path, and the path of
575 // all of its subdirectories, their subdirectories, and so on.
576 static private void HandleMove (string srcpath, string dstparent, string dstname)
578 string dstpath = Path.Combine (dstparent, dstname);
579 lock (watched_by_wd) {
581 WatchInfo start = watched_by_path [srcpath] as WatchInfo; // not the same as src!
582 if (start == null) {
583 Logger.Log.Warn ("Lookup failed for {0}", srcpath);
584 return;
587 // Queue our starting point, then walk its subdirectories, invoking MoveWatch() on
588 // each, repeating for their subdirectories. The relationship between start, child
589 // and dstpath is fickle and important.
590 Queue queue = new Queue();
591 queue.Enqueue (start);
592 do {
593 WatchInfo target = queue.Dequeue () as WatchInfo;
594 for (int i = 0; i < target.Children.Count; i++) {
595 WatchInfo child = target.Children[i] as WatchInfo;
596 Logger.Log.Debug ("Moving watch on {0} from {1} to {2}", child.Path, srcpath, dstpath);
597 string name = Path.Combine (dstpath, child.Path.Substring (srcpath.Length + 1));
598 MoveWatch (child, name);
599 queue.Enqueue (child);
601 } while (queue.Count > 0);
603 // Ultimately, fixup the original watch, too
604 MoveWatch (start, dstpath);
605 if (start.Parent != null)
606 start.Parent.Children.Remove (start);
607 start.Parent = watched_by_path [dstparent] as WatchInfo;
608 if (start.Parent != null)
609 start.Parent.Children.Add (start);
613 static private void SendEvent (WatchInfo watched, string filename, string srcpath, EventType mask)
615 // Does the watch care about this event?
616 if ((watched.Mask & mask) == 0)
617 return;
619 bool isDirectory = false;
620 if ((mask & EventType.IsDirectory) != 0)
621 isDirectory = true;
623 if (Verbose) {
624 Console.WriteLine ("*** inotify: {0} {1} {2} {3} {4} {5}",
625 mask, watched.Wd, watched.Path,
626 filename != "" ? filename : "\"\"",
627 isDirectory == true ? "(directory)" : "(file)",
628 srcpath != null ? "(from " + srcpath + ")" : "");
631 if (watched.Subscribers == null)
632 return;
634 foreach (WatchInternal watch in (IEnumerable) watched.Subscribers.Clone ())
635 try {
636 if (watch.Callback != null && (watch.Mask & mask) != 0)
637 watch.Callback (watch, watched.Path, filename, srcpath, mask);
638 } catch (Exception e) {
639 Logger.Log.Error ("Caught exception executing Inotify callbacks");
640 Logger.Log.Error (e);
644 ////////////////////////////////////////////////////////////////////////////////////////////////////
646 // Dispatch-time operations on the event queue
648 static Hashtable pending_move_cookies = new Hashtable ();
650 // Clean up the queue, removing dispatched objects.
651 // We assume that the called holds the event_queue lock.
652 static void CleanQueue_Unlocked ()
654 int first_undispatched = 0;
655 while (first_undispatched < event_queue.Count) {
656 QueuedEvent qe = event_queue [first_undispatched] as QueuedEvent;
657 if (! qe.Dispatched)
658 break;
660 if (qe.Cookie != 0)
661 pending_move_cookies.Remove (qe.Cookie);
663 ++first_undispatched;
666 if (first_undispatched > 0)
667 event_queue.RemoveRange (0, first_undispatched);
671 // Apply high-level processing to the queue. Pair moves,
672 // coalesce events, etc.
673 // We assume that the caller holds the event_queue lock.
674 static void AnalyzeQueue_Unlocked ()
676 int first_unanalyzed = event_queue.Count;
677 while (first_unanalyzed > 0) {
678 --first_unanalyzed;
679 QueuedEvent qe = event_queue [first_unanalyzed] as QueuedEvent;
680 if (qe.Analyzed) {
681 ++first_unanalyzed;
682 break;
685 if (first_unanalyzed == event_queue.Count)
686 return;
688 // Walk across the unanalyzed events...
689 for (int i = first_unanalyzed; i < event_queue.Count; ++i) {
690 QueuedEvent qe = event_queue [i] as QueuedEvent;
692 // Pair off the MovedFrom and MovedTo events.
693 if (qe.Cookie != 0) {
694 if ((qe.Type & EventType.MovedFrom) != 0) {
695 pending_move_cookies [qe.Cookie] = qe;
696 // This increases the MovedFrom's HoldUntil time,
697 // giving us more time for the matching MovedTo to
698 // show up.
699 // (512 ms is totally arbitrary)
700 qe.AddMilliseconds (512);
701 } else if ((qe.Type & EventType.MovedTo) != 0) {
702 QueuedEvent paired_move = pending_move_cookies [qe.Cookie] as QueuedEvent;
703 if (paired_move != null) {
704 paired_move.Dispatched = true;
705 qe.PairedMove = paired_move;
710 qe.Analyzed = true;
714 static void DispatchWorker ()
716 while (running) {
717 QueuedEvent next_event = null;
719 // Until we find something we want to dispatch, we will stay
720 // inside the following block of code.
721 lock (event_queue) {
723 while (running) {
724 CleanQueue_Unlocked ();
726 AnalyzeQueue_Unlocked ();
728 // Now look for an event to dispatch.
729 DateTime min_hold_until = DateTime.MaxValue;
730 DateTime now = DateTime.Now;
731 foreach (QueuedEvent qe in event_queue) {
732 if (qe.Dispatched)
733 continue;
734 if (qe.HoldUntil <= now) {
735 next_event = qe;
736 break;
738 if (qe.HoldUntil < min_hold_until)
739 min_hold_until = qe.HoldUntil;
742 // If we found an event, break out of this block
743 // and dispatch it.
744 if (next_event != null)
745 break;
747 // If we didn't find an event to dispatch, we can sleep
748 // (1) until the next hold-until time
749 // (2) until the lock pulses (which means something changed, so
750 // we need to check that we are still running, new events
751 // are on the queue, etc.)
752 // and then we go back up and try to find something to dispatch
753 // all over again.
754 if (min_hold_until == DateTime.MaxValue)
755 Monitor.Wait (event_queue);
756 else
757 Monitor.Wait (event_queue, min_hold_until - now);
761 // If "running" gets set to false, we might get a null next_event as the above
762 // loop terminates
763 if (next_event == null)
764 return;
766 // Now we have an event, so we release the event_queue lock and do
767 // the actual dispatch.
769 // Before we get any further, mark it
770 next_event.Dispatched = true;
772 WatchInfo watched;
773 watched = Lookup (next_event.Wd, next_event.Type);
774 if (watched == null)
775 continue;
777 string srcpath = null;
779 // If this event is a paired MoveTo, there is extra work to do.
780 if ((next_event.Type & EventType.MovedTo) != 0 && next_event.PairedMove != null) {
781 WatchInfo paired_watched;
782 paired_watched = Lookup (next_event.PairedMove.Wd, next_event.PairedMove.Type);
784 if (paired_watched != null) {
785 // Set the source path accordingly.
786 srcpath = Path.Combine (paired_watched.Path, next_event.PairedMove.Filename);
788 // Handle the internal rename of the directory.
789 if ((next_event.Type & EventType.IsDirectory) != 0)
790 HandleMove (srcpath, watched.Path, next_event.Filename);
794 SendEvent (watched, next_event.Filename, srcpath, next_event.Type);
796 // If a directory we are watching gets ignored, we need
797 // to remove it from the watchedByFoo hashes.
798 if ((next_event.Type & EventType.Ignored) != 0) {
799 lock (watched_by_wd)
800 Forget (watched);
805 /////////////////////////////////////////////////////////////////////////////////
807 #if INOTIFY_TEST
808 static void Main (string [] args)
810 Queue to_watch = new Queue ();
811 bool recursive = false;
813 foreach (string arg in args) {
814 if (arg == "-r" || arg == "--recursive")
815 recursive = true;
816 else {
817 // Our hashes work without a trailing path delimiter
818 string path = arg.TrimEnd ('/');
819 to_watch.Enqueue (path);
823 while (to_watch.Count > 0) {
824 string path = (string) to_watch.Dequeue ();
826 Console.WriteLine ("Watching {0}", path);
827 Inotify.Subscribe (path, null, Inotify.EventType.All);
829 if (recursive) {
830 foreach (string subdir in DirectoryWalker.GetDirectories (path))
831 to_watch.Enqueue (subdir);
835 Inotify.Start ();
836 Inotify.Verbose = true;
838 while (Inotify.Enabled && Inotify.WatchCount > 0)
839 Thread.Sleep (1000);
841 if (Inotify.WatchCount == 0)
842 Console.WriteLine ("Nothing being watched.");
844 // Kill the event-reading thread so that we exit
845 Inotify.Stop ();
847 #endif
849 #endif // ENABLE_INOTIFY