Lines 150-156
public void Dispose ()
Link Here
|
150 |
[StructLayout(LayoutKind.Sequential)] |
150 |
[StructLayout(LayoutKind.Sequential)] |
151 |
struct timespec { |
151 |
struct timespec { |
152 |
public IntPtr tv_sec; |
152 |
public IntPtr tv_sec; |
153 |
public IntPtr tv_usec; |
153 |
public IntPtr tv_nsec; |
154 |
} |
154 |
} |
155 |
|
155 |
|
156 |
class PathData |
156 |
class PathData |
Lines 223-230
public void Stop ()
Link Here
|
223 |
conn = -1; |
223 |
conn = -1; |
224 |
} |
224 |
} |
225 |
|
225 |
|
226 |
if (!thread.Join (2000)) |
226 |
while (!thread.Join (2000)) |
227 |
thread.Abort (); |
227 |
thread.Interrupt (); |
228 |
|
228 |
|
229 |
requestStop = false; |
229 |
requestStop = false; |
230 |
started = false; |
230 |
started = false; |
Lines 291-301
void Setup ()
Link Here
|
291 |
else |
291 |
else |
292 |
fullPathNoLastSlash = fsw.FullPath; |
292 |
fullPathNoLastSlash = fsw.FullPath; |
293 |
|
293 |
|
294 |
// GetFilenameFromFd() returns the *realpath* which can be different than fsw.FullPath because symlinks. |
294 |
// realpath() returns the *realpath* which can be different than fsw.FullPath because symlinks. |
295 |
// If so, introduce a fixup step. |
295 |
// If so, introduce a fixup step. |
296 |
int fd = open (fullPathNoLastSlash, O_EVTONLY, 0); |
296 |
var sb = new StringBuilder (__DARWIN_MAXPATHLEN); |
297 |
var resolvedFullPath = GetFilenameFromFd (fd); |
297 |
var resolvedFullPath = (realpath(fsw.FullPath, sb) == IntPtr.Zero) ? "" : sb.ToString(); |
298 |
close (fd); |
|
|
299 |
|
298 |
|
300 |
if (resolvedFullPath != fullPathNoLastSlash) |
299 |
if (resolvedFullPath != fullPathNoLastSlash) |
301 |
fixupPath = resolvedFullPath; |
300 |
fixupPath = resolvedFullPath; |
Lines 304-317
void Setup ()
Link Here
|
304 |
|
303 |
|
305 |
Scan (fullPathNoLastSlash, false, ref initialFds); |
304 |
Scan (fullPathNoLastSlash, false, ref initialFds); |
306 |
|
305 |
|
307 |
var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)0 }; |
306 |
var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_nsec = (IntPtr)0 }; |
308 |
var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point |
307 |
var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point |
309 |
var changes = CreateChangeList (ref initialFds); |
308 |
var changes = CreateChangeList (ref initialFds); |
310 |
|
309 |
|
311 |
int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref immediate_timeout); |
310 |
int numEvents; |
|
|
311 |
int errno = 0; |
312 |
do { |
313 |
numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref immediate_timeout); |
314 |
if (numEvents == -1) { |
315 |
errno = Marshal.GetLastWin32Error (); |
316 |
} |
317 |
} while (numEvents == -1 && errno == EINTR); |
312 |
|
318 |
|
313 |
if (numEvents == -1) { |
319 |
if (numEvents == -1) { |
314 |
var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", Marshal.GetLastWin32Error ()); |
320 |
var errMsg = String.Format ("kevent() error at initial event registration, error code = '{0}'", errno); |
315 |
throw new IOException (errMsg); |
321 |
throw new IOException (errMsg); |
316 |
} |
322 |
} |
317 |
} |
323 |
} |
Lines 361-369
void Monitor ()
Link Here
|
361 |
// Stop () signals us to stop by closing the connection |
367 |
// Stop () signals us to stop by closing the connection |
362 |
if (requestStop) |
368 |
if (requestStop) |
363 |
break; |
369 |
break; |
364 |
if (++retries == 3) |
370 |
int errno = Marshal.GetLastWin32Error (); |
|
|
371 |
if (errno != EINTR && ++retries == 3) |
365 |
throw new IOException (String.Format ( |
372 |
throw new IOException (String.Format ( |
366 |
"persistent kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ())); |
373 |
"persistent kevent() error, error code = '{0}'", errno)); |
367 |
|
374 |
|
368 |
continue; |
375 |
continue; |
369 |
} |
376 |
} |
Lines 395-402
void Monitor ()
Link Here
|
395 |
} |
402 |
} |
396 |
|
403 |
|
397 |
if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) { |
404 |
if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) { |
398 |
UpdatePath (pathData); |
405 |
/* We can simply remove the entire subtree here, as |
399 |
} |
406 |
the move will trigger a directory update and thus |
|
|
407 |
a re-scan at the new location, which will cause any |
408 |
children to be re-added. */ |
409 |
removeQueue.Add (pathData); |
410 |
if (pathData.IsDirectory) { |
411 |
var prefix = pathData.Path + Path.DirectorySeparatorChar; |
412 |
foreach (var path in pathsDict.Keys) |
413 |
if (path.StartsWith (prefix)) { |
414 |
removeQueue.Add (pathsDict [path]); |
415 |
} |
416 |
} |
417 |
PostEvent (FileAction.RenamedOldName, pathData.Path); |
418 |
} |
400 |
|
419 |
|
401 |
if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) { |
420 |
if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) { |
402 |
if (pathData.IsDirectory) //TODO: Check if dirs trigger Changed events on .NET |
421 |
if (pathData.IsDirectory) //TODO: Check if dirs trigger Changed events on .NET |
Lines 584-598
void PostEvent (FileAction action, string path, string newPath = null)
Link Here
|
584 |
return; |
603 |
return; |
585 |
|
604 |
|
586 |
// e.Name |
605 |
// e.Name |
587 |
string name = path.Substring (fullPathNoLastSlash.Length + 1); |
606 |
string name = (path.Length > fullPathNoLastSlash.Length) ? path.Substring (fullPathNoLastSlash.Length + 1) : String.Empty; |
588 |
|
607 |
|
589 |
// only post events that match filter pattern. check both old and new paths for renames |
608 |
// only post events that match filter pattern. check both old and new paths for renames |
590 |
if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath))) |
609 |
if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath))) |
591 |
return; |
610 |
return; |
592 |
|
611 |
|
593 |
if (action == FileAction.RenamedNewName) { |
612 |
if (action == FileAction.RenamedNewName) { |
594 |
string newName = newPath.Substring (fullPathNoLastSlash.Length + 1); |
613 |
string newName = (newPath.Length > fullPathNoLastSlash.Length) ? newPath.Substring (fullPathNoLastSlash.Length + 1) : String.Empty; |
595 |
renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name); |
614 |
renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name); |
|
|
615 |
} else if (action == FileAction.RenamedOldName) { |
616 |
renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, null, name); |
596 |
} |
617 |
} |
597 |
|
618 |
|
598 |
fsw.DispatchEvents (action, name, ref renamed); |
619 |
fsw.DispatchEvents (action, name, ref renamed); |
Lines 624-629
private string GetFilenameFromFd (int fd)
Link Here
|
624 |
const int O_EVTONLY = 0x8000; |
645 |
const int O_EVTONLY = 0x8000; |
625 |
const int F_GETPATH = 50; |
646 |
const int F_GETPATH = 50; |
626 |
const int __DARWIN_MAXPATHLEN = 1024; |
647 |
const int __DARWIN_MAXPATHLEN = 1024; |
|
|
648 |
const int EINTR = 4; |
627 |
static readonly kevent[] emptyEventList = new System.IO.kevent[0]; |
649 |
static readonly kevent[] emptyEventList = new System.IO.kevent[0]; |
628 |
const int maxFds = 200; |
650 |
const int maxFds = 200; |
629 |
|
651 |
|
Lines 643-664
private string GetFilenameFromFd (int fd)
Link Here
|
643 |
string fixupPath = null; |
665 |
string fixupPath = null; |
644 |
string fullPathNoLastSlash = null; |
666 |
string fullPathNoLastSlash = null; |
645 |
|
667 |
|
646 |
[DllImport ("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)] |
668 |
[DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)] |
647 |
static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb); |
669 |
static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb); |
648 |
|
670 |
|
649 |
[DllImport ("libc")] |
671 |
[DllImport ("libc", SetLastError=true)] |
650 |
extern static int open (string path, int flags, int mode_t); |
672 |
extern static int open (string path, int flags, int mode_t); |
651 |
|
673 |
|
|
|
674 |
[DllImport ("libc", CharSet=CharSet.Auto, SetLastError=true)] |
675 |
static extern IntPtr realpath (string pathname, StringBuilder sb); |
676 |
|
652 |
[DllImport ("libc")] |
677 |
[DllImport ("libc")] |
653 |
extern static int close (int fd); |
678 |
extern static int close (int fd); |
654 |
|
679 |
|
655 |
[DllImport ("libc")] |
680 |
[DllImport ("libc", SetLastError=true)] |
656 |
extern static int kqueue (); |
681 |
extern static int kqueue (); |
657 |
|
682 |
|
658 |
[DllImport ("libc")] |
683 |
[DllImport ("libc", SetLastError=true)] |
659 |
extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time); |
684 |
extern static int kevent (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, [In] ref timespec time); |
660 |
|
685 |
|
661 |
[DllImport ("libc", EntryPoint="kevent")] |
686 |
[DllImport ("libc", EntryPoint="kevent", SetLastError=true)] |
662 |
extern static int kevent_notimeout (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr ptr); |
687 |
extern static int kevent_notimeout (int kq, [In]kevent[] ev, int nchanges, [Out]kevent[] evtlist, int nevents, IntPtr ptr); |
663 |
} |
688 |
} |
664 |
|
689 |
|