Some more fixes wrt child-indexables. Namely, fix proper handling of child indexables...
[beagle.git] / Util / Inotify.cs
blob2b686d47a9aaf7bae29bf139182f5ddcc53ce706
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 OnlyDir = 0x01000000, // Only watch a path if it is a directory
68 DoNotFollow = 0x02000000, // Do not follow symbolic links
69 MaskAdd = 0x20000000, // Add, do not replace, mask on the inode if it exists
70 IsDirectory = 0x40000000, // Event is against a directory
71 OneShot = 0x80000000, // Watch is one-shot
73 // For forward compatibility, define these explicitly
74 All = (EventType.Access | EventType.Modify | EventType.Attrib |
75 EventType.CloseWrite | EventType.CloseNoWrite | EventType.Open |
76 EventType.MovedFrom | EventType.MovedTo | EventType.Create |
77 EventType.Delete | EventType.DeleteSelf)
80 // Events that we want internally, even if the handlers do not
81 private static EventType base_mask = EventType.MovedFrom | EventType.MovedTo;
83 /////////////////////////////////////////////////////////////////////////////////////
85 [StructLayout (LayoutKind.Sequential)]
86 private struct inotify_event {
87 public int wd;
88 public EventType mask;
89 public uint cookie;
90 public uint len;
93 [DllImport ("libbeagleglue")]
94 static extern int inotify_glue_init ();
96 [DllImport ("libbeagleglue")]
97 static extern int inotify_glue_watch (int fd, string filename, EventType mask);
99 [DllImport ("libbeagleglue")]
100 static extern int inotify_glue_ignore (int fd, int wd);
102 [DllImport ("libbeagleglue")]
103 static extern unsafe void inotify_snarf_events (int fd,
104 out int nr,
105 out IntPtr buffer);
107 [DllImport ("libbeagleglue")]
108 static extern void inotify_snarf_cancel ();
110 /////////////////////////////////////////////////////////////////////////////////////
112 public static bool Verbose = false;
113 private static int inotify_fd = -1;
115 static Inotify ()
117 if (Environment.GetEnvironmentVariable ("BEAGLE_DISABLE_INOTIFY") != null) {
118 Logger.Log.Debug ("BEAGLE_DISABLE_INOTIFY is set");
119 return;
122 if (Environment.GetEnvironmentVariable ("BEAGLE_INOTIFY_VERBOSE") != null)
123 Inotify.Verbose = true;
125 try {
126 inotify_fd = inotify_glue_init ();
127 } catch (EntryPointNotFoundException) {
128 Logger.Log.Info ("Inotify not available on system.");
129 return;
132 if (inotify_fd == -1)
133 Logger.Log.Warn ("Could not initialize inotify");
136 public static bool Enabled {
137 get { return inotify_fd >= 0; }
140 /////////////////////////////////////////////////////////////////////////////////////
142 #if ! ENABLE_INOTIFY
144 // Stubs for systems where inotify is unavailable
146 public static Watch Subscribe (string path, InotifyCallback callback, EventType mask)
148 return null;
151 public static void Start ()
153 return;
156 public static void Stop ()
158 return;
161 #else // ENABLE_INOTIFY
163 /////////////////////////////////////////////////////////////////////////////////////
164 private static ArrayList event_queue = new ArrayList ();
166 private class QueuedEvent {
167 public int Wd;
168 public EventType Type;
169 public string Filename;
170 public uint Cookie;
172 public bool Analyzed;
173 public bool Dispatched;
174 public DateTime HoldUntil;
175 public QueuedEvent PairedMove;
177 // Measured in milliseconds; 57ms is totally random
178 public const double DefaultHoldTime = 57;
180 public QueuedEvent ()
182 // Set a default HoldUntil time
183 HoldUntil = DateTime.Now.AddMilliseconds (DefaultHoldTime);
186 public void AddMilliseconds (double x)
188 HoldUntil = HoldUntil.AddMilliseconds (x);
191 public void PairWith (QueuedEvent other)
193 this.PairedMove = other;
194 other.PairedMove = this;
196 if (this.HoldUntil < other.HoldUntil)
197 this.HoldUntil = other.HoldUntil;
198 other.HoldUntil = this.HoldUntil;
202 /////////////////////////////////////////////////////////////////////////////////////
204 private class WatchInternal : Watch {
205 private InotifyCallback callback;
206 private EventType mask;
207 private WatchInfo watchinfo;
208 private bool is_subscribed;
210 public InotifyCallback Callback {
211 get { return callback; }
214 public EventType Mask {
215 get { return mask; }
216 set { mask = value; }
219 public WatchInternal (InotifyCallback callback, EventType mask, WatchInfo watchinfo)
221 this.callback = callback;
222 this.mask = mask;
223 this.watchinfo = watchinfo;
224 this.is_subscribed = true;
227 public void Unsubscribe ()
229 if (!this.is_subscribed)
230 return;
232 Inotify.Unsubscribe (watchinfo, this);
233 this.is_subscribed = false;
236 public void ChangeSubscription (EventType mask)
238 if (! this.is_subscribed)
239 return;
241 this.mask = mask;
242 CreateOrModifyWatch (this.watchinfo);
247 private class WatchInfo {
248 public int Wd = -1;
249 public string Path;
250 public bool IsDirectory;
251 public EventType Mask;
253 public EventType FilterMask;
254 public EventType FilterSeen;
256 public ArrayList Children;
257 public WatchInfo Parent;
259 public ArrayList Subscribers;
262 private static Hashtable watched_by_wd = new Hashtable ();
263 private static Hashtable watched_by_path = new Hashtable ();
264 private static WatchInfo last_watched = null;
266 private class PendingMove {
267 public WatchInfo Watch;
268 public string SrcName;
269 public DateTime Time;
270 public uint Cookie;
272 public PendingMove (WatchInfo watched, string srcname, DateTime time, uint cookie) {
273 Watch = watched;
274 SrcName = srcname;
275 Time = time;
276 Cookie = cookie;
280 public static int WatchCount {
281 get { return watched_by_wd.Count; }
284 public static bool IsWatching (string path)
286 path = Path.GetFullPath (path);
287 return watched_by_path.Contains (path);
290 // Filter WatchInfo items when we do the Lookup.
291 // We do the filtering here to avoid having to acquire
292 // the watched_by_wd lock yet again.
293 private static WatchInfo Lookup (int wd, EventType event_type)
295 lock (watched_by_wd) {
296 WatchInfo watched;
297 if (last_watched != null && last_watched.Wd == wd)
298 watched = last_watched;
299 else {
300 watched = watched_by_wd [wd] as WatchInfo;
301 if (watched != null)
302 last_watched = watched;
305 if (watched != null && (watched.FilterMask & event_type) != 0) {
306 watched.FilterSeen |= event_type;
307 watched = null;
310 return watched;
314 // The caller has to handle all locking itself
315 private static void Forget (WatchInfo watched)
317 if (last_watched == watched)
318 last_watched = null;
319 if (watched.Parent != null)
320 watched.Parent.Children.Remove (watched);
321 watched_by_wd.Remove (watched.Wd);
322 watched_by_path.Remove (watched.Path);
325 public static Watch Subscribe (string path, InotifyCallback callback, EventType mask, EventType initial_filter)
327 WatchInternal watch;
328 WatchInfo watched;
329 EventType mask_orig = mask;
331 if (!Path.IsPathRooted (path))
332 path = Path.GetFullPath (path);
334 bool is_directory = false;
335 if (Directory.Exists (path))
336 is_directory = true;
337 else if (! File.Exists (path))
338 throw new IOException (path);
340 lock (watched_by_wd) {
341 watched = watched_by_path [path] as WatchInfo;
343 if (watched == null) {
344 // We need an entirely new WatchInfo object
345 watched = new WatchInfo ();
346 watched.Path = path;
347 watched.IsDirectory = is_directory;
348 watched.Subscribers = new ArrayList ();
349 watched.Children = new ArrayList ();
350 DirectoryInfo dir = new DirectoryInfo (path);
351 if (dir.Parent != null)
352 watched.Parent = watched_by_path [dir.Parent.ToString ()] as WatchInfo;
353 if (watched.Parent != null)
354 watched.Parent.Children.Add (watched);
355 watched_by_path [watched.Path] = watched;
358 watched.FilterMask = initial_filter;
359 watched.FilterSeen = 0;
361 watch = new WatchInternal (callback, mask_orig, watched);
362 watched.Subscribers.Add (watch);
364 CreateOrModifyWatch (watched);
365 watched_by_wd [watched.Wd] = watched;
368 return watch;
371 public static Watch Subscribe (string path, InotifyCallback callback, EventType mask)
373 return Subscribe (path, callback, mask, 0);
376 public static EventType Filter (string path, EventType mask)
378 EventType seen = 0;
380 path = Path.GetFullPath (path);
382 lock (watched_by_wd) {
383 WatchInfo watched;
384 watched = watched_by_path [path] as WatchInfo;
386 seen = watched.FilterSeen;
387 watched.FilterMask = mask;
388 watched.FilterSeen = 0;
391 return seen;
394 private static void Unsubscribe (WatchInfo watched, WatchInternal watch)
396 watched.Subscribers.Remove (watch);
398 // Other subscribers might still be around
399 if (watched.Subscribers.Count > 0) {
400 // Minimize it
401 CreateOrModifyWatch (watched);
402 return;
405 int retval = inotify_glue_ignore (inotify_fd, watched.Wd);
406 if (retval < 0) {
407 string msg = String.Format ("Attempt to ignore {0} failed!", watched.Path);
408 throw new IOException (msg);
411 Forget (watched);
412 return;
415 // Ensure our watch exists, meets all the subscribers requirements,
416 // and isn't matching any other events that we don't care about.
417 private static void CreateOrModifyWatch (WatchInfo watched)
419 EventType new_mask = base_mask;
420 foreach (WatchInternal watch in watched.Subscribers)
421 new_mask |= watch.Mask;
423 if (watched.Wd >= 0 && watched.Mask == new_mask)
424 return;
426 // We rely on the behaviour that watching the same inode twice won't result
427 // in the wd value changing.
428 // (no need to worry about watched_by_wd being polluted with stale watches)
430 int wd = -1;
431 wd = inotify_glue_watch (inotify_fd, watched.Path, new_mask);
432 if (wd < 0) {
433 string msg = String.Format ("Attempt to watch {0} failed!", watched.Path);
434 throw new IOException (msg);
436 if (watched.Wd >= 0 && watched.Wd != wd) {
437 string msg = String.Format ("Watch handle changed unexpectedly!", watched.Path);
438 throw new IOException (msg);
441 watched.Wd = wd;
442 watched.Mask = new_mask;
445 /////////////////////////////////////////////////////////////////////////////////////
447 private static Thread snarf_thread = null;
448 private static bool running = false;
449 private static bool shutdown_requested = false;
451 public static void ShutdownRequested () {
452 lock (event_queue) {
453 shutdown_requested = true;
457 public static void Start ()
459 if (! Enabled)
460 return;
462 Logger.Log.Debug("Starting Inotify threads");
464 lock (event_queue) {
465 if (shutdown_requested || snarf_thread != null)
466 return;
468 running = true;
470 snarf_thread = ExceptionHandlingThread.Start (new ThreadStart (SnarfWorker));
471 ExceptionHandlingThread.Start (new ThreadStart (DispatchWorker));
475 public static void Stop ()
477 if (! Enabled)
478 return;
480 Log.Debug ("Stopping inotify threads");
482 lock (event_queue) {
483 shutdown_requested = true;
485 if (! running)
486 return;
488 running = false;
489 Monitor.Pulse (event_queue);
492 inotify_snarf_cancel ();
495 private static unsafe void SnarfWorker ()
497 Encoding filename_encoding = Encoding.UTF8;
498 int event_size = Marshal.SizeOf (typeof (inotify_event));
500 while (running) {
502 // We get much better performance if we wait a tiny bit
503 // between reads in order to let events build up.
504 // FIXME: We need to be smarter here to avoid queue overflows.
505 Thread.Sleep (15);
507 IntPtr buffer;
508 int nr;
510 // Will block while waiting for events, but with a 1s timeout.
511 inotify_snarf_events (inotify_fd,
512 out nr,
513 out buffer);
515 if (!running)
516 break;
518 if (nr == 0)
519 continue;
521 ArrayList new_events = new ArrayList ();
523 bool saw_overflow = false;
524 while (nr > 0) {
526 // Read the low-level event struct from the buffer.
527 inotify_event raw_event;
528 raw_event = (inotify_event) Marshal.PtrToStructure (buffer, typeof (inotify_event));
529 buffer = (IntPtr) ((long) buffer + event_size);
531 if ((raw_event.mask & EventType.QueueOverflow) != 0)
532 saw_overflow = true;
534 // Now we convert our low-level event struct into a nicer object.
535 QueuedEvent qe = new QueuedEvent ();
536 qe.Wd = raw_event.wd;
537 qe.Type = raw_event.mask;
538 qe.Cookie = raw_event.cookie;
540 // Extract the filename payload (if any) from the buffer.
541 byte [] filename_bytes = new byte[raw_event.len];
542 Marshal.Copy (buffer, filename_bytes, 0, (int) raw_event.len);
543 buffer = (IntPtr) ((long) buffer + raw_event.len);
544 int n_chars = 0;
545 while (n_chars < filename_bytes.Length && filename_bytes [n_chars] != 0)
546 ++n_chars;
547 qe.Filename = "";
548 if (n_chars > 0)
549 qe.Filename = filename_encoding.GetString (filename_bytes, 0, n_chars);
551 new_events.Add (qe);
552 nr -= event_size + (int) raw_event.len;
555 if (saw_overflow)
556 Logger.Log.Warn ("Inotify queue overflow!");
558 lock (event_queue) {
559 event_queue.AddRange (new_events);
560 Monitor.Pulse (event_queue);
566 // Update the watched_by_path hash and the path stored inside the watch
567 // in response to a move event.
568 private static void MoveWatch (WatchInfo watch, string name)
570 lock (watched_by_wd) {
572 watched_by_path.Remove (watch.Path);
573 watch.Path = name;
574 watched_by_path [watch.Path] = watch;
577 if (Verbose)
578 Console.WriteLine ("*** inotify: Moved Watch to {0}", watch.Path);
581 // A directory we are watching has moved. We need to fix up its path, and the path of
582 // all of its subdirectories, their subdirectories, and so on.
583 private static void HandleMove (string srcpath, string dstparent, string dstname)
585 string dstpath = Path.Combine (dstparent, dstname);
586 lock (watched_by_wd) {
588 WatchInfo start = watched_by_path [srcpath] as WatchInfo; // not the same as src!
589 if (start == null) {
590 Logger.Log.Warn ("Lookup failed for {0}", srcpath);
591 return;
594 // Queue our starting point, then walk its subdirectories, invoking MoveWatch() on
595 // each, repeating for their subdirectories. The relationship between start, child
596 // and dstpath is fickle and important.
597 Queue queue = new Queue();
598 queue.Enqueue (start);
599 do {
600 WatchInfo target = queue.Dequeue () as WatchInfo;
601 for (int i = 0; i < target.Children.Count; i++) {
602 WatchInfo child = target.Children[i] as WatchInfo;
603 Logger.Log.Debug ("Moving watch on {0} from {1} to {2}", child.Path, srcpath, dstpath);
604 string name = Path.Combine (dstpath, child.Path.Substring (srcpath.Length + 1));
605 MoveWatch (child, name);
606 queue.Enqueue (child);
608 } while (queue.Count > 0);
610 // Ultimately, fixup the original watch, too
611 MoveWatch (start, dstpath);
612 if (start.Parent != null)
613 start.Parent.Children.Remove (start);
614 start.Parent = watched_by_path [dstparent] as WatchInfo;
615 if (start.Parent != null)
616 start.Parent.Children.Add (start);
620 private static void SendEvent (WatchInfo watched, string filename, string srcpath, EventType mask)
622 // Does the watch care about this event?
623 if ((watched.Mask & mask) == 0)
624 return;
626 bool isDirectory = false;
627 if ((mask & EventType.IsDirectory) != 0)
628 isDirectory = true;
630 if (Verbose) {
631 Console.WriteLine ("*** inotify: {0} {1} {2} {3} {4} {5}",
632 mask, watched.Wd, watched.Path,
633 filename != "" ? filename : "\"\"",
634 isDirectory == true ? "(directory)" : "(file)",
635 srcpath != null ? "(from " + srcpath + ")" : "");
638 if (watched.Subscribers == null)
639 return;
641 foreach (WatchInternal watch in (IEnumerable) watched.Subscribers.Clone ())
642 try {
643 if (watch.Callback != null && (watch.Mask & mask) != 0)
644 watch.Callback (watch, watched.Path, filename, srcpath, mask);
645 } catch (Exception e) {
646 Logger.Log.Error (e, "Caught exception executing Inotify callbacks");
650 ////////////////////////////////////////////////////////////////////////////////////////////////////
652 // Dispatch-time operations on the event queue
654 private static Hashtable pending_move_cookies = new Hashtable ();
656 // Clean up the queue, removing dispatched objects.
657 // We assume that the called holds the event_queue lock.
658 private static void CleanQueue_Unlocked ()
660 int first_undispatched = 0;
661 while (first_undispatched < event_queue.Count) {
662 QueuedEvent qe = event_queue [first_undispatched] as QueuedEvent;
663 if (! qe.Dispatched)
664 break;
666 if (qe.Cookie != 0)
667 pending_move_cookies.Remove (qe.Cookie);
669 ++first_undispatched;
672 if (first_undispatched > 0)
673 event_queue.RemoveRange (0, first_undispatched);
677 // Apply high-level processing to the queue. Pair moves,
678 // coalesce events, etc.
679 // We assume that the caller holds the event_queue lock.
680 private static void AnalyzeQueue_Unlocked ()
682 int first_unanalyzed = event_queue.Count;
683 while (first_unanalyzed > 0) {
684 --first_unanalyzed;
685 QueuedEvent qe = event_queue [first_unanalyzed] as QueuedEvent;
686 if (qe.Analyzed) {
687 ++first_unanalyzed;
688 break;
691 if (first_unanalyzed == event_queue.Count)
692 return;
694 // Walk across the unanalyzed events...
695 for (int i = first_unanalyzed; i < event_queue.Count; ++i) {
696 QueuedEvent qe = event_queue [i] as QueuedEvent;
698 // Pair off the MovedFrom and MovedTo events.
699 if (qe.Cookie != 0) {
700 if ((qe.Type & EventType.MovedFrom) != 0) {
701 pending_move_cookies [qe.Cookie] = qe;
702 // This increases the MovedFrom's HoldUntil time,
703 // giving us more time for the matching MovedTo to
704 // show up.
705 // (512 ms is totally arbitrary)
706 qe.AddMilliseconds (512);
707 } else if ((qe.Type & EventType.MovedTo) != 0) {
708 QueuedEvent paired_move = pending_move_cookies [qe.Cookie] as QueuedEvent;
709 if (paired_move != null) {
710 paired_move.Dispatched = true;
711 qe.PairedMove = paired_move;
716 qe.Analyzed = true;
720 private static void DispatchWorker ()
722 while (running) {
723 QueuedEvent next_event = null;
725 // Until we find something we want to dispatch, we will stay
726 // inside the following block of code.
727 lock (event_queue) {
729 while (running) {
730 CleanQueue_Unlocked ();
732 AnalyzeQueue_Unlocked ();
734 // Now look for an event to dispatch.
735 DateTime min_hold_until = DateTime.MaxValue;
736 DateTime now = DateTime.Now;
737 foreach (QueuedEvent qe in event_queue) {
738 if (qe.Dispatched)
739 continue;
740 if (qe.HoldUntil <= now) {
741 next_event = qe;
742 break;
744 if (qe.HoldUntil < min_hold_until)
745 min_hold_until = qe.HoldUntil;
748 // If we found an event, break out of this block
749 // and dispatch it.
750 if (next_event != null)
751 break;
753 // If we didn't find an event to dispatch, we can sleep
754 // (1) until the next hold-until time
755 // (2) until the lock pulses (which means something changed, so
756 // we need to check that we are still running, new events
757 // are on the queue, etc.)
758 // and then we go back up and try to find something to dispatch
759 // all over again.
760 if (min_hold_until == DateTime.MaxValue)
761 Monitor.Wait (event_queue);
762 else
763 Monitor.Wait (event_queue, min_hold_until - now);
767 // If "running" gets set to false, we might get a null next_event as the above
768 // loop terminates
769 if (next_event == null)
770 return;
772 // Now we have an event, so we release the event_queue lock and do
773 // the actual dispatch.
775 // Before we get any further, mark it
776 next_event.Dispatched = true;
778 WatchInfo watched;
779 watched = Lookup (next_event.Wd, next_event.Type);
780 if (watched == null)
781 continue;
783 string srcpath = null;
785 // If this event is a paired MoveTo, there is extra work to do.
786 if ((next_event.Type & EventType.MovedTo) != 0 && next_event.PairedMove != null) {
787 WatchInfo paired_watched;
788 paired_watched = Lookup (next_event.PairedMove.Wd, next_event.PairedMove.Type);
790 if (paired_watched != null) {
791 // Set the source path accordingly.
792 srcpath = Path.Combine (paired_watched.Path, next_event.PairedMove.Filename);
794 // Handle the internal rename of the directory.
795 if ((next_event.Type & EventType.IsDirectory) != 0)
796 HandleMove (srcpath, watched.Path, next_event.Filename);
800 SendEvent (watched, next_event.Filename, srcpath, next_event.Type);
802 // If a directory we are watching gets ignored, we need
803 // to remove it from the watchedByFoo hashes.
804 if ((next_event.Type & EventType.Ignored) != 0) {
805 lock (watched_by_wd)
806 Forget (watched);
811 /////////////////////////////////////////////////////////////////////////////////
813 #if INOTIFY_TEST
814 private static void Main (string [] args)
816 Queue to_watch = new Queue ();
817 bool recursive = false;
819 foreach (string arg in args) {
820 if (arg == "-r" || arg == "--recursive")
821 recursive = true;
822 else {
823 // Our hashes work without a trailing path delimiter
824 string path = arg.TrimEnd ('/');
825 to_watch.Enqueue (path);
829 while (to_watch.Count > 0) {
830 string path = (string) to_watch.Dequeue ();
832 Console.WriteLine ("Watching {0}", path);
833 Inotify.Subscribe (path, null, Inotify.EventType.All);
835 if (recursive) {
836 foreach (string subdir in DirectoryWalker.GetDirectories (path))
837 to_watch.Enqueue (subdir);
841 Inotify.Start ();
842 Inotify.Verbose = true;
844 while (Inotify.Enabled && Inotify.WatchCount > 0)
845 Thread.Sleep (1000);
847 if (Inotify.WatchCount == 0)
848 Console.WriteLine ("Nothing being watched.");
850 // Kill the event-reading thread so that we exit
851 Inotify.Stop ();
853 #endif
855 #endif // ENABLE_INOTIFY