diff --git a/platform.d b/platform.d index 8494307..56bb095 100644 --- a/platform.d +++ b/platform.d @@ -1,14 +1,15 @@ module dlib.platform; import dlib.aliases; +import dlib.alloc; +import dlib.util; + import includes; import std.stdio; import core.memory; import core.thread.osthread; import core.time; -@nogc: - const WINDOW_EDGE_BUFFER = 50; enum Input @@ -45,6 +46,11 @@ version(linux) { import core.sys.posix.dlfcn; import core.sys.posix.sys.mman; +import core.sys.linux.sys.inotify; +import core.sys.linux.fcntl; +import core.sys.posix.unistd; + +import core.stdc.string : strlen; struct InputEvent { @@ -616,6 +622,291 @@ MemFree(void* ptr, u64 size) assert(munmap(ptr, size) == 0, "MemFree failure"); } +struct Watcher +{ + Arena arena; + u8[] buffer; + WatcherH handle; + WatchH dir_handle; + u8[] watched_dir; + bool blocking; +} + +alias WatcherH = int; +alias WatchH = int; + +enum WatchType +{ + None, + Access = IN_ACCESS, + Metadata = IN_ATTRIB, + Create = IN_CREATE, + Delete = IN_DELETE, + Modify = IN_MODIFY, + Moved = IN_MOVED_FROM | IN_MOVED_TO, +} + +alias WT = WatchType; + +Watcher +WatchDirectory(string dir, WatchType type, bool blocking = false) +{ + assert(dir.length > 0); + + Watcher watcher = { + arena: CreateArena(MB(4)), + buffer: AllocArray!(u8)(MB(1)), + blocking: blocking, + watched_dir: (cast(u8*)dir.ptr)[0 .. dir.length], + }; + + watcher.handle = inotify_init(); + assert(watcher.dir_handle >= 0, "WatchDirectory failure: unable to initialize"); + + if (!blocking) + { + fcntl(watcher.handle, F_SETFL, fcntl(watcher.handle, F_GETFL) | O_NONBLOCK); + } + + watcher.dir_handle = inotify_add_watch(watcher.handle, dir.ptr, IN_CREATE|IN_DELETE|IN_DELETE_SELF|IN_MODIFY|IN_MOVE_SELF|IN_MOVED_FROM|IN_MOVED_TO|IN_ATTRIB|IN_EXCL_UNLINK); + + return watcher; +} + +WatchEvent[] +ViewChanges(Watcher* watcher) +{ + assert(watcher.handle >= 0 && watcher.dir_handle >= 0, "ViewChanges failure: handles are not valid"); + + Reset(&watcher.arena); + + WatchEvent[] events; + + i64 length = read(watcher.handle, watcher.buffer.ptr, watcher.buffer.length); + if (length > 0) + { + i64 count = 0; + i64 i = 0; + while(i < length) + { + inotify_event* event = (cast(inotify_event*)(watcher.buffer.ptr + i)); + count += 1; + + assert(event.wd == watcher.dir_handle); + + i += inotify_event.sizeof + event.len; + } + + if (count > 0) + { + struct Moved + { + u32 cookie; + i64 to; + i64 from; + } + + Moved[] moved = AllocArray!(Moved)(&watcher.arena, (count/2)+1); + i64 m_count = 0; + events = AllocArray!(WatchEvent)(&watcher.arena, count); + count = 0; + i = 0; + while (i < length) + { + inotify_event* event = (cast(inotify_event*)(watcher.buffer.ptr + i)); + if (event.len > 0) + { + u8[] file_name = (cast(u8*)event.name)[0 .. strlen(event.name.ptr)]; + + if (event.mask & IN_MOVED_FROM || event.mask & IN_MOVED_TO) + { + bool from = (event.mask & IN_MOVED_FROM) > 0; + + Moved* m; + foreach(j; 0 .. m_count) + { + if (moved[j].cookie == event.cookie) + { + m = moved.ptr + j; + } + } + + if (m != null) + { + if (from && m.to >= 0) + { + events[m.to].names[0] = file_name; + + if (watcher.watched_dir == file_name) + { + events[m.to].type &= ~(WET.File | WET.Dir); + events[m.to].type |= WET.Self; + } + } + else if (!from && m.from >= 0) + { + events[m.from].names[1] = file_name; + } + } + else + { + WatchEvent* ev = events.ptr + count; + ev.type = (event.mask & IN_ISDIR) ? WET.Dir : WET.File; + ev.type |= WET.Moved; + + moved[m_count].cookie = event.cookie; + + if (from) + { + ev.names[0] = file_name; + moved[m_count].from = count; + moved[m_count].to = -1; + + if (watcher.watched_dir == file_name) + { + ev.type &= ~(WET.File | WET.Dir); + ev.type |= WET.Self; + } + } + else + { + ev.names[1] = file_name; + moved[m_count].to = count; + moved[m_count].from = -1; + } + + count += 1; + m_count += 1; + } + } + else + { + WatchEvent* ev = events.ptr + count; + ev.type = (event.mask & IN_ISDIR) ? WET.Dir : WET.File; + + ev.names[0] = file_name; + if (ev.names[0] == watcher.watched_dir) + { + ev.type = WET.Self; + } + + SetEventType(ev, event.mask); + + count += 1; + } + } + else + { + WatchEvent* ev = events.ptr + count; + ev.type = (event.mask & IN_ISDIR) ? WET.Dir : WET.File; + + SetEventType(ev, event.mask); + + count += 1; + } + + i += inotify_event.sizeof + event.len; + } + } + } + + return events; +} + +void +SetEventType(WatchEvent* ev, u32 mask) +{ + if (mask & IN_ACCESS) + { + ev.type |= WET.Accessed; + } + else if (mask & IN_ATTRIB) + { + ev.type |= WET.Metadata; + } + else if (mask & IN_CREATE) + { + ev.type |= WET.Created; + } + else if (mask & IN_MODIFY) + { + ev.type |= WET.Modified; + } + else if (mask & IN_DELETE) + { + ev.type |= WET.Deleted; + } +} + +unittest +{ + import std.stdio; + import std.file : Remove = remove; + + Watcher fw = WatchDirectory("./", WT.Create | WT.Delete | WT.Moved | WT.Modify | WT.Access | WT.Metadata | WT.Access | WT.Metadata); + + auto f = File("test_file.txt", "wb"); + + f.write("test"); + f.sync(); + f.close(); + + Remove("./test_file.txt"); + + WatchEvent[] events = ViewChanges(&fw); + + assert(events.length == 3); + + assert(events[0].type == WET.FileCreated); + assert(events[0].names[0] == r"test_file.txt"); + + assert(events[1].type == WET.FileModified); + assert(events[1].names[0] == r"test_file.txt"); + + assert(events[2].type == WET.FileDeleted); + assert(events[2].names[0] == r"test_file.txt"); +} + +} + +enum WatchEventType +{ + None = 0x0000, + File = 0x0001, + Dir = 0x0002, + Self = 0x0004, + Created = 0x0008, + Modified = 0x0010, + Deleted = 0x0020, + Moved = 0x0040, + Metadata = 0x0080, + Accessed = 0x0100, + FileCreated = WET.File | WET.Created, + FileModified = WET.File | WET.Modified, + FileDeleted = WET.File | WET.Deleted, + FileMoved = WET.File | WET.Moved, + FileMetadata = WET.File | WET.Metadata, + FileAccessed = WET.File | WET.Accessed, + DirCreated = WET.Dir | WET.Created, + DirModified = WET.Dir | WET.Modified, + DirDeleted = WET.Dir | WET.Deleted, + DirMoved = WET.Dir | WET.Moved, + DirMetadata = WET.Dir | WET.Metadata, + DirAccessed = WET.Dir | WET.Accessed, + SelfCreated = WET.Self | WET.Created, + SelfModified = WET.Self | WET.Modified, + SelfDeleted = WET.Self | WET.Deleted, + SelfMoved = WET.Self | WET.Moved, + SelfMetadata = WET.Self | WET.Metadata, + SelfAccessed = WET.Self | WET.Accessed, +} + +alias WET = WatchEventType; + +struct WatchEvent +{ + WatchEventType type; + u8[][2] names; } version(Windows) diff --git a/test.sh b/test.sh new file mode 100755 index 0000000..c893390 --- /dev/null +++ b/test.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +name="Test_Runner" + +ldc2 platform.d aliases.d math.d util.d alloc.d external/xxhash/xxhash.d -P-I/usr/include/freetype2 -L-lfreetype --main --unittest --of=$name +rm $name.o +./$name +rm $name + diff --git a/util.d b/util.d index eb7d77b..1f9fde9 100644 --- a/util.d +++ b/util.d @@ -72,19 +72,19 @@ Log(char* str) writeln(str); } -u64 +@nogc u64 KB(u64 v) { return v * 1024; }; -u64 +@nogc u64 MB(u64 v) { return KB(v) * 1024; }; -u64 +@nogc u64 GB(u64 v) { return MB(v) * 1024;