From 6b68e934f3f62fe2c23a78c30728c17dfc81fea5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 22 Jan 2017 02:01:13 +0100 Subject: [PATCH] Implement file support for the WinAPI driver. --- README.md | 2 +- source/eventcore/drivers/winapi.d | 210 +++++++++++++++++++++++++++--- 2 files changed, 192 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a034416..47a90c8 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ DNS | yes | yes | no | no Timers | yes | yes | yes | no Events | yes | yes | yes | no Signals | yes² | yes² | no | no -Files | yes | yes | no | no +Files | yes | yes | yes | no UI Integration | no | no | yes | no File watcher | yes² | yes² | yes | no diff --git a/source/eventcore/drivers/winapi.d b/source/eventcore/drivers/winapi.d index 025f643..1daa9d7 100644 --- a/source/eventcore/drivers/winapi.d +++ b/source/eventcore/drivers/winapi.d @@ -20,10 +20,10 @@ 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 { private { WinAPIEventDriverCore m_core; @@ -52,7 +52,7 @@ final class WinAPIEventDriver : EventDriver { m_timers = new LoopTimeoutTimerDriver(); m_core = new WinAPIEventDriverCore(m_timers); m_events = new WinAPIEventDriverEvents(m_core); - m_files = new WinAPIEventDriverFiles(); + m_files = new WinAPIEventDriverFiles(m_core); m_sockets = new WinAPIEventDriverSockets(); m_dns = new WinAPIEventDriverDNS(); m_watchers = new WinAPIEventDriverWatchers(m_core); @@ -358,58 +358,200 @@ final class WinAPIEventDriverDNS : EventDriverDNS { final class WinAPIEventDriverFiles : EventDriverFiles { -@safe: /*@nogc:*/ nothrow: +@safe /*@nogc*/ nothrow: + private { + WinAPIEventDriverCore m_core; + } + + this(WinAPIEventDriverCore core) + { + m_core = core; + } + override FileFD open(string path, FileOpenMode mode) { - assert(false, "TODO!"); + import std.utf : toUTF16z; + + auto access = mode == FileOpenMode.readWrite || mode == FileOpenMode.createTrunc ? (GENERIC_WRITE | GENERIC_READ) : + mode == FileOpenMode.append ? GENERIC_WRITE : GENERIC_READ; + auto shareMode = mode == FileOpenMode.read ? FILE_SHARE_READ : 0; + auto creation = mode == FileOpenMode.createTrunc ? CREATE_ALWAYS : mode == FileOpenMode.append? OPEN_ALWAYS : OPEN_EXISTING; + + auto handle = () @trusted { + scope (failure) assert(false); + return CreateFileW(path.toUTF16z, access, shareMode, null, creation, + FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, null); + } (); + auto errorcode = GetLastError(); + if (handle == INVALID_HANDLE_VALUE) + return FileFD.invalid; + + if (mode == FileOpenMode.createTrunc && errorcode == ERROR_ALREADY_EXISTS) { + BOOL ret = SetEndOfFile(handle); + if (!ret) { + CloseHandle(handle); + return FileFD.init; + } + } + + return adopt(cast(int)handle); } override FileFD adopt(int system_handle) { - assert(false, "TODO!"); + auto handle = () @trusted { return cast(HANDLE)system_handle; } (); + DWORD f; + if (!() @trusted { return GetHandleInformation(handle, &f); } ()) + return FileFD.init; + + auto s = &m_core.setupSlot!FileSlot(handle); + s.read.handle = s.write.handle = handle; + + return FileFD(system_handle); } override void close(FileFD file) { - assert(false, "TODO!"); + auto h = idToHandle(file); + auto slot = &m_core.m_handles[h].file(); + if (slot.read.handle != INVALID_HANDLE_VALUE) { + CloseHandle(h); + slot.read.handle = slot.write.handle = INVALID_HANDLE_VALUE; + } } override ulong getSize(FileFD file) { - assert(false, "TODO!"); + LARGE_INTEGER size; + auto succeeded = () @trusted { return GetFileSizeEx(idToHandle(file), &size); } (); + if (!succeeded || size.QuadPart < 0) + return ulong.max; + return size.QuadPart; } override void write(FileFD file, ulong offset, const(ubyte)[] buffer, IOMode mode, FileIOCallback on_write_finish) { - assert(false, "TODO!"); + if (!buffer.length) { + on_write_finish(file, IOStatus.ok, 0); + return; + } + + auto h = idToHandle(file); + auto slot = &m_core.m_handles[h].file.write; + slot.bytesTransferred = 0; + slot.offset = offset; + slot.buffer = buffer; + slot.mode = mode; + slot.callback = on_write_finish; + startIO!(WriteFileEx, true)(h, slot); } override void read(FileFD file, ulong offset, ubyte[] buffer, IOMode mode, FileIOCallback on_read_finish) { - assert(false, "TODO!"); + if (!buffer.length) { + on_read_finish(file, IOStatus.ok, 0); + return; + } + + auto h = idToHandle(file); + auto slot = &m_core.m_handles[h].file.read; + slot.bytesTransferred = 0; + slot.offset = offset; + slot.buffer = buffer; + slot.mode = mode; + slot.callback = on_read_finish; + startIO!(ReadFileEx, false)(h, slot); } override void cancelWrite(FileFD file) { - assert(false, "TODO!"); + auto h = idToHandle(file); + cancelIO!true(h, m_core.m_handles[h].file.write); } override void cancelRead(FileFD file) { - assert(false, "TODO!"); + auto h = idToHandle(file); + cancelIO!false(h, m_core.m_handles[h].file.read); } override void addRef(FileFD descriptor) { - assert(false, "TODO!"); + m_core.m_handles[idToHandle(descriptor)].addRef(); } override bool releaseRef(FileFD descriptor) { - assert(false, "TODO!"); + auto h = idToHandle(descriptor); + return m_core.m_handles[h].releaseRef({ + close(descriptor); + m_core.freeSlot(h); + }); + } + + private static void startIO(alias fun, bool RO)(HANDLE h, FileSlot.Direction!RO* slot) + { + import std.algorithm.comparison : min; + + with (slot.overlapped) { + Internal = 0; + InternalHigh = 0; + Offset = cast(uint)(slot.offset & 0xFFFFFFFF); + OffsetHigh = cast(uint)(slot.offset >> 32); + hEvent = () @trusted { return cast(HANDLE)slot; } (); + } + + auto nbytes = min(slot.buffer.length, DWORD.max); + if (!() @trusted { return fun(h, &slot.buffer[0], nbytes, &slot.overlapped, &onIOFinished!(fun, RO)); } ()) { + slot.invokeCallback(IOStatus.error, slot.bytesTransferred); + } + } + + private static void cancelIO(bool RO)(HANDLE h, ref FileSlot.Direction!RO slot) + { + if (slot.callback) { + //CancelIoEx(h, &slot.overlapped); // FIXME: currently causes linker errors for DMD due to outdated kernel32.lib files + slot.callback = null; + slot.buffer = null; + } + } + + private static extern(Windows) + void onIOFinished(alias fun, bool RO)(DWORD error, DWORD bytes_transferred, OVERLAPPED* overlapped) + { + + auto slot = () @trusted { return cast(FileSlot.Direction!RO*)overlapped.hEvent; } (); + assert(slot !is null); + HANDLE h = slot.handle; + auto id = FileFD(cast(int)h); + + if (!slot.callback) { + // request was already cancelled + return; + } + + if (error != 0) { + slot.invokeCallback(IOStatus.error, slot.bytesTransferred + bytes_transferred); + return; + } + + slot.bytesTransferred += bytes_transferred; + slot.offset += bytes_transferred; + + if (slot.bytesTransferred >= slot.buffer.length || slot.mode != IOMode.all) { + slot.invokeCallback(IOStatus.ok, slot.bytesTransferred); + } else { + startIO!(fun, RO)(h, slot); + } + } + + private static HANDLE idToHandle(FileFD id) + @trusted { + return cast(HANDLE)cast(int)id; } } + final class WinAPIEventDriverEvents : EventDriverEvents { @safe: /*@nogc:*/ nothrow: private { @@ -656,18 +798,15 @@ final class WinAPIEventDriverWatchers : EventDriverWatchers { override bool releaseRef(WatcherID descriptor) { - bool freed; auto handle = idToHandle(descriptor); - m_core.m_handles[handle].releaseRef(()nothrow{ + return 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) @@ -747,6 +886,7 @@ private long currStdTime() private struct HandleSlot { static union SpecificTypes { typeof(null) none; + FileSlot files; WatcherSlot watcher; } int refCount; @@ -754,6 +894,7 @@ private struct HandleSlot { @safe nothrow: + @property ref FileSlot file() { return specific.get!FileSlot; } @property ref WatcherSlot watcher() { return specific.get!WatcherSlot; } void addRef() @@ -762,14 +903,40 @@ private struct HandleSlot { refCount++; } - void releaseRef(scope void delegate() @safe nothrow on_free) + bool releaseRef(scope void delegate() @safe nothrow on_free) { assert(refCount > 0); - if (--refCount == 0) + if (--refCount == 0) { on_free(); + return false; + } + return true; } } +private struct FileSlot { + static struct Direction(bool RO) { + OVERLAPPED overlapped; + FileIOCallback callback; + ulong offset; + size_t bytesTransferred; + IOMode mode; + static if (RO) const(ubyte)[] buffer; + else ubyte[] buffer; + HANDLE handle; // set to INVALID_HANDLE_VALUE when closed + + void invokeCallback(IOStatus status, size_t bytes_transferred) + @safe nothrow { + auto cb = this.callback; + this.callback = null; + assert(cb !is null); + cb(FileFD(cast(int)this.handle), status, bytes_transferred); + } + } + Direction!false read; + Direction!true write; +} + private struct WatcherSlot { ubyte[] buffer; OVERLAPPED overlapped; @@ -777,3 +944,8 @@ private struct WatcherSlot { bool recursive; FileChangesCallback callback; } + +private extern(Windows) @trusted nothrow @nogc { + BOOL SetEndOfFile(HANDLE hFile); + BOOL CancelIoEx(HANDLE hFile, OVERLAPPED* lpOverlapped); +}