BsLinuxFolderMonitor.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  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 <corecrt_io.h>
  7. namespace bs
  8. {
  9. class WorkerFunc
  10. {
  11. public:
  12. WorkerFunc(FolderMonitor* owner);
  13. void operator()();
  14. private:
  15. FolderMonitor* mOwner;
  16. };
  17. struct FolderMonitor::FolderWatchInfo
  18. {
  19. FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories, UINT32 monitorFlags);
  20. ~FolderWatchInfo();
  21. void startMonitor();
  22. void stopMonitor();
  23. Path mFolderToMonitor;
  24. int mDirHandle;
  25. bool mMonitorSubdirectories;
  26. UINT32 mMonitorFlags;
  27. };
  28. FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories, UINT32 monitorFlags)
  29. :mFolderToMonitor(folderToMonitor), mDirHandle(0), mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags)
  30. { }
  31. FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
  32. {
  33. stopMonitor();
  34. }
  35. void FolderMonitor::FolderWatchInfo::startMonitor()
  36. {
  37. }
  38. void FolderMonitor::FolderWatchInfo::stopMonitor()
  39. {
  40. }
  41. class FolderMonitor::FileNotifyInfo
  42. {
  43. };
  44. enum class FileActionType
  45. {
  46. Added,
  47. Removed,
  48. Modified,
  49. Renamed
  50. };
  51. struct FileAction
  52. {
  53. static FileAction* createAdded(const WString& fileName)
  54. {
  55. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  56. FileAction* action = (FileAction*)bytes;
  57. bytes += sizeof(FileAction);
  58. action->oldName = nullptr;
  59. action->newName = (WString::value_type*)bytes;
  60. action->type = FileActionType::Added;
  61. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  62. action->newName[fileName.size()] = L'\0';
  63. action->lastSize = 0;
  64. action->checkForWriteStarted = false;
  65. return action;
  66. }
  67. static FileAction* createRemoved(const WString& fileName)
  68. {
  69. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  70. FileAction* action = (FileAction*)bytes;
  71. bytes += sizeof(FileAction);
  72. action->oldName = nullptr;
  73. action->newName = (WString::value_type*)bytes;
  74. action->type = FileActionType::Removed;
  75. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  76. action->newName[fileName.size()] = L'\0';
  77. action->lastSize = 0;
  78. action->checkForWriteStarted = false;
  79. return action;
  80. }
  81. static FileAction* createModified(const WString& fileName)
  82. {
  83. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  84. FileAction* action = (FileAction*)bytes;
  85. bytes += sizeof(FileAction);
  86. action->oldName = nullptr;
  87. action->newName = (WString::value_type*)bytes;
  88. action->type = FileActionType::Modified;
  89. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  90. action->newName[fileName.size()] = L'\0';
  91. action->lastSize = 0;
  92. action->checkForWriteStarted = false;
  93. return action;
  94. }
  95. static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
  96. {
  97. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) +
  98. (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
  99. FileAction* action = (FileAction*)bytes;
  100. bytes += sizeof(FileAction);
  101. action->oldName = (WString::value_type*)bytes;
  102. bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
  103. action->newName = (WString::value_type*)bytes;
  104. action->type = FileActionType::Modified;
  105. memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
  106. action->oldName[oldFilename.size()] = L'\0';
  107. memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
  108. action->newName[newfileName.size()] = L'\0';
  109. action->lastSize = 0;
  110. action->checkForWriteStarted = false;
  111. return action;
  112. }
  113. static void destroy(FileAction* action)
  114. {
  115. bs_free(action);
  116. }
  117. WString::value_type* oldName;
  118. WString::value_type* newName;
  119. FileActionType type;
  120. UINT64 lastSize;
  121. bool checkForWriteStarted;
  122. };
  123. struct FolderMonitor::Pimpl
  124. {
  125. Vector<FolderWatchInfo*> mFoldersToWatch;
  126. Queue<FileAction*> mFileActions;
  127. List<FileAction*> mActiveFileActions;
  128. int inHandle;
  129. Mutex mMainMutex;
  130. Thread* mWorkerThread;
  131. };
  132. FolderMonitor::FolderMonitor()
  133. {
  134. mPimpl = bs_new<Pimpl>();
  135. mPimpl->mWorkerThread = nullptr;
  136. mPimpl->inHandle = 0;
  137. }
  138. FolderMonitor::~FolderMonitor()
  139. {
  140. stopMonitorAll();
  141. // No need for mutex since we know worker thread is shut down by now
  142. while (!mPimpl->mFileActions.empty())
  143. {
  144. FileAction* action = mPimpl->mFileActions.front();
  145. mPimpl->mFileActions.pop();
  146. FileAction::destroy(action);
  147. }
  148. bs_delete(mPimpl);
  149. }
  150. void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter)
  151. {
  152. if(!FileSystem::isDirectory(folderPath))
  153. {
  154. LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory");
  155. return;
  156. }
  157. UINT32 filterFlags = 0;
  158. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::FileName) != 0)
  159. filterFlags |= IN_MOVE | IN_CREATE | IN_DELETE;
  160. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::DirName) != 0)
  161. filterFlags |= IN_MOVE | IN_CREATE | IN_DELETE;
  162. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Attributes) != 0)
  163. filterFlags |= IN_ATTRIB;
  164. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Size) != 0)
  165. filterFlags |= IN_ATTRIB | IN_MODIFY | IN_CREATE | IN_DELETE;
  166. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::LastWrite) != 0)
  167. filterFlags |= IN_ATTRIB | IN_MODIFY | IN_CLOSE_WRITE | IN_CREATE | IN_MOVED_TO;
  168. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::LastAccess) != 0)
  169. filterFlags |= IN_ATTRIB | IN_ACCESS;
  170. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Creation) != 0)
  171. filterFlags |= IN_CREATE;
  172. if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Security) != 0)
  173. filterFlags |= IN_ATTRIB;
  174. mPimpl->inHandle = inotify_init();
  175. mPimpl->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, subdirectories, filterFlags));
  176. FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
  177. if(mPimpl->mWorkerThread == nullptr)
  178. {
  179. mPimpl->mWorkerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
  180. if(mPimpl->mWorkerThread == nullptr)
  181. {
  182. mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
  183. bs_delete(watchInfo);
  184. BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
  185. }
  186. }
  187. if(mPimpl->mWorkerThread != nullptr)
  188. {
  189. watchInfo->startMonitor();
  190. }
  191. else
  192. {
  193. mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
  194. bs_delete(watchInfo);
  195. BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
  196. }
  197. }
  198. void FolderMonitor::stopMonitor(const Path& folderPath)
  199. {
  200. auto findIter = std::find_if(mPimpl->mFoldersToWatch.begin(), mPimpl->mFoldersToWatch.end(),
  201. [&](const FolderWatchInfo* x) { return x->mFolderToMonitor == folderPath; });
  202. if(findIter != mPimpl->mFoldersToWatch.end())
  203. {
  204. FolderWatchInfo* watchInfo = *findIter;
  205. watchInfo->stopMonitor();
  206. bs_delete(watchInfo);
  207. mPimpl->mFoldersToWatch.erase(findIter);
  208. }
  209. if(mPimpl->mFoldersToWatch.size() == 0)
  210. stopMonitorAll();
  211. }
  212. void FolderMonitor::stopMonitorAll()
  213. {
  214. for(auto& watchInfo : mPimpl->mFoldersToWatch)
  215. {
  216. watchInfo->stopMonitor();
  217. {
  218. // Note: Need this mutex to ensure worker thread is done with watchInfo.
  219. // Even though we wait for a condition variable from the worker thread in stopMonitor,
  220. // that doesn't mean the worker thread is done with the condition variable
  221. // (which is stored inside watchInfo)
  222. Lock lock(mPimpl->mMainMutex);
  223. bs_delete(watchInfo);
  224. }
  225. }
  226. mPimpl->mFoldersToWatch.clear();
  227. if(mPimpl->mWorkerThread != nullptr)
  228. {
  229. mPimpl->mWorkerThread->join();
  230. bs_delete(mPimpl->mWorkerThread);
  231. mPimpl->mWorkerThread = nullptr;
  232. }
  233. if(mPimpl->inHandle != 0)
  234. {
  235. close(mPimpl->inHandle);
  236. mPimpl->inHandle = 0;
  237. }
  238. }
  239. void FolderMonitor::workerThreadMain()
  240. {
  241. }
  242. void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
  243. {
  244. }
  245. void FolderMonitor::_update()
  246. {
  247. {
  248. Lock lock(mPimpl->mMainMutex);
  249. while (!mPimpl->mFileActions.empty())
  250. {
  251. FileAction* action = mPimpl->mFileActions.front();
  252. mPimpl->mFileActions.pop();
  253. mPimpl->mActiveFileActions.push_back(action);
  254. }
  255. }
  256. for (auto iter = mPimpl->mActiveFileActions.begin(); iter != mPimpl->mActiveFileActions.end();)
  257. {
  258. FileAction* action = *iter;
  259. // Reported file actions might still be in progress (i.e. something might still be writing to those files).
  260. // Sadly there doesn't seem to be a way to properly determine when those files are done being written, so instead
  261. // we check for at least a couple of frames if the file's size hasn't changed before reporting a file action.
  262. // This takes care of most of the issues and avoids reporting partially written files in almost all cases.
  263. if (FileSystem::exists(action->newName))
  264. {
  265. UINT64 size = FileSystem::getFileSize(action->newName);
  266. if (!action->checkForWriteStarted)
  267. {
  268. action->checkForWriteStarted = true;
  269. action->lastSize = size;
  270. ++iter;
  271. continue;
  272. }
  273. else
  274. {
  275. if (action->lastSize != size)
  276. {
  277. action->lastSize = size;
  278. ++iter;
  279. continue;
  280. }
  281. }
  282. }
  283. switch (action->type)
  284. {
  285. case FileActionType::Added:
  286. if (!onAdded.empty())
  287. onAdded(Path(action->newName));
  288. break;
  289. case FileActionType::Removed:
  290. if (!onRemoved.empty())
  291. onRemoved(Path(action->newName));
  292. break;
  293. case FileActionType::Modified:
  294. if (!onModified.empty())
  295. onModified(Path(action->newName));
  296. break;
  297. case FileActionType::Renamed:
  298. if (!onRenamed.empty())
  299. onRenamed(Path(action->oldName), Path(action->newName));
  300. break;
  301. }
  302. mPimpl->mActiveFileActions.erase(iter++);
  303. FileAction::destroy(action);
  304. }
  305. }
  306. }