cmFileMonitor.cxx 9.7 KB

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