1 module mutils.file; 2 3 import core.stdc.stdio : fclose, FILE, fopen, fread, fseek, ftell, fwrite, 4 removeFileC = remove, SEEK_END, SEEK_SET; 5 import core.stdc.stdlib : free, malloc; 6 import std.algorithm : canFind; 7 8 import mutils.container.hash_map; 9 import mutils.container.vector; 10 import mutils..string; 11 import mutils..string : getTmpCString; 12 13 struct File { 14 static long getModificationTimestamp(const(char)[] path) { 15 version (Posix) { 16 import core.sys.posix.sys.stat : stat, stat_t; 17 } else version (Windows) { 18 import core.sys.windows.stat : stat, stat_t = struct_stat; 19 } else { 20 static assert(0); 21 } 22 auto tmpCString = getTmpCString(path); 23 stat_t statbuf = void; 24 int ok = stat(tmpCString.str.ptr, &statbuf); 25 if (ok != 0) { 26 return -1; 27 } 28 return statbuf.st_mtime; 29 } 30 31 static bool exists(const(char)[] path) { 32 return File.getModificationTimestamp(path) != -1; 33 } 34 35 static bool remove(const(char)[] path) { 36 char[1024] tmpBuff; 37 auto tmpPath = getTmpCString(path, tmpBuff[]); 38 return removeFileC(tmpPath.str.ptr) == 0; 39 } 40 41 static ubyte[] rawRead(const(char)[] path) { 42 char[1024] tmpBuff; 43 auto tmpPath = getTmpCString(path, tmpBuff[]); 44 FILE* f = fopen(tmpPath.str.ptr, "rb"); 45 if (f == null) { 46 return null; 47 } 48 fseek(f, 0, SEEK_END); 49 long length = ftell(f); 50 if (length <= 0 || length == long.max) { // If path was a directory fopen will succeed but ftell will return long.max 51 fclose(f); 52 return null; 53 } 54 fseek(f, 0, SEEK_SET); 55 ubyte* buffer = cast(ubyte*) malloc(length); 56 fread(buffer, 1, length, f); 57 fclose(f); 58 59 return buffer[0 .. length]; 60 } 61 62 static void rawRemoveReadedData(ubyte[] data) { 63 free(data.ptr); 64 } 65 66 static bool write(T)(const(char)[] path, const(T)[] data) { 67 static assert(is(T == ubyte) || is(T == char)); 68 char[1024] tmpBuff; 69 auto tmpPath = getTmpCString(path, tmpBuff[]); 70 FILE* f = fopen(tmpPath.str.ptr, "wb"); 71 if (f is null) { 72 return false; 73 } 74 size_t elementsWritten = fwrite(data.ptr, data.length * T.sizeof, 1, f); 75 fclose(f); 76 return elementsWritten == 1; 77 } 78 79 } 80 81 unittest { 82 long timestapm = File.getModificationTimestamp("source/"); 83 } 84 85 struct FileWatcher { 86 @disable this(); 87 private this(int) { 88 } 89 90 @disable this(this); 91 __gshared static FileWatcher instance = FileWatcher(1); 92 93 alias EventDelegate = void delegate(string path, const ref WatchedFileInfo info); 94 95 struct WatchedFileInfo { 96 long timestamp; 97 Vector!EventDelegate dels; 98 } 99 100 HashMap!(Vector!(char), WatchedFileInfo) watchedFiles; 101 102 bool watchFile(const(char)[] path, EventDelegate del) { 103 long timestamp = File.getModificationTimestamp(path); 104 WatchedFileInfo* info = &watchedFiles.getInsertDefault( 105 Vector!(char)(cast(char[]) path), WatchedFileInfo(timestamp)); 106 info.timestamp = timestamp; 107 if (!canFind(info.dels[], del)) { 108 info.dels ~= del; 109 } 110 return timestamp != -1; 111 } 112 113 bool removeFileFromWatch(string path) { 114 return watchedFiles.tryRemove(Vector!(char)(cast(char[]) path)); 115 } 116 117 bool removeFileFromWatch(string path, EventDelegate del) { 118 WatchedFileInfo noInfo = WatchedFileInfo(-1); 119 WatchedFileInfo* info = &watchedFiles.get(Vector!(char)(cast(char[]) path), noInfo); 120 if (info.timestamp == -1) { 121 return false; 122 } 123 return info.dels.tryRemoveElement(del); 124 } 125 126 void update() { 127 foreach (ref Vector!char path, ref WatchedFileInfo info; &watchedFiles.byKeyValue) { 128 long timestamp = File.getModificationTimestamp(cast(string) path[]); 129 if (info.timestamp == -1 && timestamp == -1) { 130 continue; 131 } 132 if (timestamp != -1 && timestamp <= info.timestamp) { 133 continue; 134 } 135 info.timestamp = timestamp; 136 foreach (del; info.dels) { 137 del(cast(string) path[], info); 138 } 139 } 140 141 } 142 } 143 144 unittest { 145 void watch(string path, const ref FileWatcher.WatchedFileInfo info) { 146 } 147 148 FileWatcher.instance.watchFile("source/app.d", &watch); 149 FileWatcher.instance.watchFile("source/app.d", &watch); 150 FileWatcher.instance.watchFile("source/app.d", &watch); 151 assert(FileWatcher.instance.watchedFiles.get(Vector!(char)(cast(char[]) "source/app.d")) 152 .dels.length == 1); 153 FileWatcher.instance.update(); 154 FileWatcher.instance.removeFileFromWatch("source/app.d", &watch); 155 FileWatcher.instance.removeFileFromWatch("source/app.d"); 156 }