Implement directory watchers for the WinAPI driver.

This commit is contained in:
Sönke Ludwig 2017-01-21 21:10:17 +01:00
parent 4c7281dd01
commit f7ec3da756
2 changed files with 188 additions and 9 deletions

View file

@ -28,12 +28,12 @@ TCP Sockets | yes | yes | no | no
UDP Sockets | yes | yes | no | no UDP Sockets | yes | yes | no | no
USDS | yes | yes | no | no USDS | yes | yes | no | no
DNS | yes | yes | no | no DNS | yes | yes | no | no
Timers | yes | yes | no | no Timers | yes | yes | yes | no
Events | yes | yes | no | no Events | yes | yes | no | no
Signals | yes² | yes² | no | no Signals | yes² | yes² | no | no
Files | yes | yes | no | no Files | yes | yes | no | no
UI Integration | no | no | no | no UI Integration | no | no | yes | no
File watcher | yes² | yes² | no | no File watcher | yes² | yes² | yes | no
² Currently only supported on Linux ² Currently only supported on Linux

View file

@ -11,12 +11,17 @@ version (Windows):
import eventcore.driver; import eventcore.driver;
import eventcore.drivers.timer; import eventcore.drivers.timer;
import std.socket : Address; import taggedalgebraic;
import core.time : Duration;
import core.sys.windows.windows; import core.sys.windows.windows;
import core.sys.windows.winsock2; import core.sys.windows.winsock2;
import core.time : Duration;
import std.experimental.allocator;
import std.socket : Address;
static assert(HANDLE.sizeof <= FD.BaseType.sizeof);
static assert(FD(cast(int)INVALID_HANDLE_VALUE) == FD.init);
final class WinAPIEventDriver : EventDriver { final class WinAPIEventDriver : EventDriver {
private { private {
WinAPIEventDriverCore m_core; WinAPIEventDriverCore m_core;
@ -29,8 +34,13 @@ final class WinAPIEventDriver : EventDriver {
WinAPIEventDriverWatchers m_watchers; WinAPIEventDriverWatchers m_watchers;
} }
static WinAPIEventDriver threadInstance;
this() this()
@safe { @safe {
assert(threadInstance is null);
threadInstance = this;
import std.exception : enforce; import std.exception : enforce;
WSADATA wd; WSADATA wd;
@ -43,7 +53,7 @@ final class WinAPIEventDriver : EventDriver {
m_files = new WinAPIEventDriverFiles(); m_files = new WinAPIEventDriverFiles();
m_sockets = new WinAPIEventDriverSockets(); m_sockets = new WinAPIEventDriverSockets();
m_dns = new WinAPIEventDriverDNS(); m_dns = new WinAPIEventDriverDNS();
m_watchers = new WinAPIEventDriverWatchers(); m_watchers = new WinAPIEventDriverWatchers(m_core);
} }
@safe: /*@nogc:*/ nothrow: @safe: /*@nogc:*/ nothrow:
@ -60,6 +70,8 @@ final class WinAPIEventDriver : EventDriver {
override void dispose() override void dispose()
{ {
assert(threadInstance !is null);
threadInstance = null;
} }
} }
@ -72,6 +84,8 @@ final class WinAPIEventDriverCore : EventDriverCore {
LoopTimeoutTimerDriver m_timers; LoopTimeoutTimerDriver m_timers;
HANDLE[] m_registeredEvents; HANDLE[] m_registeredEvents;
HANDLE m_fileCompletionEvent; HANDLE m_fileCompletionEvent;
HandleSlot[HANDLE] m_handles; // FIXME: use allocator based hash map
} }
this(LoopTimeoutTimerDriver timers) this(LoopTimeoutTimerDriver timers)
@ -187,6 +201,22 @@ final class WinAPIEventDriverCore : EventDriverCore {
return got_event; return got_event;
} }
private ref SlotType setupSlot(SlotType)(HANDLE h)
{
assert(h !in m_handles, "Handle already in use.");
HandleSlot s;
s.refCount = 1;
s.specific = SlotType.init;
m_handles[h] = s;
return m_handles[h].specific.get!SlotType;
}
private void freeSlot(HANDLE h)
{
assert(h in m_handles, "Handle not in use - cannot free.");
m_handles.remove(h);
}
} }
final class WinAPIEventDriverSockets : EventDriverSockets { final class WinAPIEventDriverSockets : EventDriverSockets {
@ -471,20 +501,135 @@ final class WinAPIEventDriverTimers : EventDriverTimers {
final class WinAPIEventDriverWatchers : EventDriverWatchers { final class WinAPIEventDriverWatchers : EventDriverWatchers {
@safe: /*@nogc:*/ nothrow: @safe: /*@nogc:*/ nothrow:
private {
WinAPIEventDriverCore m_core;
}
this(WinAPIEventDriverCore core)
{
m_core = core;
}
override WatcherID watchDirectory(string path, bool recursive, FileChangesCallback callback) override WatcherID watchDirectory(string path, bool recursive, FileChangesCallback callback)
{ {
assert(false, "TODO!"); import std.utf : toUTF16z;
auto handle = () @trusted {
scope (failure) assert(false);
return CreateFileW(path.toUTF16z, FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
null, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
null);
} ();
if (handle == INVALID_HANDLE_VALUE)
return WatcherID.invalid;
auto id = WatcherID(cast(int)handle);
auto slot = &m_core.setupSlot!WatcherSlot(handle);
slot.directory = path;
slot.recursive = recursive;
slot.callback = callback;
slot.buffer = () @trusted {
try return theAllocator.makeArray!ubyte(16384);
catch (Exception e) assert(false, "Failed to allocate directory watcher buffer.");
} ();
if (!triggerRead(handle, *slot)) {
releaseRef(id);
return WatcherID.invalid;
}
return id;
} }
override void addRef(WatcherID descriptor) override void addRef(WatcherID descriptor)
{ {
assert(false, "TODO!"); m_core.m_handles[idToHandle(descriptor)].addRef();
} }
override bool releaseRef(WatcherID descriptor) override bool releaseRef(WatcherID descriptor)
{ {
assert(false, "TODO!"); bool freed;
auto handle = idToHandle(descriptor);
m_core.m_handles[handle].releaseRef(()nothrow{
CloseHandle(handle);
() @trusted {
try theAllocator.dispose(m_core.m_handles[handle].watcher.buffer);
catch (Exception e) assert(false, "Freeing directory watcher buffer failed.");
} ();
m_core.freeSlot(handle);
freed = true;
});
return !freed;
} }
private static nothrow extern(System)
void onIOCompleted(DWORD dwError, DWORD cbTransferred, OVERLAPPED* overlapped)
{
import std.conv : to;
auto handle = overlapped.hEvent; // *file* handle
auto id = WatcherID(cast(int)handle);
auto slot = &WinAPIEventDriver.threadInstance.core.m_handles[handle].watcher();
if (dwError != 0) {
// FIXME: this must be propagated to the caller
//logWarn("Failed to read directory changes: %s", dwError);
return;
}
ubyte[] result = slot.buffer[0 .. cbTransferred];
do {
assert(result.length >= FILE_NOTIFY_INFORMATION.sizeof);
auto fni = () @trusted { return cast(FILE_NOTIFY_INFORMATION*)result.ptr; } ();
FileChange ch;
switch (fni.Action) {
default: ch.kind = FileChangeKind.modified; break;
case 0x1: ch.kind = FileChangeKind.added; break;
case 0x2: ch.kind = FileChangeKind.removed; break;
case 0x3: ch.kind = FileChangeKind.modified; break;
case 0x4: ch.kind = FileChangeKind.removed; break;
case 0x5: ch.kind = FileChangeKind.added; break;
}
ch.directory = slot.directory;
ch.isDirectory = false; // FIXME: is this right?
ch.name = () @trusted { scope (failure) assert(false); return to!string(fni.FileName[0 .. fni.FileNameLength/2]); } ();
slot.callback(id, ch);
if (fni.NextEntryOffset == 0) break;
result = result[fni.NextEntryOffset .. $];
} while (result.length > 0);
triggerRead(handle, *slot);
}
private static bool triggerRead(HANDLE handle, ref WatcherSlot slot)
{
enum UINT notifications = FILE_NOTIFY_CHANGE_FILE_NAME|
FILE_NOTIFY_CHANGE_DIR_NAME|FILE_NOTIFY_CHANGE_SIZE|
FILE_NOTIFY_CHANGE_LAST_WRITE;
slot.overlapped.Internal = 0;
slot.overlapped.InternalHigh = 0;
slot.overlapped.Offset = 0;
slot.overlapped.OffsetHigh = 0;
slot.overlapped.hEvent = handle;
BOOL ret;
() @trusted {
ret = ReadDirectoryChangesW(handle, slot.buffer.ptr, slot.buffer.length, slot.recursive,
notifications, null, &slot.overlapped, &onIOCompleted);
} ();
if (!ret) {
//logError("Failed to read directory changes in '%s'", m_path);
return false;
}
return true;
}
static private HANDLE idToHandle(WatcherID id) @trusted { return cast(HANDLE)cast(int)id; }
} }
private long currStdTime() private long currStdTime()
@ -493,3 +638,37 @@ private long currStdTime()
scope (failure) assert(false); scope (failure) assert(false);
return Clock.currStdTime; return Clock.currStdTime;
} }
private struct HandleSlot {
static union SpecificTypes {
typeof(null) none;
WatcherSlot watcher;
}
int refCount;
TaggedAlgebraic!SpecificTypes specific;
@safe nothrow:
@property ref WatcherSlot watcher() { return specific.get!WatcherSlot; }
void addRef()
{
assert(refCount > 0);
refCount++;
}
void releaseRef(scope void delegate() @safe nothrow on_free)
{
assert(refCount > 0);
if (--refCount == 0)
on_free();
}
}
private struct WatcherSlot {
ubyte[] buffer;
OVERLAPPED overlapped;
string directory;
bool recursive;
FileChangesCallback callback;
}