startedEvent.WaitOne ();
- if (failedInit) {
+ if (monitorExc != null) {
thread.Join ();
CleanUp ();
- throw new IOException ("Monitor thread failed while initializing.");
+ throw monitorExc;
}
else
started = true;
CleanUp ();
started = false;
+
+ if (monitorExc != null)
+ throw monitorExc;
}
}
void DoMonitor ()
{
- Exception exc = null;
- failedInit = false;
+ monitorExc = null;
try {
Setup ();
} catch (Exception e) {
- failedInit = true;
- exc = e;
+ monitorExc = e;
} finally {
startedEvent.Set ();
}
- if (failedInit) {
- fsw.OnError (new ErrorEventArgs (exc));
+ if (monitorExc != null)
return;
- }
try {
Monitor ();
} catch (Exception e) {
- exc = e;
+ monitorExc = e;
} finally {
if (!requestStop) { // failure
CleanUp ();
started = false;
+ if (monitorExc != null)
+ throw monitorExc;
}
- if (exc != null)
- fsw.OnError (new ErrorEventArgs (exc));
+
}
}
{
var initialFds = new List<int> ();
+ // fsw.FullPath may end in '/', see https://bugzilla.xamarin.com/show_bug.cgi?id=5747
+ if (fsw.FullPath.EndsWith ("/", StringComparison.Ordinal))
+ fullPathNoLastSlash = fsw.FullPath.Substring (0, fsw.FullPath.Length - 1);
+ else
+ fullPathNoLastSlash = fsw.FullPath;
+
// GetFilenameFromFd() returns the *realpath* which can be different than fsw.FullPath because symlinks.
// If so, introduce a fixup step.
- int fd = open (fsw.FullPath, O_EVTONLY, 0);
+ int fd = open (fullPathNoLastSlash, O_EVTONLY, 0);
var resolvedFullPath = GetFilenameFromFd (fd);
close (fd);
- if (resolvedFullPath != fsw.FullPath)
+ if (resolvedFullPath != fullPathNoLastSlash)
fixupPath = resolvedFullPath;
else
fixupPath = null;
- Scan (fsw.FullPath, false, ref initialFds);
+ Scan (fullPathNoLastSlash, false, ref initialFds);
var immediate_timeout = new timespec { tv_sec = (IntPtr)0, tv_usec = (IntPtr)0 };
var eventBuffer = new kevent[0]; // we don't want to take any events from the queue at this point
List<PathData> removeQueue = new List<PathData> ();
List<string> rescanQueue = new List<string> ();
+ int retries = 0;
+
while (!requestStop) {
var changes = CreateChangeList (ref newFds);
int numEvents = kevent (conn, changes, changes.Length, eventBuffer, eventBuffer.Length, ref timeout);
if (numEvents == -1) {
- var errMsg = String.Format ("kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ());
- fsw.OnError (new ErrorEventArgs (new IOException (errMsg)));
- }
+ if (++retries == 3)
+ throw new IOException (String.Format (
+ "persistent kevent() error, error code = '{0}'", Marshal.GetLastWin32Error ()));
- if (numEvents == 0)
continue;
+ }
+
+ retries = 0;
for (var i = 0; i < numEvents; i++) {
var kevt = eventBuffer [i];
fsw.OnError (new ErrorEventArgs (new IOException (errMsg)));
continue;
}
-
- if ((kevt.fflags & FilterFlags.VNodeDelete) == FilterFlags.VNodeDelete || (kevt.fflags & FilterFlags.VNodeRevoke) == FilterFlags.VNodeRevoke)
+
+ if ((kevt.fflags & FilterFlags.VNodeDelete) == FilterFlags.VNodeDelete || (kevt.fflags & FilterFlags.VNodeRevoke) == FilterFlags.VNodeRevoke) {
removeQueue.Add (pathData);
+ continue;
+ }
- else if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) {
- if (pathData.IsDirectory)
+ if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) {
+ UpdatePath (pathData);
+ }
+
+ if ((kevt.fflags & FilterFlags.VNodeWrite) == FilterFlags.VNodeWrite) {
+ if (pathData.IsDirectory) //TODO: Check if dirs trigger Changed events on .NET
rescanQueue.Add (pathData.Path);
else
PostEvent (FileAction.Modified, pathData.Path);
- }
-
- else if ((kevt.fflags & FilterFlags.VNodeRename) == FilterFlags.VNodeRename) {
- var newFilename = GetFilenameFromFd (pathData.Fd);
-
- if (newFilename.StartsWith (fsw.FullPath))
- Rename (pathData, newFilename);
- else //moved outside of our watched dir so stop watching
- RemoveTree (pathData);
- }
-
- else if ((kevt.fflags & FilterFlags.VNodeAttrib) == FilterFlags.VNodeAttrib || (kevt.fflags & FilterFlags.VNodeExtend) == FilterFlags.VNodeExtend)
+ }
+
+ if ((kevt.fflags & FilterFlags.VNodeAttrib) == FilterFlags.VNodeAttrib || (kevt.fflags & FilterFlags.VNodeExtend) == FilterFlags.VNodeExtend)
PostEvent (FileAction.Modified, pathData.Path);
}
if (pathData != null)
return pathData;
+ if (fdsDict.Count >= maxFds)
+ throw new IOException ("kqueue() FileSystemWatcher has reached the maximum nunmber of files to watch.");
+
var fd = open (path, O_EVTONLY, 0);
if (fd == -1) {
toRemove.ForEach (Remove);
}
- void Rename (PathData pathData, string newRoot)
+ void UpdatePath (PathData pathData)
{
+ var newRoot = GetFilenameFromFd (pathData.Fd);
+ if (!newRoot.StartsWith (fullPathNoLastSlash)) { // moved outside of our watched path (so stop observing it)
+ RemoveTree (pathData);
+ return;
+ }
+
var toRename = new List<PathData> ();
var oldRoot = pathData.Path;
toRename.Add (pathData);
- if (pathData.IsDirectory) {
+ if (pathData.IsDirectory) { // anything under the directory must have their paths updated
var prefix = oldRoot + Path.DirectorySeparatorChar;
foreach (var path in pathsDict.Keys)
if (path.StartsWith (prefix))
toRename.Add (pathsDict [path]);
}
-
- toRename.ForEach ((pd) => {
- var oldPath = pd.Path;
+
+ foreach (var renaming in toRename) {
+ var oldPath = renaming.Path;
var newPath = newRoot + oldPath.Substring (oldRoot.Length);
- pd.Path = newPath;
+
+ renaming.Path = newPath;
pathsDict.Remove (oldPath);
- pathsDict.Add (newPath, pd);
- });
+ // destination may exist in our records from a Created event, take care of it
+ if (pathsDict.ContainsKey (newPath)) {
+ var conflict = pathsDict [newPath];
+ if (GetFilenameFromFd (renaming.Fd) == GetFilenameFromFd (conflict.Fd))
+ Remove (conflict);
+ else
+ UpdatePath (conflict);
+ }
+
+ pathsDict.Add (newPath, renaming);
+ }
+
PostEvent (FileAction.RenamedNewName, oldRoot, newRoot);
}
if (action == 0)
return;
+ // e.Name
+ string name = path.Substring (fullPathNoLastSlash.Length + 1);
+
// only post events that match filter pattern. check both old and new paths for renames
- if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath)))
+ if (!fsw.Pattern.IsMatch (path) && (newPath == null || !fsw.Pattern.IsMatch (newPath)))
return;
- if (action == FileAction.RenamedNewName)
- renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, "", newPath, path);
+ if (action == FileAction.RenamedNewName) {
+ string newName = newPath.Substring (fullPathNoLastSlash.Length + 1);
+ renamed = new RenamedEventArgs (WatcherChangeTypes.Renamed, fsw.Path, newName, name);
+ }
lock (fsw) {
- fsw.DispatchEvents (action, path, ref renamed);
+ fsw.DispatchEvents (action, name, ref renamed);
if (fsw.Waiting) {
fsw.Waiting = false;
var sb = new StringBuilder (__DARWIN_MAXPATHLEN);
if (fcntl (fd, F_GETPATH, sb) != -1) {
- if (fixupPath != null)
- sb.Replace (fixupPath, fsw.FullPath, 0, fixupPath.Length); // see Setup()
+ if (fixupPath != null)
+ sb.Replace (fixupPath, fullPathNoLastSlash, 0, fixupPath.Length); // see Setup()
+
return sb.ToString ();
} else {
fsw.OnError (new ErrorEventArgs (new IOException (String.Format (
const int F_GETPATH = 50;
const int __DARWIN_MAXPATHLEN = 1024;
static readonly kevent[] emptyEventList = new System.IO.kevent[0];
+ const int maxFds = 200;
FileSystemWatcher fsw;
int conn;
volatile bool requestStop = false;
AutoResetEvent startedEvent = new AutoResetEvent (false);
bool started = false;
- bool failedInit = false;
+ Exception monitorExc;
object stateLock = new object ();
readonly Dictionary<string, PathData> pathsDict = new Dictionary<string, PathData> ();
readonly Dictionary<int, PathData> fdsDict = new Dictionary<int, PathData> ();
string fixupPath = null;
+ string fullPathNoLastSlash = null;
[DllImport ("libc", EntryPoint="fcntl", CharSet=CharSet.Auto, SetLastError=true)]
static extern int fcntl (int file_names_by_descriptor, int cmd, StringBuilder sb);