ctkCmdLineModuleDirectoryWatcher.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. /*=============================================================================
  2. Library: CTK
  3. Copyright (c) University College London
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. =============================================================================*/
  14. #include "ctkCmdLineModuleDirectoryWatcher.h"
  15. #include "ctkCmdLineModuleDirectoryWatcher_p.h"
  16. #include "ctkCmdLineModuleManager.h"
  17. #include "ctkCmdLineModuleConcurrentHelpers.h"
  18. #include "ctkException.h"
  19. #include <QObject>
  20. #include <QFileSystemWatcher>
  21. #include <QDir>
  22. #include <QFile>
  23. #include <QFileInfo>
  24. #include <QUrl>
  25. #include <QDebug>
  26. #include <QtConcurrentMap>
  27. #include <iostream>
  28. //-----------------------------------------------------------------------------
  29. // ctkCmdLineModuleDirectoryWatcher methods
  30. //-----------------------------------------------------------------------------
  31. ctkCmdLineModuleDirectoryWatcher::ctkCmdLineModuleDirectoryWatcher(ctkCmdLineModuleManager* moduleManager)
  32. : d(new ctkCmdLineModuleDirectoryWatcherPrivate(moduleManager))
  33. {
  34. Q_ASSERT(moduleManager);
  35. }
  36. //-----------------------------------------------------------------------------
  37. ctkCmdLineModuleDirectoryWatcher::~ctkCmdLineModuleDirectoryWatcher()
  38. {
  39. }
  40. //-----------------------------------------------------------------------------
  41. void ctkCmdLineModuleDirectoryWatcher::setDebug(bool debug)
  42. {
  43. d->setDebug(debug);
  44. }
  45. //-----------------------------------------------------------------------------
  46. void ctkCmdLineModuleDirectoryWatcher::setDirectories(const QStringList& directories)
  47. {
  48. d->setDirectories(directories);
  49. }
  50. //-----------------------------------------------------------------------------
  51. QStringList ctkCmdLineModuleDirectoryWatcher::directories() const
  52. {
  53. return d->directories();
  54. }
  55. //-----------------------------------------------------------------------------
  56. void ctkCmdLineModuleDirectoryWatcher::setAdditionalModules(const QStringList& modules)
  57. {
  58. d->setAdditionalModules(modules);
  59. }
  60. //-----------------------------------------------------------------------------
  61. QStringList ctkCmdLineModuleDirectoryWatcher::additionalModules() const
  62. {
  63. return d->additionalModules();
  64. }
  65. //-----------------------------------------------------------------------------
  66. QStringList ctkCmdLineModuleDirectoryWatcher::commandLineModules() const
  67. {
  68. return d->commandLineModules();
  69. }
  70. //-----------------------------------------------------------------------------
  71. // ctkCmdLineModuleDirectoryWatcherPrivate methods
  72. //-----------------------------------------------------------------------------
  73. ctkCmdLineModuleDirectoryWatcherPrivate::ctkCmdLineModuleDirectoryWatcherPrivate(ctkCmdLineModuleManager* moduleManager)
  74. : ModuleManager(moduleManager)
  75. , FileSystemWatcher(NULL)
  76. , Debug(false)
  77. {
  78. FileSystemWatcher = new QFileSystemWatcher();
  79. connect(this->FileSystemWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onFileChanged(QString)));
  80. connect(this->FileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(onDirectoryChanged(QString)));
  81. }
  82. //-----------------------------------------------------------------------------
  83. ctkCmdLineModuleDirectoryWatcherPrivate::~ctkCmdLineModuleDirectoryWatcherPrivate()
  84. {
  85. delete this->FileSystemWatcher;
  86. }
  87. //-----------------------------------------------------------------------------
  88. void ctkCmdLineModuleDirectoryWatcherPrivate::setDebug(bool debug)
  89. {
  90. this->Debug = debug;
  91. }
  92. //-----------------------------------------------------------------------------
  93. void ctkCmdLineModuleDirectoryWatcherPrivate::setDirectories(const QStringList& directories)
  94. {
  95. QStringList validDirectories = this->filterInvalidDirectories(directories);
  96. this->setModules(validDirectories);
  97. this->updateWatchedPaths(validDirectories, this->MapFileNameToReference.keys());
  98. }
  99. //-----------------------------------------------------------------------------
  100. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::directories() const
  101. {
  102. return this->FileSystemWatcher->directories();
  103. }
  104. //-----------------------------------------------------------------------------
  105. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::commandLineModules() const
  106. {
  107. // So, the commandLineModules() method returns all files registered with
  108. // QFileSystemWatcher, which means we must filter out any invalid ones before
  109. // asking QFileSystemWatcher to watch them.
  110. return this->FileSystemWatcher->files();
  111. }
  112. //-----------------------------------------------------------------------------
  113. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::additionalModules() const
  114. {
  115. // So, in comparison to commandLineModules(), we store the list of
  116. // modules that are watched in addition to the directories.
  117. return this->AdditionalModules;
  118. }
  119. //-----------------------------------------------------------------------------
  120. void ctkCmdLineModuleDirectoryWatcherPrivate::setAdditionalModules(const QStringList& executables)
  121. {
  122. QStringList filteredFileNames = this->filterFilesNotInCurrentDirectories(executables);
  123. QStringList filteredAdditionalModules = this->filterFilesNotInCurrentDirectories(this->AdditionalModules);
  124. this->unloadModules(filteredAdditionalModules);
  125. QList<ctkCmdLineModuleReference> refs = this->loadModules(filteredFileNames);
  126. QStringList validFileNames;
  127. for (int i = 0; i < refs.size(); ++i)
  128. {
  129. if (refs[i])
  130. {
  131. validFileNames << refs[i].location().toLocalFile();
  132. }
  133. }
  134. this->AdditionalModules = validFileNames;
  135. this->updateWatchedPaths(this->directories(), this->MapFileNameToReference.keys());
  136. if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::setAdditionalModules watching:" << this->AdditionalModules;
  137. }
  138. //-----------------------------------------------------------------------------
  139. void ctkCmdLineModuleDirectoryWatcherPrivate::updateWatchedPaths(const QStringList& directories, const QStringList& files)
  140. {
  141. // This method is the main interface to QFileSystemWatcher. The input parameters
  142. // directories, and files are quite simply what is being watched. So all directories
  143. // and all files must be valid examples of things to watch.
  144. QStringList currentDirectories = this->directories();
  145. QStringList currentCommandLineModules = this->commandLineModules();
  146. if (currentDirectories.size() > 0)
  147. {
  148. this->FileSystemWatcher->removePaths(currentDirectories);
  149. }
  150. if (currentCommandLineModules.size() > 0)
  151. {
  152. this->FileSystemWatcher->removePaths(currentCommandLineModules);
  153. }
  154. if (directories.size() > 0)
  155. {
  156. this->FileSystemWatcher->addPaths(directories);
  157. }
  158. if (files.size() > 0)
  159. {
  160. this->FileSystemWatcher->addPaths(files);
  161. }
  162. if (this->Debug)
  163. {
  164. qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::updateWatchedPaths watching directories:\n" << directories << "\n and files:\n" << files;
  165. }
  166. }
  167. //-----------------------------------------------------------------------------
  168. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::filterInvalidDirectories(const QStringList& directories) const
  169. {
  170. QStringList result;
  171. QString path;
  172. foreach (path, directories)
  173. {
  174. if (!path.isNull() && !path.isEmpty() && !path.trimmed().isEmpty())
  175. {
  176. QDir dir = QDir(path);
  177. if (dir.exists())
  178. {
  179. result << dir.absolutePath();
  180. }
  181. }
  182. }
  183. return result;
  184. }
  185. //-----------------------------------------------------------------------------
  186. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::filterFilesNotInCurrentDirectories(const QStringList& filenames) const
  187. {
  188. QStringList currentDirectories = this->directories();
  189. QStringList filteredFileNames;
  190. for (int i = 0; i < filenames.size(); i++)
  191. {
  192. QFileInfo fileInfo(filenames[i]);
  193. if (fileInfo.exists() && !(currentDirectories.contains(fileInfo.absolutePath())))
  194. {
  195. filteredFileNames << fileInfo.absoluteFilePath();
  196. }
  197. }
  198. return filteredFileNames;
  199. }
  200. //-----------------------------------------------------------------------------
  201. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::getExecutablesInDirectory(const QString& path) const
  202. {
  203. QStringList result;
  204. QString executable;
  205. QFileInfo executableFileInfo;
  206. QDir dir = QDir(path);
  207. if (dir.exists())
  208. {
  209. dir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::Executable);
  210. QFileInfoList executablesFileInfoList = dir.entryInfoList();
  211. foreach (executableFileInfo, executablesFileInfoList)
  212. {
  213. executable = executableFileInfo.absoluteFilePath();
  214. result << executable;
  215. }
  216. }
  217. return result;
  218. }
  219. //-----------------------------------------------------------------------------
  220. QStringList ctkCmdLineModuleDirectoryWatcherPrivate::extractCurrentlyWatchedFilenamesInDirectory(const QString& path) const
  221. {
  222. QStringList result;
  223. QDir dir(path);
  224. if (dir.exists())
  225. {
  226. QList<QString> keys = this->MapFileNameToReference.keys();
  227. QString fileName;
  228. foreach(fileName, keys)
  229. {
  230. QFileInfo fileInfo(fileName);
  231. if (fileInfo.absolutePath() == dir.absolutePath())
  232. {
  233. result << fileInfo.absoluteFilePath();
  234. }
  235. }
  236. }
  237. return result;
  238. }
  239. //-----------------------------------------------------------------------------
  240. void ctkCmdLineModuleDirectoryWatcherPrivate::setModules(const QStringList &directories)
  241. {
  242. // Note: This method, is called from setDirectories and updateModules,
  243. // so the input directories list may be longer or shorter than the currently watched directories.
  244. // In addition, within those directories, programs may have been added/removed.
  245. QString path;
  246. QStringList currentlyWatchedDirectories = this->directories();
  247. QStringList modulesToUnload;
  248. QStringList modulesToLoad;
  249. // First remove modules from current directories that are no longer in the requested "directories" list.
  250. foreach (path, currentlyWatchedDirectories)
  251. {
  252. if (!directories.contains(path))
  253. {
  254. QStringList currentlyWatchedFiles = this->extractCurrentlyWatchedFilenamesInDirectory(path);
  255. QString filename;
  256. foreach (filename, currentlyWatchedFiles)
  257. {
  258. modulesToUnload << filename;
  259. }
  260. }
  261. }
  262. // Now for each requested directory.
  263. foreach (path, directories)
  264. {
  265. // Existing folder.
  266. if (currentlyWatchedDirectories.contains(path))
  267. {
  268. QStringList currentlyWatchedFiles = this->extractCurrentlyWatchedFilenamesInDirectory(path);
  269. QStringList executablesInDirectory = this->getExecutablesInDirectory(path);
  270. QString executable;
  271. foreach (executable, currentlyWatchedFiles)
  272. {
  273. if (!executablesInDirectory.contains(executable))
  274. {
  275. modulesToUnload << executable;
  276. }
  277. }
  278. foreach(executable, executablesInDirectory)
  279. {
  280. if (!currentlyWatchedFiles.contains(executable))
  281. {
  282. modulesToLoad << executable;
  283. }
  284. }
  285. }
  286. else
  287. {
  288. // New folder
  289. QStringList executables = this->getExecutablesInDirectory(path);
  290. QString executable;
  291. foreach (executable, executables)
  292. {
  293. modulesToLoad << executable;
  294. }
  295. }
  296. }
  297. this->unloadModules(modulesToUnload);
  298. this->loadModules(modulesToLoad);
  299. }
  300. //-----------------------------------------------------------------------------
  301. void ctkCmdLineModuleDirectoryWatcherPrivate::updateModules(const QString &directory)
  302. {
  303. // Note: If updateModules is only called from onDirectoryChanged which is only called
  304. // when an EXISTING directory is updated, then this if clause should never be true.
  305. QStringList currentlyWatchedDirectories = this->directories();
  306. if (!currentlyWatchedDirectories.contains(directory))
  307. {
  308. currentlyWatchedDirectories << directory;
  309. }
  310. this->setModules(currentlyWatchedDirectories);
  311. this->updateWatchedPaths(currentlyWatchedDirectories, this->MapFileNameToReference.keys());
  312. }
  313. //-----------------------------------------------------------------------------
  314. QList<ctkCmdLineModuleReference> ctkCmdLineModuleDirectoryWatcherPrivate::loadModules(const QStringList& executables)
  315. {
  316. QList<ctkCmdLineModuleReference> refs = QtConcurrent::blockingMapped(executables,
  317. ctkCmdLineModuleConcurrentRegister(this->ModuleManager, this->Debug));
  318. for (int i = 0; i < executables.size(); ++i)
  319. {
  320. if (refs[i])
  321. {
  322. this->MapFileNameToReference[executables[i]] = refs[i];
  323. }
  324. }
  325. return refs;
  326. }
  327. //-----------------------------------------------------------------------------
  328. void ctkCmdLineModuleDirectoryWatcherPrivate::unloadModules(const QStringList& executables)
  329. {
  330. QtConcurrent::blockingMapped(executables, ctkCmdLineModuleConcurrentUnRegister(this->ModuleManager));
  331. foreach(QString executable, executables)
  332. {
  333. this->MapFileNameToReference.remove(executable);
  334. }
  335. }
  336. //-----------------------------------------------------------------------------
  337. void ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(const QString& path)
  338. {
  339. ctkCmdLineModuleReference ref = this->loadModules(QStringList() << path).front();
  340. if (ref)
  341. {
  342. if (this->Debug) qDebug() << "Reloaded " << path;
  343. }
  344. else
  345. {
  346. if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(" << path << "): failed to load module";
  347. }
  348. }
  349. //-----------------------------------------------------------------------------
  350. void ctkCmdLineModuleDirectoryWatcherPrivate::onDirectoryChanged(const QString &path)
  351. {
  352. QStringList directories;
  353. directories << path;
  354. QStringList validDirectories = this->filterInvalidDirectories(directories);
  355. if (validDirectories.size() > 0)
  356. {
  357. updateModules(path);
  358. if (this->Debug) qDebug() << "Reloaded modules in" << path;
  359. }
  360. else
  361. {
  362. if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::onDirectoryChanged(" << path << "): failed to load modules, as path invalid.";
  363. }
  364. }