ctkCmdLineModuleDirectoryWatcher.cpp 15 KB

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