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 }