瀏覽代碼

Added new class ctkCmdLineModuleDirectoryWatcher

Conflicts:

	Libs/CommandLineModules/Core/CMakeLists.txt
MattClarkson 13 年之前
父節點
當前提交
45d8e97bb1

+ 3 - 0
Libs/CommandLineModules/Core/CMakeLists.txt

@@ -17,6 +17,7 @@ set(KIT_SRCS
   ctkCmdLineModuleDefaultPathBuilder.cpp
   ctkCmdLineModuleDescription.cpp
   ctkCmdLineModuleDescriptionPrivate.h
+  ctkCmdLineModuleDirectoryWatcher.cpp
   ctkCmdLineModuleInstance.cpp
   ctkCmdLineModuleInstanceFactory.cpp
   ctkCmdLineModuleManager.cpp
@@ -42,6 +43,8 @@ set(KIT_SRCS
 
 # Headers that should run through moc
 set(KIT_MOC_SRCS
+  ctkCmdLineModuleDirectoryWatcher.h
+  ctkCmdLineModuleDirectoryWatcherPrivate.h
   ctkCmdLineModuleInstance.h
   ctkCmdLineModuleProcessTask.h
 )

+ 392 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher.cpp

@@ -0,0 +1,392 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) University College London
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#include "ctkCmdLineModuleDirectoryWatcher.h"
+#include "ctkCmdLineModuleDirectoryWatcherPrivate.h"
+#include "ctkCmdLineModuleManager.h"
+#include <QObject>
+#include <QFileSystemWatcher>
+#include <QDir>
+#include <QFile>
+#include <QFileInfo>
+#include <QDebug>
+#include <iostream>
+
+//-----------------------------------------------------------------------------
+// ctkCmdLineModuleDirectoryWatcher methods
+
+//-----------------------------------------------------------------------------
+ctkCmdLineModuleDirectoryWatcher::ctkCmdLineModuleDirectoryWatcher(ctkCmdLineModuleManager* moduleManager)
+  : d(new ctkCmdLineModuleDirectoryWatcherPrivate(moduleManager))
+{
+  connect(d.data(), SIGNAL(modulesChanged()), this, SLOT(onModulesChanged()));
+}
+
+
+//-----------------------------------------------------------------------------
+ctkCmdLineModuleDirectoryWatcher::~ctkCmdLineModuleDirectoryWatcher()
+{
+
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcher::setDebug(const bool& debug)
+{
+  d->setDebug(debug);
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcher::setDirectories(const QStringList& directories)
+{
+  d->setDirectories(directories);
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcher::directories()
+{
+  return d->directories();
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcher::files()
+{
+  return d->files();
+}
+
+
+//-----------------------------------------------------------------------------
+QHash<QString, ctkCmdLineModuleReference> ctkCmdLineModuleDirectoryWatcher::filenameToReferenceMap() const
+{
+  return d->filenameToReferenceMap();
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcher::onModulesChanged()
+{
+  emit modulesChanged();
+}
+
+//-----------------------------------------------------------------------------
+// ctkCmdLineModuleDirectoryWatcherPrivate methods
+
+
+//-----------------------------------------------------------------------------
+ctkCmdLineModuleDirectoryWatcherPrivate::ctkCmdLineModuleDirectoryWatcherPrivate(ctkCmdLineModuleManager* moduleManager)
+: ModuleManager(moduleManager)
+, FileSystemWatcher(NULL)
+, Debug(false)
+{
+  FileSystemWatcher = new QFileSystemWatcher();
+
+  connect(this->FileSystemWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onFileChanged(QString)));
+  connect(this->FileSystemWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(onDirectoryChanged(QString)));
+}
+
+
+//-----------------------------------------------------------------------------
+ctkCmdLineModuleDirectoryWatcherPrivate::~ctkCmdLineModuleDirectoryWatcherPrivate()
+{
+  delete this->FileSystemWatcher;
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::setDebug(const bool& debug)
+{
+  this->Debug = debug;
+}
+
+
+//-----------------------------------------------------------------------------
+QHash<QString, ctkCmdLineModuleReference> ctkCmdLineModuleDirectoryWatcherPrivate::filenameToReferenceMap() const
+{
+  return this->MapFileNameToReference;
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcherPrivate::directories()
+{
+  return this->FileSystemWatcher->directories();
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcherPrivate::files()
+{
+  return this->FileSystemWatcher->files();
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::setDirectories(const QStringList& directories)
+{
+  QStringList validDirectories = this->filterInvalidDirectories(directories);
+  this->setModuleReferences(validDirectories);
+  this->updateWatchedPaths(validDirectories, this->MapFileNameToReference.keys());
+  emit modulesChanged();
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::updateWatchedPaths(const QStringList& directories, const QStringList& files)
+{
+  QStringList currentDirectories = this->directories();
+  QStringList currentFiles = this->files();
+
+  if (currentDirectories.size() > 0)
+  {
+    this->FileSystemWatcher->removePaths(currentDirectories);
+  }
+  if (currentFiles.size() > 0)
+  {
+    this->FileSystemWatcher->removePaths(currentFiles);
+  }
+
+  if (directories.size() > 0)
+  {
+    this->FileSystemWatcher->addPaths(directories);
+  }
+  if (files.size() > 0)
+  {
+    this->FileSystemWatcher->addPaths(files);
+  }
+}
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcherPrivate::filterInvalidDirectories(const QStringList& directories)
+{
+  QStringList result;
+
+  QString path;
+  foreach (path, directories)
+  {
+    if (!path.isNull() && !path.isEmpty() && !path.trimmed().isEmpty())
+    {
+      QDir dir = QDir(path);
+      if (dir.exists())
+      {
+        result << dir.absolutePath();
+      }
+    }
+  }
+
+  return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcherPrivate::extractCurrentlyWatchedFilenamesInDirectory(const QString& path)
+{
+  QStringList result;
+
+  QDir dir(path);
+  if (dir.exists())
+  {
+    QList<QString> keys = this->MapFileNameToReference.keys();
+
+    QString fileName;
+    foreach(fileName, keys)
+    {
+      QFileInfo fileInfo(fileName);
+      if (fileInfo.absolutePath() == dir.absolutePath())
+      {
+        result << fileInfo.absoluteFilePath();
+      }
+    }
+  }
+
+  return result;
+}
+
+
+//-----------------------------------------------------------------------------
+QStringList ctkCmdLineModuleDirectoryWatcherPrivate::getExecutablesInDirectory(const QString& path)
+{
+  QStringList result;
+
+  QString executable;
+  QFileInfo executableFileInfo;
+
+  QDir dir = QDir(path);
+  if (dir.exists())
+  {
+    dir.setFilter(QDir::Files | QDir::NoDotAndDotDot | QDir::Executable);
+    QFileInfoList executablesFileInfoList = dir.entryInfoList();
+
+    foreach (executableFileInfo, executablesFileInfoList)
+    {
+      executable = executableFileInfo.absoluteFilePath();
+      result << executable;
+    }
+  }
+
+  return result;
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::setModuleReferences(const QStringList &directories)
+{
+  // Note: This method, is called from setDirectories and updateModuleReferences,
+  // so the input directories list may be longer or shorter than the currently watched directories.
+  // In addition, within those directories, programs may have been added/removed.
+
+  QString path;
+  QStringList currentlyWatchedDirectories = this->directories();
+
+  // First remove modules from current directories that are no longer in the requested "directories" list.
+  foreach (path, currentlyWatchedDirectories)
+  {
+    if (!directories.contains(path))
+    {
+      QStringList currentlyWatchedFiles = this->extractCurrentlyWatchedFilenamesInDirectory(path);
+
+      QString filename;
+      foreach (filename, currentlyWatchedFiles)
+      {
+        this->unloadModule(filename);
+      }
+    }
+  }
+
+  // Now for each requested directory.
+  foreach (path, directories)
+  {
+    // Existing folder.
+    if (currentlyWatchedDirectories.contains(path))
+    {
+      QStringList currentlyWatchedFiles = this->extractCurrentlyWatchedFilenamesInDirectory(path);
+      QStringList executablesInDirectory = this->getExecutablesInDirectory(path);
+
+      QString executable;
+      foreach (executable, currentlyWatchedFiles)
+      {
+        if (!executablesInDirectory.contains(executable))
+        {
+          this->unloadModule(executable);
+        }
+      }
+
+      foreach(executable, executablesInDirectory)
+      {
+        if (!currentlyWatchedFiles.contains(executable))
+        {
+          this->loadModule(executable);
+        }
+      }
+    }
+    else
+    {
+      // New folder
+      QStringList executables = this->getExecutablesInDirectory(path);
+
+      QString executable;
+      foreach (executable, executables)
+      {
+        this->loadModule(executable);
+      }
+    }
+  }
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::updateModuleReferences(const QString &directory)
+{
+  // Note: If updateModuleReferences is only called from onDirectoryChanged which is only called
+  // when an EXISTING directory is updated, then this if clause should never be true.
+
+  QStringList currentlyWatchedDirectories = this->directories();
+  if (!currentlyWatchedDirectories.contains(directory))
+  {
+    currentlyWatchedDirectories << directory;
+  }
+  this->setModuleReferences(currentlyWatchedDirectories);
+}
+
+
+//-----------------------------------------------------------------------------
+ctkCmdLineModuleReference ctkCmdLineModuleDirectoryWatcherPrivate::loadModule(const QString& pathToExecutable)
+{
+  ctkCmdLineModuleReference ref = this->ModuleManager->registerModule(pathToExecutable, !this->Debug);
+  if (ref)
+  {
+    this->MapFileNameToReference[pathToExecutable] = ref;
+  }
+  return ref;
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::unloadModule(const QString& pathToExecutable)
+{
+  ctkCmdLineModuleReference ref = this->ModuleManager->moduleReference(pathToExecutable);
+  if (ref)
+  {
+    this->ModuleManager->unregisterModule(ref);
+    this->MapFileNameToReference.remove(pathToExecutable);
+  }
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(const QString& path)
+{
+  ctkCmdLineModuleReference ref = this->loadModule(path);
+  if (ref)
+  {
+    if (this->Debug) qDebug() << "Reloaded " << path;
+    emit modulesChanged();
+  }
+  else
+  {
+    if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::onFileChanged(" << path << "): failed to load module";
+  }
+}
+
+
+//-----------------------------------------------------------------------------
+void ctkCmdLineModuleDirectoryWatcherPrivate::onDirectoryChanged(const QString &path)
+{
+  QStringList directories;
+  directories << path;
+
+  QStringList validDirectories = this->filterInvalidDirectories(directories);
+
+  if (validDirectories.size() > 0)
+  {
+    updateModuleReferences(path);
+
+    if (this->Debug) qDebug() << "Reloaded modules in" << path;
+    emit modulesChanged();
+  }
+  else
+  {
+    if (this->Debug) qDebug() << "ctkCmdLineModuleDirectoryWatcherPrivate::onDirectoryChanged(" << path << "): failed to load modules, as path invalid.";
+  }
+}
+
+

+ 100 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcher.h

@@ -0,0 +1,100 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) University College London
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef __ctkCmdLineModuleDirectoryWatcher_h
+#define __ctkCmdLineModuleDirectoryWatcher_h
+
+#include <ctkCommandLineModulesCoreExport.h>
+#include <QObject>
+#include <QHash>
+#include <QStringList>
+#include <QScopedPointer>
+#include "ctkCmdLineModuleReference.h"
+
+class ctkCmdLineModuleManager;
+class ctkCmdLineModuleDirectoryWatcherPrivate;
+
+/**
+ * \class ctkCmdLineModuleDirectoryWatcher
+ * \brief Provides directory scanning to load new modules into a ctkCmdLineModuleManager.
+ * \ingroup CommandLineModulesCore
+ * \author m.clarkson@ucl.ac.uk
+ *
+ * This class provides directory scanning and automatic loading of command line modules.
+ * The client should call setDirectories() to set the list of directories, and listen
+ * to the signal modulesChanged to know when to re-build the GUI representation.
+ */
+class CTK_CMDLINEMODULECORE_EXPORT ctkCmdLineModuleDirectoryWatcher : public QObject
+{
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleDirectoryWatcher(ctkCmdLineModuleManager* moduleManager);
+  virtual ~ctkCmdLineModuleDirectoryWatcher();
+
+  /**
+   * \brief Set the watcher into debug mode, for more output.
+   * \param debug if true, you get more output, otherwise, less output.
+   */
+  void setDebug(const bool& debug);
+
+  /**
+   * \brief Set the directories to be watched.
+   * \param directories a StringList of directory names. If any of these are invalid, they will be filtered out and ignored.
+   */
+  void setDirectories(const QStringList& directories);
+
+  /**
+   * \brief Returns the list of directories currently being watched.
+   */
+  QStringList directories();
+
+  /**
+   * \brief Returns the list of files (command line apps) currently being watched.
+   */
+  QStringList files();
+
+  /**
+   * \brief Retrieves a map of filenames (command line apps) and their command line module reference.
+   */
+  QHash<QString, ctkCmdLineModuleReference> filenameToReferenceMap() const;
+
+Q_SIGNALS:
+
+  /**
+   * \brief Signals that the modules have changed, so GUI's can re-build their menus.
+   */
+  void modulesChanged();
+
+private Q_SLOTS:
+
+  /**
+   * \brief Private slot, so we can connect to the ctkCmdLineModuleDirectoryWatcherPrivate::modulesChanged signal.
+   */
+  void onModulesChanged();
+
+private:
+
+  QScopedPointer<ctkCmdLineModuleDirectoryWatcherPrivate> d;
+  Q_DISABLE_COPY(ctkCmdLineModuleDirectoryWatcher)
+};
+
+#endif // __ctkCmdLineModuleDirectoryWatcher_h

+ 151 - 0
Libs/CommandLineModules/Core/ctkCmdLineModuleDirectoryWatcherPrivate.h

@@ -0,0 +1,151 @@
+/*=============================================================================
+
+  Library: CTK
+
+  Copyright (c) University College London
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+
+=============================================================================*/
+
+#ifndef __ctkCmdLineModuleDirectoryWatcherPrivate_h
+#define __ctkCmdLineModuleDirectoryWatcherPrivate_h
+
+#include <QHash>
+#include <QString>
+#include <QStringList>
+#include <QFileInfoList>
+
+#include "ctkCmdLineModuleReference.h"
+#include "ctkCmdLineModuleDirectoryWatcher.h"
+
+class QFileSystemWatcher;
+
+/**
+ * \class ctkCmdLineModuleDirectoryWatcherPrivate
+ * \brief Private implementation class implementing directory scanning to load new modules into a ctkCmdLineModuleManager.
+ * \ingroup CommandLineModulesCore
+ * \author m.clarkson@ucl.ac.uk
+ */
+class ctkCmdLineModuleDirectoryWatcherPrivate : public QObject
+{
+
+  Q_OBJECT
+
+public:
+
+  ctkCmdLineModuleDirectoryWatcherPrivate(ctkCmdLineModuleManager* ModuleManager);
+  virtual ~ctkCmdLineModuleDirectoryWatcherPrivate();
+
+  /**
+   * \see ctkCmdLineModuleDirectoryWatcher::setDebug
+   */
+  void setDebug(const bool& debug);
+
+  /**
+   * \see ctkCmdLineModuleDirectoryWatcher::setDirectories
+   */
+  void setDirectories(const QStringList& directories);
+
+  /**
+   * \see ctkCmdLineModuleDirectoryWatcher::directories
+   */
+  QStringList directories();
+
+  /**
+   * \see ctkCmdLineModuleDirectoryWatcher::files
+   */
+  QStringList files();
+
+  /**
+   * \see ctkCmdLineModuleDirectoryWatcher::filenameToReferenceMap
+   */
+  QHash<QString, ctkCmdLineModuleReference> filenameToReferenceMap() const;
+
+Q_SIGNALS:
+
+  /**
+   * \brief Used to signal to ctkCmdLineModuleDirectoryWatcher public class.
+   */
+  void modulesChanged();
+
+public Q_SLOTS:
+
+  /**
+   * \brief We connect QFileSystemWatcher to here.
+   */
+  void onFileChanged(const QString& path);
+
+  /**
+   * \brief We connect QFileSystemWatcher to here.
+   */
+  void onDirectoryChanged(const QString &path);
+
+private:
+
+  /**
+   * \brief Used to update the QFileSystemWatcher with the right list of directories and files to watch.
+   * \param directories list of absolute directory paths
+   * \param files list of absolute file paths
+   */
+  void updateWatchedPaths(const QStringList& directories, const QStringList& files);
+
+  /**
+   * \brief Takes a list of directories, and only returns ones that are valid,
+   * meaning that the directory name is non-null, non-empty, and the directory exists.
+   * \param directories a list of directories, relative or absolute.
+   * \return a list of directories, denoted by their absolute path.
+   */
+  QStringList filterInvalidDirectories(const QStringList& directories);
+
+  /**
+   * \brief Uses the MapFileNameToReference to work out a list of valid command line modules in a given directory.
+   * \param directory the absolute or relative path of a directory.
+   * \return a list of executables, denoted by their absolute path.
+   */
+  QStringList extractCurrentlyWatchedFilenamesInDirectory(const QString& directory);
+  QStringList getExecutablesInDirectory(const QString& directory);
+
+  /**
+   * \brief Main method to update the current list of watched directories and files.
+   * \param directories a list of directories, denoted by their absolute path.
+   */
+  void setModuleReferences(const QStringList &directories);
+
+  /**
+   * \brief Called from the onDirectoryChanged slot to update the current list by calling back to setModuleReferences.
+   * \param directory denoted by its absolute path.
+   */
+  void updateModuleReferences(const QString &directory);
+
+  /**
+   * \brief Uses the ctkCmdLineModuleManager to try and add the executable to the list
+   * of executables, and if successful it is added to this->MapFileNameToReference.
+   * \param pathToExecutable path to an executable file, denoted by its absolute path.
+   */
+  ctkCmdLineModuleReference loadModule(const QString& pathToExecutable);
+
+  /**
+   * \brief Removes the executable from both the ctkCmdLineModuleManager and this->MapFileNameToReference.
+   * \param pathToExecutable path to an executable file, denoted by its absolute path.
+   */
+  void unloadModule(const QString& pathToExecutable);
+
+  QHash<QString, ctkCmdLineModuleReference> MapFileNameToReference;
+  ctkCmdLineModuleManager* ModuleManager;
+  QFileSystemWatcher* FileSystemWatcher;
+  bool Debug;
+};
+
+#endif
+