Implement directory watchers for the WinAPI driver.
This commit is contained in:
parent
4c7281dd01
commit
f7ec3da756
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue