a575963da9
Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
462 lines
13 KiB
C#
462 lines
13 KiB
C#
//
|
|
// System.IO.KeventWatcher.cs: interface with osx kevent
|
|
//
|
|
// Authors:
|
|
// Geoff Norton (gnorton@customerdna.com)
|
|
//
|
|
// (c) 2004 Geoff Norton
|
|
// Copyright 2014 Xamarin Inc
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining
|
|
// a copy of this software and associated documentation files (the
|
|
// "Software"), to deal in the Software without restriction, including
|
|
// without limitation the rights to use, copy, modify, merge, publish,
|
|
// distribute, sublicense, and/or sell copies of the Software, and to
|
|
// permit persons to whom the Software is furnished to do so, subject to
|
|
// the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be
|
|
// included in all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.ComponentModel;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace System.IO {
|
|
|
|
[Flags]
|
|
enum EventFlags : ushort {
|
|
Add = 0x0001,
|
|
Delete = 0x0002,
|
|
Enable = 0x0004,
|
|
Disable = 0x0008,
|
|
OneShot = 0x0010,
|
|
Clear = 0x0020,
|
|
Receipt = 0x0040,
|
|
Dispatch = 0x0080,
|
|
|
|
Flag0 = 0x1000,
|
|
Flag1 = 0x2000,
|
|
SystemFlags = unchecked (0xf000),
|
|
|
|
// Return values.
|
|
EOF = 0x8000,
|
|
Error = 0x4000,
|
|
}
|
|
|
|
enum EventFilter : short {
|
|
Read = -1,
|
|
Write = -2,
|
|
Aio = -3,
|
|
Vnode = -4,
|
|
Proc = -5,
|
|
Signal = -6,
|
|
Timer = -7,
|
|
MachPort = -8,
|
|
FS = -9,
|
|
User = -10,
|
|
VM = -11
|
|
}
|
|
|
|
enum FilterFlags : uint {
|
|
ReadPoll = EventFlags.Flag0,
|
|
ReadOutOfBand = EventFlags.Flag1,
|
|
ReadLowWaterMark = 0x00000001,
|
|
|
|
WriteLowWaterMark = ReadLowWaterMark,
|
|
|
|
NoteTrigger = 0x01000000,
|
|
NoteFFNop = 0x00000000,
|
|
NoteFFAnd = 0x40000000,
|
|
NoteFFOr = 0x80000000,
|
|
NoteFFCopy = 0xc0000000,
|
|
NoteFFCtrlMask = 0xc0000000,
|
|
NoteFFlagsMask = 0x00ffffff,
|
|
|
|
VNodeDelete = 0x00000001,
|
|
VNodeWrite = 0x00000002,
|
|
VNodeExtend = 0x00000004,
|
|
VNodeAttrib = 0x00000008,
|
|
VNodeLink = 0x00000010,
|
|
VNodeRename = 0x00000020,
|
|
VNodeRevoke = 0x00000040,
|
|
VNodeNone = 0x00000080,
|
|
|
|
ProcExit = 0x80000000,
|
|
ProcFork = 0x40000000,
|
|
ProcExec = 0x20000000,
|
|
ProcReap = 0x10000000,
|
|
ProcSignal = 0x08000000,
|
|
ProcExitStatus = 0x04000000,
|
|
ProcResourceEnd = 0x02000000,
|
|
|
|
// iOS only
|
|
ProcAppactive = 0x00800000,
|
|
ProcAppBackground = 0x00400000,
|
|
ProcAppNonUI = 0x00200000,
|
|
ProcAppInactive = 0x00100000,
|
|
ProcAppAllStates = 0x00f00000,
|
|
|
|
// Masks
|
|
ProcPDataMask = 0x000fffff,
|
|
ProcControlMask = 0xfff00000,
|
|
|
|
VMPressure = 0x80000000,
|
|
VMPressureTerminate = 0x40000000,
|
|
VMPressureSuddenTerminate = 0x20000000,
|
|
VMError = 0x10000000,
|
|
TimerSeconds = 0x00000001,
|
|
TimerMicroSeconds = 0x00000002,
|
|
TimerNanoSeconds = 0x00000004,
|
|
TimerAbsolute = 0x00000008,
|
|
}
|
|
|
|
struct kevent : IDisposable {
|
|
public int ident;
|
|
public EventFilter filter;
|
|
public EventFlags flags;
|
|
public FilterFlags fflags;
|
|
public int data;
|
|
public IntPtr udata;
|
|
|
|
public void Dispose ()
|
|
{
|
|
if (udata != IntPtr.Zero)
|
|
Marshal.FreeHGlobal (udata);
|
|
}
|
|
}
|
|
|
|
struct timespec {
|
|
public int tv_sec;
|
|
public int tv_usec;
|
|
}
|
|
|
|
class KeventFileData {
|
|
public FileSystemInfo fsi;
|
|
public DateTime LastAccessTime;
|
|
public DateTime LastWriteTime;
|
|
|
|
public KeventFileData(FileSystemInfo fsi, DateTime LastAccessTime, DateTime LastWriteTime) {
|
|
this.fsi = fsi;
|
|
this.LastAccessTime = LastAccessTime;
|
|
this.LastWriteTime = LastWriteTime;
|
|
}
|
|
}
|
|
|
|
class KeventData {
|
|
public FileSystemWatcher FSW;
|
|
public string Directory;
|
|
public string FileMask;
|
|
public bool IncludeSubdirs;
|
|
public bool Enabled;
|
|
public Hashtable DirEntries;
|
|
public kevent ev;
|
|
}
|
|
|
|
class KeventWatcher : IFileWatcher
|
|
{
|
|
static bool failed;
|
|
static KeventWatcher instance;
|
|
static Hashtable watches;
|
|
static Hashtable requests;
|
|
static Thread thread;
|
|
static int conn;
|
|
static bool stop;
|
|
|
|
private KeventWatcher ()
|
|
{
|
|
}
|
|
|
|
// Locked by caller
|
|
public static bool GetInstance (out IFileWatcher watcher)
|
|
{
|
|
if (failed == true) {
|
|
watcher = null;
|
|
return false;
|
|
}
|
|
|
|
if (instance != null) {
|
|
watcher = instance;
|
|
return true;
|
|
}
|
|
|
|
watches = Hashtable.Synchronized (new Hashtable ());
|
|
requests = Hashtable.Synchronized (new Hashtable ());
|
|
conn = kqueue();
|
|
if (conn == -1) {
|
|
failed = true;
|
|
watcher = null;
|
|
return false;
|
|
}
|
|
|
|
instance = new KeventWatcher ();
|
|
watcher = instance;
|
|
return true;
|
|
}
|
|
|
|
public void StartDispatching (FileSystemWatcher fsw)
|
|
{
|
|
KeventData data;
|
|
lock (this) {
|
|
if (thread == null) {
|
|
thread = new Thread (new ThreadStart (Monitor));
|
|
thread.IsBackground = true;
|
|
thread.Start ();
|
|
}
|
|
|
|
data = (KeventData) watches [fsw];
|
|
}
|
|
|
|
if (data == null) {
|
|
data = new KeventData ();
|
|
data.FSW = fsw;
|
|
data.Directory = fsw.FullPath;
|
|
data.FileMask = fsw.MangledFilter;
|
|
data.IncludeSubdirs = fsw.IncludeSubdirectories;
|
|
|
|
data.Enabled = true;
|
|
lock (this) {
|
|
StartMonitoringDirectory (data);
|
|
watches [fsw] = data;
|
|
stop = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void StartMonitoringDirectory (KeventData data)
|
|
{
|
|
DirectoryInfo dir = new DirectoryInfo (data.Directory);
|
|
if(data.DirEntries == null) {
|
|
data.DirEntries = new Hashtable();
|
|
foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
|
|
data.DirEntries.Add(fsi.FullName, new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime));
|
|
}
|
|
|
|
int fd = open(data.Directory, 0, 0);
|
|
kevent ev = new kevent();
|
|
ev.udata = IntPtr.Zero;
|
|
timespec nullts = new timespec();
|
|
nullts.tv_sec = 0;
|
|
nullts.tv_usec = 0;
|
|
if (fd > 0) {
|
|
ev.ident = fd;
|
|
ev.filter = EventFilter.Vnode;
|
|
ev.flags = EventFlags.Add | EventFlags.Enable | EventFlags.OneShot;
|
|
ev.fflags = // 20 | 2 | 1 | 8;
|
|
FilterFlags.VNodeDelete |
|
|
FilterFlags.VNodeWrite |
|
|
FilterFlags.VNodeAttrib |
|
|
// The following two values are the equivalent of the original value "20", but we suspect the original author meant
|
|
// 0x20, we will review later with some test cases
|
|
FilterFlags.VNodeLink |
|
|
FilterFlags.VNodeExtend;
|
|
ev.data = 0;
|
|
ev.udata = Marshal.StringToHGlobalAuto (data.Directory);
|
|
kevent outev = new kevent();
|
|
outev.udata = IntPtr.Zero;
|
|
kevent (conn, ref ev, 1, ref outev, 0, ref nullts);
|
|
data.ev = ev;
|
|
requests [fd] = data;
|
|
}
|
|
|
|
if (!data.IncludeSubdirs)
|
|
return;
|
|
|
|
}
|
|
|
|
public void StopDispatching (FileSystemWatcher fsw)
|
|
{
|
|
KeventData data;
|
|
lock (this) {
|
|
data = (KeventData) watches [fsw];
|
|
if (data == null)
|
|
return;
|
|
|
|
StopMonitoringDirectory (data);
|
|
watches.Remove (fsw);
|
|
if (watches.Count == 0)
|
|
stop = true;
|
|
|
|
if (!data.IncludeSubdirs)
|
|
return;
|
|
|
|
}
|
|
}
|
|
|
|
static void StopMonitoringDirectory (KeventData data)
|
|
{
|
|
close(data.ev.ident);
|
|
}
|
|
|
|
void Monitor ()
|
|
{
|
|
|
|
while (!stop) {
|
|
kevent ev = new kevent();
|
|
ev.udata = IntPtr.Zero;
|
|
kevent nullev = new kevent();
|
|
nullev.udata = IntPtr.Zero;
|
|
timespec ts = new timespec();
|
|
ts.tv_sec = 0;
|
|
ts.tv_usec = 0;
|
|
int haveEvents;
|
|
lock (this) {
|
|
haveEvents = kevent (conn, ref nullev, 0, ref ev, 1, ref ts);
|
|
}
|
|
|
|
if (haveEvents > 0) {
|
|
// Restart monitoring
|
|
KeventData data = (KeventData) requests [ev.ident];
|
|
StopMonitoringDirectory (data);
|
|
StartMonitoringDirectory (data);
|
|
ProcessEvent (ev);
|
|
} else {
|
|
System.Threading.Thread.Sleep (500);
|
|
}
|
|
}
|
|
|
|
lock (this) {
|
|
thread = null;
|
|
stop = false;
|
|
}
|
|
}
|
|
|
|
void ProcessEvent (kevent ev)
|
|
{
|
|
lock (this) {
|
|
KeventData data = (KeventData) requests [ev.ident];
|
|
if (!data.Enabled)
|
|
return;
|
|
|
|
FileSystemWatcher fsw;
|
|
string filename = "";
|
|
|
|
fsw = data.FSW;
|
|
FileAction fa = 0;
|
|
DirectoryInfo dir = new DirectoryInfo (data.Directory);
|
|
FileSystemInfo changedFsi = null;
|
|
|
|
try {
|
|
foreach (FileSystemInfo fsi in dir.GetFileSystemInfos() )
|
|
if (data.DirEntries.ContainsKey (fsi.FullName) && (fsi is FileInfo)) {
|
|
KeventFileData entry = (KeventFileData) data.DirEntries [fsi.FullName];
|
|
if (entry.LastWriteTime != fsi.LastWriteTime) {
|
|
filename = fsi.Name;
|
|
fa = FileAction.Modified;
|
|
data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
|
|
if (fsw.IncludeSubdirectories && fsi is DirectoryInfo) {
|
|
data.Directory = filename;
|
|
requests [ev.ident] = data;
|
|
ProcessEvent(ev);
|
|
}
|
|
changedFsi = fsi;
|
|
PostEvent(filename, fsw, fa, changedFsi);
|
|
}
|
|
}
|
|
} catch (Exception) {
|
|
// The file system infos were changed while we processed them
|
|
}
|
|
// Deleted
|
|
try {
|
|
bool deleteMatched = true;
|
|
while(deleteMatched) {
|
|
foreach (KeventFileData entry in data.DirEntries.Values) {
|
|
if (!File.Exists (entry.fsi.FullName) && !Directory.Exists (entry.fsi.FullName)) {
|
|
filename = entry.fsi.Name;
|
|
fa = FileAction.Removed;
|
|
data.DirEntries.Remove (entry.fsi.FullName);
|
|
changedFsi = entry.fsi;
|
|
PostEvent(filename, fsw, fa, changedFsi);
|
|
break;
|
|
}
|
|
}
|
|
deleteMatched = false;
|
|
}
|
|
} catch (Exception) {
|
|
// The file system infos were changed while we processed them
|
|
}
|
|
// Added
|
|
try {
|
|
foreach (FileSystemInfo fsi in dir.GetFileSystemInfos())
|
|
if (!data.DirEntries.ContainsKey (fsi.FullName)) {
|
|
changedFsi = fsi;
|
|
filename = fsi.Name;
|
|
fa = FileAction.Added;
|
|
data.DirEntries [fsi.FullName] = new KeventFileData(fsi, fsi.LastAccessTime, fsi.LastWriteTime);
|
|
PostEvent(filename, fsw, fa, changedFsi);
|
|
}
|
|
} catch (Exception) {
|
|
// The file system infos were changed while we processed them
|
|
}
|
|
|
|
|
|
}
|
|
}
|
|
|
|
private void PostEvent (string filename, FileSystemWatcher fsw, FileAction fa, FileSystemInfo changedFsi) {
|
|
RenamedEventArgs renamed = null;
|
|
if (fa == 0)
|
|
return;
|
|
|
|
if (fsw.IncludeSubdirectories && fa == FileAction.Added) {
|
|
if (changedFsi is DirectoryInfo) {
|
|
KeventData newdirdata = new KeventData ();
|
|
newdirdata.FSW = fsw;
|
|
newdirdata.Directory = changedFsi.FullName;
|
|
newdirdata.FileMask = fsw.MangledFilter;
|
|
newdirdata.IncludeSubdirs = fsw.IncludeSubdirectories;
|
|
|
|
newdirdata.Enabled = true;
|
|
lock (this) {
|
|
StartMonitoringDirectory (newdirdata);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!fsw.Pattern.IsMatch(filename, true))
|
|
return;
|
|
|
|
lock (fsw) {
|
|
if (changedFsi.FullName.StartsWith (fsw.FullPath, StringComparison.Ordinal)) {
|
|
if (fsw.FullPath.EndsWith ("/", StringComparison.Ordinal)) {
|
|
filename = changedFsi.FullName.Substring (fsw.FullPath.Length);
|
|
} else {
|
|
filename = changedFsi.FullName.Substring (fsw.FullPath.Length + 1);
|
|
}
|
|
}
|
|
fsw.DispatchEvents (fa, filename, ref renamed);
|
|
if (fsw.Waiting) {
|
|
fsw.Waiting = false;
|
|
System.Threading.Monitor.PulseAll (fsw);
|
|
}
|
|
}
|
|
}
|
|
|
|
[DllImport ("libc")]
|
|
extern static int open(string path, int flags, int mode_t);
|
|
|
|
[DllImport ("libc")]
|
|
extern static int close(int fd);
|
|
|
|
[DllImport ("libc")]
|
|
extern static int kqueue();
|
|
|
|
[DllImport ("libc")]
|
|
extern static int kevent(int kqueue, ref kevent ev, int nchanges, ref kevent evtlist, int nevents, ref timespec ts);
|
|
}
|
|
}
|
|
|