source: imaps-frontend/node_modules/@parcel/watcher/src/windows/WindowsBackend.cc@ 0c6b92a

main
Last change on this file since 0c6b92a was 0c6b92a, checked in by stefan toskovski <stefantoska84@…>, 5 weeks ago

Pred finalna verzija

  • Property mode set to 100644
File size: 7.9 KB
Line 
1#include <string>
2#include <stack>
3#include "../DirTree.hh"
4#include "../shared/BruteForceBackend.hh"
5#include "./WindowsBackend.hh"
6#include "./win_utils.hh"
7
8#define DEFAULT_BUF_SIZE 1024 * 1024
9#define NETWORK_BUF_SIZE 64 * 1024
10#define CONVERT_TIME(ft) ULARGE_INTEGER{ft.dwLowDateTime, ft.dwHighDateTime}.QuadPart
11
12void BruteForceBackend::readTree(WatcherRef watcher, std::shared_ptr<DirTree> tree) {
13 std::stack<std::string> directories;
14
15 directories.push(watcher->mDir);
16
17 while (!directories.empty()) {
18 HANDLE hFind = INVALID_HANDLE_VALUE;
19
20 std::string path = directories.top();
21 std::string spec = path + "\\*";
22 directories.pop();
23
24 WIN32_FIND_DATA ffd;
25 hFind = FindFirstFile(spec.c_str(), &ffd);
26
27 if (hFind == INVALID_HANDLE_VALUE) {
28 if (path == watcher->mDir) {
29 FindClose(hFind);
30 throw WatcherError("Error opening directory", watcher);
31 }
32
33 tree->remove(path);
34 continue;
35 }
36
37 do {
38 if (strcmp(ffd.cFileName, ".") != 0 && strcmp(ffd.cFileName, "..") != 0) {
39 std::string fullPath = path + "\\" + ffd.cFileName;
40 if (watcher->isIgnored(fullPath)) {
41 continue;
42 }
43
44 tree->add(fullPath, CONVERT_TIME(ffd.ftLastWriteTime), ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
45 if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
46 directories.push(fullPath);
47 }
48 }
49 } while (FindNextFile(hFind, &ffd) != 0);
50
51 FindClose(hFind);
52 }
53}
54
55void WindowsBackend::start() {
56 mRunning = true;
57 notifyStarted();
58
59 while (mRunning) {
60 SleepEx(INFINITE, true);
61 }
62}
63
64WindowsBackend::~WindowsBackend() {
65 // Mark as stopped, and queue a noop function in the thread to break the loop
66 mRunning = false;
67 QueueUserAPC([](__in ULONG_PTR) {}, mThread.native_handle(), (ULONG_PTR)this);
68}
69
70class Subscription: public WatcherState {
71public:
72 Subscription(WindowsBackend *backend, WatcherRef watcher, std::shared_ptr<DirTree> tree) {
73 mRunning = true;
74 mBackend = backend;
75 mWatcher = watcher;
76 mTree = tree;
77 ZeroMemory(&mOverlapped, sizeof(OVERLAPPED));
78 mOverlapped.hEvent = this;
79 mReadBuffer.resize(DEFAULT_BUF_SIZE);
80 mWriteBuffer.resize(DEFAULT_BUF_SIZE);
81
82 mDirectoryHandle = CreateFileW(
83 utf8ToUtf16(watcher->mDir).data(),
84 FILE_LIST_DIRECTORY,
85 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
86 NULL,
87 OPEN_EXISTING,
88 FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
89 NULL
90 );
91
92 if (mDirectoryHandle == INVALID_HANDLE_VALUE) {
93 throw WatcherError("Invalid handle", mWatcher);
94 }
95
96 // Ensure that the path is a directory
97 BY_HANDLE_FILE_INFORMATION info;
98 bool success = GetFileInformationByHandle(
99 mDirectoryHandle,
100 &info
101 );
102
103 if (!success) {
104 throw WatcherError("Could not get file information", mWatcher);
105 }
106
107 if (!(info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
108 throw WatcherError("Not a directory", mWatcher);
109 }
110 }
111
112 virtual ~Subscription() {
113 stop();
114 }
115
116 void run() {
117 try {
118 poll();
119 } catch (WatcherError &err) {
120 mBackend->handleWatcherError(err);
121 }
122 }
123
124 void stop() {
125 if (mRunning) {
126 mRunning = false;
127 CancelIo(mDirectoryHandle);
128 CloseHandle(mDirectoryHandle);
129 }
130 }
131
132 void poll() {
133 if (!mRunning) {
134 return;
135 }
136
137 // Asynchronously wait for changes.
138 int success = ReadDirectoryChangesW(
139 mDirectoryHandle,
140 mWriteBuffer.data(),
141 static_cast<DWORD>(mWriteBuffer.size()),
142 TRUE, // recursive
143 FILE_NOTIFY_CHANGE_FILE_NAME | FILE_NOTIFY_CHANGE_DIR_NAME | FILE_NOTIFY_CHANGE_ATTRIBUTES
144 | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE,
145 NULL,
146 &mOverlapped,
147 [](DWORD errorCode, DWORD numBytes, LPOVERLAPPED overlapped) {
148 auto subscription = reinterpret_cast<Subscription *>(overlapped->hEvent);
149 try {
150 subscription->processEvents(errorCode);
151 } catch (WatcherError &err) {
152 subscription->mBackend->handleWatcherError(err);
153 }
154 }
155 );
156
157 if (!success) {
158 throw WatcherError("Failed to read changes", mWatcher);
159 }
160 }
161
162 void processEvents(DWORD errorCode) {
163 if (!mRunning) {
164 return;
165 }
166
167 switch (errorCode) {
168 case ERROR_OPERATION_ABORTED:
169 return;
170 case ERROR_INVALID_PARAMETER:
171 // resize buffers to network size (64kb), and try again
172 mReadBuffer.resize(NETWORK_BUF_SIZE);
173 mWriteBuffer.resize(NETWORK_BUF_SIZE);
174 poll();
175 return;
176 case ERROR_NOTIFY_ENUM_DIR:
177 throw WatcherError("Buffer overflow. Some events may have been lost.", mWatcher);
178 case ERROR_ACCESS_DENIED: {
179 // This can happen if the watched directory is deleted. Check if that is the case,
180 // and if so emit a delete event. Otherwise, fall through to default error case.
181 DWORD attrs = GetFileAttributesW(utf8ToUtf16(mWatcher->mDir).data());
182 bool isDir = attrs != INVALID_FILE_ATTRIBUTES && (attrs & FILE_ATTRIBUTE_DIRECTORY);
183 if (!isDir) {
184 mWatcher->mEvents.remove(mWatcher->mDir);
185 mTree->remove(mWatcher->mDir);
186 mWatcher->notify();
187 stop();
188 return;
189 }
190 }
191 default:
192 if (errorCode != ERROR_SUCCESS) {
193 throw WatcherError("Unknown error", mWatcher);
194 }
195 }
196
197 // Swap read and write buffers, and poll again
198 std::swap(mWriteBuffer, mReadBuffer);
199 poll();
200
201 // Read change events
202 BYTE *base = mReadBuffer.data();
203 while (true) {
204 PFILE_NOTIFY_INFORMATION info = (PFILE_NOTIFY_INFORMATION)base;
205 processEvent(info);
206
207 if (info->NextEntryOffset == 0) {
208 break;
209 }
210
211 base += info->NextEntryOffset;
212 }
213
214 mWatcher->notify();
215 }
216
217 void processEvent(PFILE_NOTIFY_INFORMATION info) {
218 std::string path = mWatcher->mDir + "\\" + utf16ToUtf8(info->FileName, info->FileNameLength / sizeof(WCHAR));
219 if (mWatcher->isIgnored(path)) {
220 return;
221 }
222
223 switch (info->Action) {
224 case FILE_ACTION_ADDED:
225 case FILE_ACTION_RENAMED_NEW_NAME: {
226 WIN32_FILE_ATTRIBUTE_DATA data;
227 if (GetFileAttributesExW(utf8ToUtf16(path).data(), GetFileExInfoStandard, &data)) {
228 mWatcher->mEvents.create(path);
229 mTree->add(path, CONVERT_TIME(data.ftLastWriteTime), data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
230 }
231 break;
232 }
233 case FILE_ACTION_MODIFIED: {
234 WIN32_FILE_ATTRIBUTE_DATA data;
235 if (GetFileAttributesExW(utf8ToUtf16(path).data(), GetFileExInfoStandard, &data)) {
236 mTree->update(path, CONVERT_TIME(data.ftLastWriteTime));
237 if (!(data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
238 mWatcher->mEvents.update(path);
239 }
240 }
241 break;
242 }
243 case FILE_ACTION_REMOVED:
244 case FILE_ACTION_RENAMED_OLD_NAME:
245 mWatcher->mEvents.remove(path);
246 mTree->remove(path);
247 break;
248 }
249 }
250
251private:
252 WindowsBackend *mBackend;
253 std::shared_ptr<Watcher> mWatcher;
254 std::shared_ptr<DirTree> mTree;
255 bool mRunning;
256 HANDLE mDirectoryHandle;
257 std::vector<BYTE> mReadBuffer;
258 std::vector<BYTE> mWriteBuffer;
259 OVERLAPPED mOverlapped;
260};
261
262// This function is called by Backend::watch which takes a lock on mMutex
263void WindowsBackend::subscribe(WatcherRef watcher) {
264 // Create a subscription for this watcher
265 auto sub = std::make_shared<Subscription>(this, watcher, getTree(watcher, false));
266 watcher->state = sub;
267
268 // Queue polling for this subscription in the correct thread.
269 bool success = QueueUserAPC([](__in ULONG_PTR ptr) {
270 Subscription *sub = (Subscription *)ptr;
271 sub->run();
272 }, mThread.native_handle(), (ULONG_PTR)sub.get());
273
274 if (!success) {
275 throw std::runtime_error("Unable to queue APC");
276 }
277}
278
279// This function is called by Backend::unwatch which takes a lock on mMutex
280void WindowsBackend::unsubscribe(WatcherRef watcher) {
281 watcher->state = nullptr;
282}
Note: See TracBrowser for help on using the repository browser.