1 module meld.fileWatcher;
2 
3 import std..string;
4 
5 static class FileWatcher
6 {
7 	alias Callback = void delegate(string file);
8 
9 	static class Watcher
10 	{
11 		version(Windows)
12 		{
13 			import core.sys.windows.windows;
14 			import core.stdc.stdlib;
15 			import core.stdc..string;
16 			import std.conv;
17 
18 			struct FILE_NOTIFY_INFORMATION {
19 				DWORD NextEntryOffset;
20 				DWORD Action;
21 				DWORD FileNameLength;
22 				WCHAR FileName[1];
23 			}
24 
25 			extern(Windows)
26 			{
27 				alias VOID function(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, OVERLAPPED* lpOverlapped) LPOVERLAPPED_COMPLETION_ROUTINE;
28 				
29 				alias fReadDirectoryChangesW = BOOL function(HANDLE hDirectory, LPVOID lpBuffer, DWORD nBufferLength, BOOL bWatchSubtree,
30 				                         DWORD dwNotifyFilter, DWORD* lpBytesReturned, OVERLAPPED* lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
31 				static fReadDirectoryChangesW ReadDirectoryChangesW;
32 
33 				alias fCreateIoCompletionPort = HANDLE function(HANDLE FileHandle, HANDLE ExistingCompletionPort, ULONG_PTR CompletionKey, DWORD NumberOfConcurrentThreads);
34 				static fCreateIoCompletionPort CreateIoCompletionPort;
35 
36 				alias fGetQueuedCompletionStatus = BOOL function(HANDLE CompletionPort, DWORD* lpNumberOfBytes, PULONG_PTR lpCompletionKey, OVERLAPPED** lpOverlapped, DWORD dwMilliseconds);
37 				static fGetQueuedCompletionStatus GetQueuedCompletionStatus;
38 
39 				alias fCancelIo = BOOL function(HANDLE hFile);
40 				static fCancelIo CancelIo;
41 
42 				static this()
43 				{
44 					HMODULE libHandle = LoadLibraryA("kernel32.dll");
45 					ReadDirectoryChangesW = cast(fReadDirectoryChangesW)GetProcAddress(libHandle, "ReadDirectoryChangesW");
46 					CreateIoCompletionPort = cast(fCreateIoCompletionPort)GetProcAddress(libHandle, "CreateIoCompletionPort");
47 					GetQueuedCompletionStatus = cast(fGetQueuedCompletionStatus)GetProcAddress(libHandle, "GetQueuedCompletionStatus");
48 					CancelIo = cast(fCancelIo)GetProcAddress(libHandle, "CancelIo");
49 				}
50 			}
51 
52 			HANDLE m_directoryHandle, m_completionPort;
53 			Callback m_callback;
54 			OVERLAPPED m_overlapped;
55 			void[4096] m_buffer = void;
56 			string m_directory;
57 
58 			this(string directory, Callback callback)
59 			{
60 				m_directory = directory;
61 				m_directoryHandle = CreateFileA(
62 					directory.toStringz, 
63 					FILE_LIST_DIRECTORY, 
64 					FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
65 					null,
66 					OPEN_EXISTING,
67 					FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
68 					null);
69 				assert(m_directoryHandle != INVALID_HANDLE_VALUE);
70 
71 				m_completionPort = CreateIoCompletionPort(m_directoryHandle, null, 0, 1);
72 				assert(m_completionPort != INVALID_HANDLE_VALUE);
73 
74 				m_callback = callback;
75 
76 				DoRead();
77 			}
78 
79 			~this()
80 			{
81 				CancelIo(m_directoryHandle);
82 				CloseHandle(m_completionPort);
83 				CloseHandle(m_directoryHandle);
84 			}
85 
86 			void DoRead()
87 			{
88 				memset(&m_overlapped, 0, m_overlapped.sizeof);
89 				ReadDirectoryChangesW(m_directoryHandle, m_buffer.ptr, m_buffer.length, true,
90 					FILE_NOTIFY_CHANGE_LAST_WRITE, null, &m_overlapped, null);
91 			}
92 
93 			void Update()
94 			{
95 				bool[string] notifyList;
96 
97 				OVERLAPPED* lpOverlapped;
98 				uint numberOfBytes;
99 				ULONG_PTR completionKey;
100 				while( GetQueuedCompletionStatus(m_completionPort, &numberOfBytes, &completionKey, &lpOverlapped, 0) != 0)
101 				{
102 					//Copy the buffer
103 					assert(numberOfBytes > 0);
104 					void[] buffer = alloca(numberOfBytes)[0..numberOfBytes];
105 					buffer[0..$] = m_buffer[0..numberOfBytes];
106 
107 					//Reissue the read request
108 					DoRead();
109 
110 					//Process the messages
111 					auto info = cast(const(FILE_NOTIFY_INFORMATION)*)buffer.ptr;
112 					while(true)
113 					{
114 						const(WCHAR)[] directory = info.FileName.ptr[0..(info.FileNameLength/2)];
115 						int bytesNeeded = WideCharToMultiByte(CP_UTF8, 0, directory.ptr, to!int(directory.length), null, 0, null, null);
116 						if(bytesNeeded > 0)
117 						{
118 							char[] dir = (cast(char*)alloca(bytesNeeded))[0..bytesNeeded];
119 							WideCharToMultiByte(CP_UTF8, 0, directory.ptr, to!int(directory.length), dir.ptr, to!int(dir.length), null, null);
120 
121 							string changedFile = to!string(dir);
122 							if (changedFile !in notifyList)
123 							{
124 								m_callback(m_directory ~ "\\" ~ to!string(dir));
125 								notifyList[changedFile] = true;	
126 							}
127 						}
128 						if(info.NextEntryOffset == 0)
129 							break;
130 						else
131 							info = cast(const(FILE_NOTIFY_INFORMATION)*)((cast(void*)info) + info.NextEntryOffset);
132 					}
133 				}
134 			}
135 		}
136 		else
137 		{
138 			this(string directory, Callback callback)
139 			{
140 
141 			}
142 
143 			void Update()
144 			{
145 				
146 			}
147 		}
148 	}
149 
150 	static Watcher contentWatcher = null;
151 	alias void delegate() FileChangeCallback;
152 
153 	static FileChangeCallback[string] callbackList;
154 
155 	static void Watch(string sourceFile, void delegate() callback)
156 	{
157 		import std.stdio : writeln;
158 		if (contentWatcher is null)
159 		{
160 			contentWatcher = new Watcher("data", (changedFile)
161 			{
162 				changedFile = translate(changedFile, ['\\': '/']);
163 				writeln(changedFile ~ " changed");
164 
165 				FileChangeCallback* callback = changedFile in callbackList;
166 				if (callback)
167 					(*callback)();
168 			});
169 		}
170 		writeln("Listening for changes to " ~ sourceFile);
171 		callbackList[sourceFile] = callback;
172 	}
173 
174 	static void Update()
175 	{
176 		if (contentWatcher !is null)
177 			contentWatcher.Update();
178 	}
179 }