cmFileMonitor.cxx 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying
  2. file Copyright.txt or https://cmake.org/licensing for details. */
  3. #include "cmFileMonitor.h"
  4. #include "cmsys/SystemTools.hxx"
  5. #include <cassert>
  6. #include <iostream>
  7. #include <set>
  8. #include <unordered_map>
  9. namespace {
  10. void on_directory_change(uv_fs_event_t* handle, const char* filename,
  11. int events, int status);
  12. void on_fs_close(uv_handle_t* handle);
  13. } // namespace
  14. class cmIBaseWatcher
  15. {
  16. public:
  17. cmIBaseWatcher() = default;
  18. virtual ~cmIBaseWatcher() = default;
  19. virtual void Trigger(const std::string& pathSegment, int events,
  20. int status) const = 0;
  21. virtual std::string Path() const = 0;
  22. virtual uv_loop_t* Loop() const = 0;
  23. virtual void StartWatching() = 0;
  24. virtual void StopWatching() = 0;
  25. virtual std::vector<std::string> WatchedFiles() const = 0;
  26. virtual std::vector<std::string> WatchedDirectories() const = 0;
  27. };
  28. class cmVirtualDirectoryWatcher : public cmIBaseWatcher
  29. {
  30. public:
  31. ~cmVirtualDirectoryWatcher() override
  32. {
  33. for (auto i : this->Children) {
  34. delete i.second;
  35. }
  36. }
  37. cmIBaseWatcher* Find(const std::string& ps)
  38. {
  39. const auto i = this->Children.find(ps);
  40. return (i == this->Children.end()) ? nullptr : i->second;
  41. }
  42. void Trigger(const std::string& pathSegment, int events,
  43. int status) const final
  44. {
  45. if (pathSegment.empty()) {
  46. for (const auto& i : this->Children) {
  47. i.second->Trigger(std::string(), events, status);
  48. }
  49. } else {
  50. const auto i = this->Children.find(pathSegment);
  51. if (i != this->Children.end()) {
  52. i->second->Trigger(std::string(), events, status);
  53. }
  54. }
  55. }
  56. void StartWatching() override
  57. {
  58. for (const auto& i : this->Children) {
  59. i.second->StartWatching();
  60. }
  61. }
  62. void StopWatching() override
  63. {
  64. for (const auto& i : this->Children) {
  65. i.second->StopWatching();
  66. }
  67. }
  68. std::vector<std::string> WatchedFiles() const final
  69. {
  70. std::vector<std::string> result;
  71. for (const auto& i : this->Children) {
  72. for (const auto& j : i.second->WatchedFiles()) {
  73. result.push_back(j);
  74. }
  75. }
  76. return result;
  77. }
  78. std::vector<std::string> WatchedDirectories() const override
  79. {
  80. std::vector<std::string> result;
  81. for (const auto& i : this->Children) {
  82. for (const auto& j : i.second->WatchedDirectories()) {
  83. result.push_back(j);
  84. }
  85. }
  86. return result;
  87. }
  88. void Reset()
  89. {
  90. for (auto c : this->Children) {
  91. delete c.second;
  92. }
  93. this->Children.clear();
  94. }
  95. void AddChildWatcher(const std::string& ps, cmIBaseWatcher* watcher)
  96. {
  97. assert(!ps.empty());
  98. assert(this->Children.find(ps) == this->Children.end());
  99. assert(watcher);
  100. this->Children.emplace(std::make_pair(ps, watcher));
  101. }
  102. private:
  103. std::unordered_map<std::string, cmIBaseWatcher*> Children; // owned!
  104. };
  105. // Root of all the different (on windows!) root directories:
  106. class cmRootWatcher : public cmVirtualDirectoryWatcher
  107. {
  108. public:
  109. cmRootWatcher(uv_loop_t* loop)
  110. : mLoop(loop)
  111. {
  112. assert(loop);
  113. }
  114. std::string Path() const final
  115. {
  116. assert(false);
  117. return std::string();
  118. }
  119. uv_loop_t* Loop() const final { return this->mLoop; }
  120. private:
  121. uv_loop_t* const mLoop; // no ownership!
  122. };
  123. // Real directories:
  124. class cmRealDirectoryWatcher : public cmVirtualDirectoryWatcher
  125. {
  126. public:
  127. cmRealDirectoryWatcher(cmVirtualDirectoryWatcher* p, const std::string& ps)
  128. : Parent(p)
  129. , PathSegment(ps)
  130. {
  131. assert(p);
  132. assert(!ps.empty());
  133. p->AddChildWatcher(ps, this);
  134. }
  135. ~cmRealDirectoryWatcher() override
  136. {
  137. // Handle is freed via uv_handle_close callback!
  138. }
  139. void StartWatching() final
  140. {
  141. if (!this->Handle) {
  142. this->Handle = new uv_fs_event_t;
  143. uv_fs_event_init(this->Loop(), this->Handle);
  144. this->Handle->data = this;
  145. uv_fs_event_start(this->Handle, &on_directory_change, Path().c_str(), 0);
  146. }
  147. cmVirtualDirectoryWatcher::StartWatching();
  148. }
  149. void StopWatching() final
  150. {
  151. if (this->Handle) {
  152. uv_fs_event_stop(this->Handle);
  153. uv_close(reinterpret_cast<uv_handle_t*>(this->Handle), &on_fs_close);
  154. this->Handle = nullptr;
  155. }
  156. cmVirtualDirectoryWatcher::StopWatching();
  157. }
  158. uv_loop_t* Loop() const final { return this->Parent->Loop(); }
  159. std::vector<std::string> WatchedDirectories() const override
  160. {
  161. std::vector<std::string> result = { Path() };
  162. for (const auto& j : cmVirtualDirectoryWatcher::WatchedDirectories()) {
  163. result.push_back(j);
  164. }
  165. return result;
  166. }
  167. protected:
  168. cmVirtualDirectoryWatcher* const Parent;
  169. const std::string PathSegment;
  170. private:
  171. uv_fs_event_t* Handle = nullptr; // owner!
  172. };
  173. // Root directories:
  174. class cmRootDirectoryWatcher : public cmRealDirectoryWatcher
  175. {
  176. public:
  177. cmRootDirectoryWatcher(cmRootWatcher* p, const std::string& ps)
  178. : cmRealDirectoryWatcher(p, ps)
  179. {
  180. }
  181. std::string Path() const final { return this->PathSegment; }
  182. };
  183. // Normal directories below root:
  184. class cmDirectoryWatcher : public cmRealDirectoryWatcher
  185. {
  186. public:
  187. cmDirectoryWatcher(cmRealDirectoryWatcher* p, const std::string& ps)
  188. : cmRealDirectoryWatcher(p, ps)
  189. {
  190. }
  191. std::string Path() const final
  192. {
  193. return this->Parent->Path() + this->PathSegment + "/";
  194. }
  195. };
  196. class cmFileWatcher : public cmIBaseWatcher
  197. {
  198. public:
  199. cmFileWatcher(cmRealDirectoryWatcher* p, const std::string& ps,
  200. cmFileMonitor::Callback cb)
  201. : Parent(p)
  202. , PathSegment(ps)
  203. , CbList({ std::move(cb) })
  204. {
  205. assert(p);
  206. assert(!ps.empty());
  207. p->AddChildWatcher(ps, this);
  208. }
  209. void StartWatching() final {}
  210. void StopWatching() final {}
  211. void AppendCallback(cmFileMonitor::Callback const& cb)
  212. {
  213. this->CbList.push_back(cb);
  214. }
  215. std::string Path() const final
  216. {
  217. return this->Parent->Path() + this->PathSegment;
  218. }
  219. std::vector<std::string> WatchedDirectories() const final { return {}; }
  220. std::vector<std::string> WatchedFiles() const final
  221. {
  222. return { this->Path() };
  223. }
  224. void Trigger(const std::string& ps, int events, int status) const final
  225. {
  226. assert(ps.empty());
  227. assert(status == 0);
  228. static_cast<void>(ps);
  229. const std::string path = this->Path();
  230. for (const auto& cb : this->CbList) {
  231. cb(path, events, status);
  232. }
  233. }
  234. uv_loop_t* Loop() const final { return this->Parent->Loop(); }
  235. private:
  236. cmRealDirectoryWatcher* Parent;
  237. const std::string PathSegment;
  238. std::vector<cmFileMonitor::Callback> CbList;
  239. };
  240. namespace {
  241. void on_directory_change(uv_fs_event_t* handle, const char* filename,
  242. int events, int status)
  243. {
  244. const cmIBaseWatcher* const watcher =
  245. static_cast<const cmIBaseWatcher*>(handle->data);
  246. const std::string pathSegment(filename ? filename : "");
  247. watcher->Trigger(pathSegment, events, status);
  248. }
  249. void on_fs_close(uv_handle_t* handle)
  250. {
  251. delete reinterpret_cast<uv_fs_event_t*>(handle);
  252. }
  253. } // namespace
  254. cmFileMonitor::cmFileMonitor(uv_loop_t* l)
  255. : Root(new cmRootWatcher(l))
  256. {
  257. }
  258. cmFileMonitor::~cmFileMonitor()
  259. {
  260. delete this->Root;
  261. }
  262. void cmFileMonitor::MonitorPaths(const std::vector<std::string>& paths,
  263. Callback const& cb)
  264. {
  265. for (const auto& p : paths) {
  266. std::vector<std::string> pathSegments;
  267. cmsys::SystemTools::SplitPath(p, pathSegments, true);
  268. const size_t segmentCount = pathSegments.size();
  269. if (segmentCount < 2) { // Expect at least rootdir and filename
  270. continue;
  271. }
  272. cmVirtualDirectoryWatcher* currentWatcher = this->Root;
  273. for (size_t i = 0; i < segmentCount; ++i) {
  274. assert(currentWatcher);
  275. const bool fileSegment = (i == segmentCount - 1);
  276. const bool rootSegment = (i == 0);
  277. assert(
  278. !(fileSegment &&
  279. rootSegment)); // Can not be both filename and root part of the path!
  280. const std::string& currentSegment = pathSegments[i];
  281. if (currentSegment.empty()) {
  282. continue;
  283. }
  284. cmIBaseWatcher* nextWatcher = currentWatcher->Find(currentSegment);
  285. if (!nextWatcher) {
  286. if (rootSegment) { // Root part
  287. assert(currentWatcher == this->Root);
  288. nextWatcher = new cmRootDirectoryWatcher(this->Root, currentSegment);
  289. assert(currentWatcher->Find(currentSegment) == nextWatcher);
  290. } else if (fileSegment) { // File part
  291. assert(currentWatcher != this->Root);
  292. nextWatcher = new cmFileWatcher(
  293. dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher),
  294. currentSegment, cb);
  295. assert(currentWatcher->Find(currentSegment) == nextWatcher);
  296. } else { // Any normal directory in between
  297. nextWatcher = new cmDirectoryWatcher(
  298. dynamic_cast<cmRealDirectoryWatcher*>(currentWatcher),
  299. currentSegment);
  300. assert(currentWatcher->Find(currentSegment) == nextWatcher);
  301. }
  302. } else {
  303. if (fileSegment) {
  304. auto filePtr = dynamic_cast<cmFileWatcher*>(nextWatcher);
  305. assert(filePtr);
  306. filePtr->AppendCallback(cb);
  307. continue;
  308. }
  309. }
  310. currentWatcher = dynamic_cast<cmVirtualDirectoryWatcher*>(nextWatcher);
  311. }
  312. }
  313. this->Root->StartWatching();
  314. }
  315. void cmFileMonitor::StopMonitoring()
  316. {
  317. this->Root->StopWatching();
  318. this->Root->Reset();
  319. }
  320. std::vector<std::string> cmFileMonitor::WatchedFiles() const
  321. {
  322. std::vector<std::string> result;
  323. if (this->Root) {
  324. result = this->Root->WatchedFiles();
  325. }
  326. return result;
  327. }
  328. std::vector<std::string> cmFileMonitor::WatchedDirectories() const
  329. {
  330. std::vector<std::string> result;
  331. if (this->Root) {
  332. result = this->Root->WatchedDirectories();
  333. }
  334. return result;
  335. }