BsWin32FolderMonitor.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "Platform/BsFolderMonitor.h"
  4. #include "FileSystem/BsFileSystem.h"
  5. #include "Error/BsException.h"
  6. #include <windows.h>
  7. namespace bs
  8. {
  9. enum class MonitorState
  10. {
  11. Inactive,
  12. Starting,
  13. Monitoring,
  14. Shutdown,
  15. Shutdown2
  16. };
  17. class WorkerFunc
  18. {
  19. public:
  20. WorkerFunc(FolderMonitor* owner);
  21. void operator()();
  22. private:
  23. FolderMonitor* mOwner;
  24. };
  25. struct FolderMonitor::FolderWatchInfo
  26. {
  27. FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
  28. ~FolderWatchInfo();
  29. void startMonitor(HANDLE compPortHandle);
  30. void stopMonitor(HANDLE compPortHandle);
  31. static const UINT32 READ_BUFFER_SIZE = 65536;
  32. Path mFolderToMonitor;
  33. HANDLE mDirHandle;
  34. OVERLAPPED mOverlapped;
  35. MonitorState mState;
  36. UINT8 mBuffer[READ_BUFFER_SIZE];
  37. DWORD mBufferSize;
  38. bool mMonitorSubdirectories;
  39. DWORD mMonitorFlags;
  40. DWORD mReadError;
  41. WString mCachedOldFileName; // Used during rename notifications as they are handled in two steps
  42. Mutex mStatusMutex;
  43. Signal mStartStopEvent;
  44. };
  45. FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
  46. :mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
  47. mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
  48. {
  49. memset(&mOverlapped, 0, sizeof(mOverlapped));
  50. }
  51. FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
  52. {
  53. assert(mState == MonitorState::Inactive);
  54. stopMonitor(0);
  55. }
  56. void FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
  57. {
  58. if(mState != MonitorState::Inactive)
  59. return; // Already monitoring
  60. {
  61. Lock lock(mStatusMutex);
  62. mState = MonitorState::Starting;
  63. PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
  64. while(mState != MonitorState::Monitoring)
  65. mStartStopEvent.wait(lock);
  66. }
  67. if(mReadError != ERROR_SUCCESS)
  68. {
  69. {
  70. Lock lock(mStatusMutex);
  71. mState = MonitorState::Inactive;
  72. }
  73. BS_EXCEPT(InternalErrorException, "Failed to start folder monitor on folder \"" +
  74. mFolderToMonitor.toString() + "\" because ReadDirectoryChangesW failed.");
  75. }
  76. }
  77. void FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
  78. {
  79. if(mState != MonitorState::Inactive)
  80. {
  81. Lock lock(mStatusMutex);
  82. mState = MonitorState::Shutdown;
  83. PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
  84. while(mState != MonitorState::Inactive)
  85. mStartStopEvent.wait(lock);
  86. }
  87. if(mDirHandle != INVALID_HANDLE_VALUE)
  88. {
  89. CloseHandle(mDirHandle);
  90. mDirHandle = INVALID_HANDLE_VALUE;
  91. }
  92. }
  93. class FolderMonitor::FileNotifyInfo
  94. {
  95. public:
  96. FileNotifyInfo(UINT8* notifyBuffer, DWORD bufferSize)
  97. :mBuffer(notifyBuffer), mBufferSize(bufferSize)
  98. {
  99. mCurrentRecord = (PFILE_NOTIFY_INFORMATION)mBuffer;
  100. }
  101. bool getNext();
  102. DWORD getAction() const;
  103. WString getFileName() const;
  104. WString getFileNameWithPath(const Path& rootPath) const;
  105. protected:
  106. UINT8* mBuffer;
  107. DWORD mBufferSize;
  108. PFILE_NOTIFY_INFORMATION mCurrentRecord;
  109. };
  110. bool FolderMonitor::FileNotifyInfo::getNext()
  111. {
  112. if(mCurrentRecord && mCurrentRecord->NextEntryOffset != 0)
  113. {
  114. PFILE_NOTIFY_INFORMATION oldRecord = mCurrentRecord;
  115. mCurrentRecord = (PFILE_NOTIFY_INFORMATION) ((UINT8*)mCurrentRecord + mCurrentRecord->NextEntryOffset);
  116. if((DWORD)((UINT8*)mCurrentRecord - mBuffer) > mBufferSize)
  117. {
  118. // Gone out of range, something bad happened
  119. assert(false);
  120. mCurrentRecord = oldRecord;
  121. }
  122. return (mCurrentRecord != oldRecord);
  123. }
  124. return false;
  125. }
  126. DWORD FolderMonitor::FileNotifyInfo::getAction() const
  127. {
  128. assert(mCurrentRecord != nullptr);
  129. if(mCurrentRecord)
  130. return mCurrentRecord->Action;
  131. return 0;
  132. }
  133. WString FolderMonitor::FileNotifyInfo::getFileName() const
  134. {
  135. if(mCurrentRecord)
  136. {
  137. wchar_t fileNameBuffer[32768 + 1] = {0};
  138. memcpy(fileNameBuffer, mCurrentRecord->FileName,
  139. std::min(DWORD(32768 * sizeof(wchar_t)), mCurrentRecord->FileNameLength));
  140. return WString(fileNameBuffer);
  141. }
  142. return WString();
  143. }
  144. WString FolderMonitor::FileNotifyInfo::getFileNameWithPath(const Path& rootPath) const
  145. {
  146. Path fullPath = rootPath;
  147. return fullPath.append(getFileName()).toWString();
  148. }
  149. enum class FileActionType
  150. {
  151. Added,
  152. Removed,
  153. Modified,
  154. Renamed
  155. };
  156. struct FileAction
  157. {
  158. static FileAction* createAdded(const WString& fileName)
  159. {
  160. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  161. FileAction* action = (FileAction*)bytes;
  162. bytes += sizeof(FileAction);
  163. action->oldName = nullptr;
  164. action->newName = (WString::value_type*)bytes;
  165. action->type = FileActionType::Added;
  166. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  167. action->newName[fileName.size()] = L'\0';
  168. action->lastSize = 0;
  169. action->checkForWriteStarted = false;
  170. return action;
  171. }
  172. static FileAction* createRemoved(const WString& fileName)
  173. {
  174. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  175. FileAction* action = (FileAction*)bytes;
  176. bytes += sizeof(FileAction);
  177. action->oldName = nullptr;
  178. action->newName = (WString::value_type*)bytes;
  179. action->type = FileActionType::Removed;
  180. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  181. action->newName[fileName.size()] = L'\0';
  182. action->lastSize = 0;
  183. action->checkForWriteStarted = false;
  184. return action;
  185. }
  186. static FileAction* createModified(const WString& fileName)
  187. {
  188. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  189. FileAction* action = (FileAction*)bytes;
  190. bytes += sizeof(FileAction);
  191. action->oldName = nullptr;
  192. action->newName = (WString::value_type*)bytes;
  193. action->type = FileActionType::Modified;
  194. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  195. action->newName[fileName.size()] = L'\0';
  196. action->lastSize = 0;
  197. action->checkForWriteStarted = false;
  198. return action;
  199. }
  200. static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
  201. {
  202. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) +
  203. (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
  204. FileAction* action = (FileAction*)bytes;
  205. bytes += sizeof(FileAction);
  206. action->oldName = (WString::value_type*)bytes;
  207. bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
  208. action->newName = (WString::value_type*)bytes;
  209. action->type = FileActionType::Modified;
  210. memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
  211. action->oldName[oldFilename.size()] = L'\0';
  212. memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
  213. action->newName[newfileName.size()] = L'\0';
  214. action->lastSize = 0;
  215. action->checkForWriteStarted = false;
  216. return action;
  217. }
  218. static void destroy(FileAction* action)
  219. {
  220. bs_free(action);
  221. }
  222. WString::value_type* oldName;
  223. WString::value_type* newName;
  224. FileActionType type;
  225. UINT64 lastSize;
  226. bool checkForWriteStarted;
  227. };
  228. struct FolderMonitor::Pimpl
  229. {
  230. Vector<FolderWatchInfo*> mFoldersToWatch;
  231. HANDLE mCompPortHandle;
  232. Queue<FileAction*> mFileActions;
  233. List<FileAction*> mActiveFileActions;
  234. Mutex mMainMutex;
  235. Thread* mWorkerThread;
  236. };
  237. FolderMonitor::FolderMonitor()
  238. {
  239. m = bs_new<Pimpl>();
  240. m->mWorkerThread = nullptr;
  241. m->mCompPortHandle = nullptr;
  242. }
  243. FolderMonitor::~FolderMonitor()
  244. {
  245. stopMonitorAll();
  246. // No need for mutex since we know worker thread is shut down by now
  247. while(!m->mFileActions.empty())
  248. {
  249. FileAction* action = m->mFileActions.front();
  250. m->mFileActions.pop();
  251. FileAction::destroy(action);
  252. }
  253. bs_delete(m);
  254. }
  255. void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter)
  256. {
  257. if(!FileSystem::isDirectory(folderPath))
  258. {
  259. LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory");
  260. return;
  261. }
  262. WString extendedFolderPath = L"\\\\?\\" + folderPath.toWString(Path::PathType::Windows);
  263. HANDLE dirHandle = CreateFileW(extendedFolderPath.c_str(), FILE_LIST_DIRECTORY,
  264. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
  265. FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
  266. if(dirHandle == INVALID_HANDLE_VALUE)
  267. {
  268. BS_EXCEPT(InternalErrorException, "Failed to open folder \"" + folderPath.toString() + "\" for monitoring. Error code: " + toString((UINT64)GetLastError()));
  269. }
  270. DWORD filterFlags = 0;
  271. if(changeFilter.isSet(FolderChangeBit::FileName))
  272. filterFlags |= FILE_NOTIFY_CHANGE_FILE_NAME;
  273. if(changeFilter.isSet(FolderChangeBit::DirName))
  274. filterFlags |= FILE_NOTIFY_CHANGE_DIR_NAME;
  275. if(changeFilter.isSet(FolderChangeBit::FileWrite))
  276. filterFlags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
  277. m->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, dirHandle, subdirectories, filterFlags));
  278. FolderWatchInfo* watchInfo = m->mFoldersToWatch.back();
  279. m->mCompPortHandle = CreateIoCompletionPort(dirHandle, m->mCompPortHandle, (ULONG_PTR)watchInfo, 0);
  280. if(m->mCompPortHandle == nullptr)
  281. {
  282. m->mFoldersToWatch.erase(m->mFoldersToWatch.end() - 1);
  283. bs_delete(watchInfo);
  284. BS_EXCEPT(InternalErrorException, "Failed to open completion port for folder monitoring. Error code: " + toString((UINT64)GetLastError()));
  285. }
  286. if(m->mWorkerThread == nullptr)
  287. {
  288. m->mWorkerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
  289. if(m->mWorkerThread == nullptr)
  290. {
  291. m->mFoldersToWatch.erase(m->mFoldersToWatch.end() - 1);
  292. bs_delete(watchInfo);
  293. BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
  294. }
  295. }
  296. if(m->mWorkerThread != nullptr)
  297. {
  298. watchInfo->startMonitor(m->mCompPortHandle);
  299. }
  300. else
  301. {
  302. m->mFoldersToWatch.erase(m->mFoldersToWatch.end() - 1);
  303. bs_delete(watchInfo);
  304. BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
  305. }
  306. }
  307. void FolderMonitor::stopMonitor(const Path& folderPath)
  308. {
  309. auto findIter = std::find_if(m->mFoldersToWatch.begin(), m->mFoldersToWatch.end(),
  310. [&](const FolderWatchInfo* x) { return x->mFolderToMonitor == folderPath; });
  311. if(findIter != m->mFoldersToWatch.end())
  312. {
  313. FolderWatchInfo* watchInfo = *findIter;
  314. watchInfo->stopMonitor(m->mCompPortHandle);
  315. bs_delete(watchInfo);
  316. m->mFoldersToWatch.erase(findIter);
  317. }
  318. if(m->mFoldersToWatch.size() == 0)
  319. stopMonitorAll();
  320. }
  321. void FolderMonitor::stopMonitorAll()
  322. {
  323. for(auto& watchInfo : m->mFoldersToWatch)
  324. {
  325. watchInfo->stopMonitor(m->mCompPortHandle);
  326. {
  327. // Note: Need this mutex to ensure worker thread is done with watchInfo.
  328. // Even though we wait for a condition variable from the worker thread in stopMonitor,
  329. // that doesn't mean the worker thread is done with the condition variable
  330. // (which is stored inside watchInfo)
  331. Lock lock(m->mMainMutex);
  332. bs_delete(watchInfo);
  333. }
  334. }
  335. m->mFoldersToWatch.clear();
  336. if(m->mWorkerThread != nullptr)
  337. {
  338. PostQueuedCompletionStatus(m->mCompPortHandle, 0, 0, nullptr);
  339. m->mWorkerThread->join();
  340. bs_delete(m->mWorkerThread);
  341. m->mWorkerThread = nullptr;
  342. }
  343. if(m->mCompPortHandle != nullptr)
  344. {
  345. CloseHandle(m->mCompPortHandle);
  346. m->mCompPortHandle = nullptr;
  347. }
  348. }
  349. void FolderMonitor::workerThreadMain()
  350. {
  351. FolderWatchInfo* watchInfo = nullptr;
  352. do
  353. {
  354. DWORD numBytes;
  355. LPOVERLAPPED overlapped;
  356. if(!GetQueuedCompletionStatus(m->mCompPortHandle, &numBytes, (PULONG_PTR) &watchInfo, &overlapped, INFINITE))
  357. {
  358. assert(false);
  359. // TODO: Folder handle was lost most likely. Not sure how to deal with that. Shutdown watch on this folder and cleanup?
  360. }
  361. if(watchInfo != nullptr)
  362. {
  363. MonitorState state;
  364. {
  365. Lock lock(watchInfo->mStatusMutex);
  366. state = watchInfo->mState;
  367. }
  368. switch(state)
  369. {
  370. case MonitorState::Starting:
  371. if(!ReadDirectoryChangesW(watchInfo->mDirHandle, watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE,
  372. watchInfo->mMonitorSubdirectories, watchInfo->mMonitorFlags, &watchInfo->mBufferSize, &watchInfo->mOverlapped, nullptr))
  373. {
  374. assert(false); // TODO - Possibly the buffer was too small?
  375. watchInfo->mReadError = GetLastError();
  376. }
  377. else
  378. {
  379. watchInfo->mReadError = ERROR_SUCCESS;
  380. {
  381. Lock lock(watchInfo->mStatusMutex);
  382. watchInfo->mState = MonitorState::Monitoring;
  383. }
  384. }
  385. watchInfo->mStartStopEvent.notify_one();
  386. break;
  387. case MonitorState::Monitoring:
  388. {
  389. FileNotifyInfo info(watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE);
  390. handleNotifications(info, *watchInfo);
  391. if(!ReadDirectoryChangesW(watchInfo->mDirHandle, watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE,
  392. watchInfo->mMonitorSubdirectories, watchInfo->mMonitorFlags, &watchInfo->mBufferSize, &watchInfo->mOverlapped, nullptr))
  393. {
  394. assert(false); // TODO: Failed during normal operation, possibly the buffer was too small. Shutdown watch on this folder and cleanup?
  395. watchInfo->mReadError = GetLastError();
  396. }
  397. else
  398. {
  399. watchInfo->mReadError = ERROR_SUCCESS;
  400. }
  401. }
  402. break;
  403. case MonitorState::Shutdown:
  404. if(watchInfo->mDirHandle != INVALID_HANDLE_VALUE)
  405. {
  406. CloseHandle(watchInfo->mDirHandle);
  407. watchInfo->mDirHandle = INVALID_HANDLE_VALUE;
  408. {
  409. Lock lock(watchInfo->mStatusMutex);
  410. watchInfo->mState = MonitorState::Shutdown2;
  411. }
  412. }
  413. else
  414. {
  415. {
  416. Lock lock(watchInfo->mStatusMutex);
  417. watchInfo->mState = MonitorState::Inactive;
  418. }
  419. {
  420. Lock lock(m->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
  421. watchInfo->mStartStopEvent.notify_one();
  422. }
  423. }
  424. break;
  425. case MonitorState::Shutdown2:
  426. if(watchInfo->mDirHandle != INVALID_HANDLE_VALUE)
  427. {
  428. // Handle is still open? Try again.
  429. CloseHandle(watchInfo->mDirHandle);
  430. watchInfo->mDirHandle = INVALID_HANDLE_VALUE;
  431. }
  432. else
  433. {
  434. {
  435. Lock lock(watchInfo->mStatusMutex);
  436. watchInfo->mState = MonitorState::Inactive;
  437. }
  438. {
  439. Lock lock(m->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
  440. watchInfo->mStartStopEvent.notify_one();
  441. }
  442. }
  443. break;
  444. default:
  445. break;
  446. }
  447. }
  448. } while (watchInfo != nullptr);
  449. }
  450. void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
  451. {
  452. Vector<FileAction*> mActions;
  453. do
  454. {
  455. WString fullPath = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
  456. // Ignore notifications about hidden files
  457. if ((GetFileAttributesW(fullPath.c_str()) & FILE_ATTRIBUTE_HIDDEN) != 0)
  458. continue;
  459. switch(notifyInfo.getAction())
  460. {
  461. case FILE_ACTION_ADDED:
  462. mActions.push_back(FileAction::createAdded(fullPath));
  463. break;
  464. case FILE_ACTION_REMOVED:
  465. mActions.push_back(FileAction::createRemoved(fullPath));
  466. break;
  467. case FILE_ACTION_MODIFIED:
  468. mActions.push_back(FileAction::createModified(fullPath));
  469. break;
  470. case FILE_ACTION_RENAMED_OLD_NAME:
  471. watchInfo.mCachedOldFileName = fullPath;
  472. break;
  473. case FILE_ACTION_RENAMED_NEW_NAME:
  474. mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fullPath));
  475. break;
  476. }
  477. } while(notifyInfo.getNext());
  478. {
  479. Lock lock(m->mMainMutex);
  480. for(auto& action : mActions)
  481. m->mFileActions.push(action);
  482. }
  483. }
  484. void FolderMonitor::_update()
  485. {
  486. {
  487. Lock lock(m->mMainMutex);
  488. while (!m->mFileActions.empty())
  489. {
  490. FileAction* action = m->mFileActions.front();
  491. m->mFileActions.pop();
  492. m->mActiveFileActions.push_back(action);
  493. }
  494. }
  495. for (auto iter = m->mActiveFileActions.begin(); iter != m->mActiveFileActions.end();)
  496. {
  497. FileAction* action = *iter;
  498. // Reported file actions might still be in progress (i.e. something might still be writing to those files).
  499. // Sadly there doesn't seem to be a way to properly determine when those files are done being written, so instead
  500. // we check for at least a couple of frames if the file's size hasn't changed before reporting a file action.
  501. // This takes care of most of the issues and avoids reporting partially written files in almost all cases.
  502. if (FileSystem::exists(action->newName))
  503. {
  504. UINT64 size = FileSystem::getFileSize(action->newName);
  505. if (!action->checkForWriteStarted)
  506. {
  507. action->checkForWriteStarted = true;
  508. action->lastSize = size;
  509. ++iter;
  510. continue;
  511. }
  512. else
  513. {
  514. if (action->lastSize != size)
  515. {
  516. action->lastSize = size;
  517. ++iter;
  518. continue;
  519. }
  520. }
  521. }
  522. switch (action->type)
  523. {
  524. case FileActionType::Added:
  525. if (!onAdded.empty())
  526. onAdded(Path(action->newName));
  527. break;
  528. case FileActionType::Removed:
  529. if (!onRemoved.empty())
  530. onRemoved(Path(action->newName));
  531. break;
  532. case FileActionType::Modified:
  533. if (!onModified.empty())
  534. onModified(Path(action->newName));
  535. break;
  536. case FileActionType::Renamed:
  537. if (!onRenamed.empty())
  538. onRenamed(Path(action->oldName), Path(action->newName));
  539. break;
  540. }
  541. m->mActiveFileActions.erase(iter++);
  542. FileAction::destroy(action);
  543. }
  544. }
  545. }