summaryrefslogtreecommitdiffstats
path: root/src/uscxml/plugins/invoker/dirmon
diff options
context:
space:
mode:
authorStefan Radomski <github@mintwerk.de>2016-06-13 08:52:55 (GMT)
committerStefan Radomski <github@mintwerk.de>2016-06-13 08:52:55 (GMT)
commit053e9bc973fbe88fc41a34064ffadc0deabac58d (patch)
tree6aeee286577159ffcb612d41972a9d18ab685c6d /src/uscxml/plugins/invoker/dirmon
parent6e13c7b6e0888323223afd5d2e36e86243df57af (diff)
downloaduscxml-053e9bc973fbe88fc41a34064ffadc0deabac58d.zip
uscxml-053e9bc973fbe88fc41a34064ffadc0deabac58d.tar.gz
uscxml-053e9bc973fbe88fc41a34064ffadc0deabac58d.tar.bz2
Fixed dozens of memory leaks
Diffstat (limited to 'src/uscxml/plugins/invoker/dirmon')
-rw-r--r--src/uscxml/plugins/invoker/dirmon/DirMonInvoker.cpp449
-rw-r--r--src/uscxml/plugins/invoker/dirmon/DirMonInvoker.h140
2 files changed, 589 insertions, 0 deletions
diff --git a/src/uscxml/plugins/invoker/dirmon/DirMonInvoker.cpp b/src/uscxml/plugins/invoker/dirmon/DirMonInvoker.cpp
new file mode 100644
index 0000000..f3b429f
--- /dev/null
+++ b/src/uscxml/plugins/invoker/dirmon/DirMonInvoker.cpp
@@ -0,0 +1,449 @@
+/**
+ * @file
+ * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de)
+ * @copyright Simplified BSD
+ *
+ * @cond
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the FreeBSD license as published by the FreeBSD
+ * project.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the FreeBSD license along with this
+ * program. If not, see <http://www.opensource.org/licenses/bsd-license>.
+ * @endcond
+ */
+
+#include "DirMonInvoker.h"
+
+#include "uscxml/config.h"
+
+#ifdef BUILD_AS_PLUGINS
+#include <Pluma/Connector.hpp>
+#endif
+
+#include <sys/stat.h>
+#ifndef WIN32
+#include <dirent.h>
+#else
+#include <strsafe.h>
+#endif
+
+#include <boost/algorithm/string.hpp>
+#include <easylogging++.h>
+
+namespace uscxml {
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_CONNECTOR
+bool pluginConnect(pluma::Host& host) {
+ host.add( new DirMonInvokerProvider() );
+ return true;
+}
+#endif
+
+DirMonInvoker::DirMonInvoker() :
+ _reportExisting(true),
+ _reportHidden(false),
+ _recurse(false),
+ _thread(NULL),
+ _watcher(NULL) {
+}
+
+DirMonInvoker::~DirMonInvoker() {
+ _isRunning = false;
+ if (_thread) {
+ _thread->join();
+ delete _thread;
+ }
+ if (_watcher)
+ delete(_watcher);
+};
+
+std::shared_ptr<InvokerImpl> DirMonInvoker::create(InterpreterImpl* interpreter) {
+ std::shared_ptr<DirMonInvoker> invoker(new DirMonInvoker());
+ invoker->_interpreter = interpreter;
+ return invoker;
+}
+
+Data DirMonInvoker::getDataModelVariables() {
+ std::lock_guard<std::recursive_mutex> lock(_mutex);
+
+ Data data;
+ data.compound["dir"] = Data(_dir, Data::VERBATIM);
+
+ std::set<std::string>::iterator suffixIter = _suffixes.begin();
+ while(suffixIter != _suffixes.end()) {
+ data.compound["suffixes"].array.push_back(Data(*suffixIter, Data::VERBATIM));
+ suffixIter++;
+ }
+
+ std::map<std::string, struct stat> entries = _watcher->getAllEntries();
+ std::map<std::string, struct stat>::iterator entryIter = entries.begin();
+ while(entryIter != entries.end()) {
+ data.compound["file"].compound[entryIter->first].compound["mtime"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED);
+ data.compound["file"].compound[entryIter->first].compound["ctime"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED);
+ data.compound["file"].compound[entryIter->first].compound["atime"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED);
+ data.compound["file"].compound[entryIter->first].compound["size"] = Data(toStr(entryIter->second.st_mtime), Data::INTERPRETED);
+ entryIter++;
+ }
+
+ return data;
+}
+
+void DirMonInvoker::eventFromSCXML(const Event& event) {
+}
+
+void DirMonInvoker::invoke(const std::string& source, const Event& req) {
+ if (req.params.find("dir") == req.params.end()) {
+ LOG(ERROR) << "No dir param given";
+ return;
+ }
+
+ if (req.params.find("reportexisting") != req.params.end() &&
+ iequals(req.params.find("reportexisting")->second.atom, "false"))
+ _reportExisting = false;
+ if (req.params.find("recurse") != req.params.end() &&
+ iequals(req.params.find("recurse")->second.atom, "true"))
+ _recurse = true;
+ if (req.params.find("reporthidden") != req.params.end() &&
+ iequals(req.params.find("reporthidden")->second.atom, "true"))
+ _reportHidden = true;
+
+ std::string suffixList;
+ if (req.params.find("suffix") != req.params.end()) {
+ suffixList = req.params.find("suffix")->second.atom;
+ } else if (req.params.find("suffixes") != req.params.end()) {
+ suffixList = req.params.find("suffixes")->second.atom;
+ }
+
+ if (suffixList.size() > 0) {
+ // seperate path into components
+ std::stringstream ss(suffixList);
+ std::string item;
+ while(std::getline(ss, item, ' ')) {
+ if (item.length() == 0)
+ continue;
+ _suffixes.insert(item);
+ }
+ }
+
+ std::multimap<std::string, Data>::const_iterator dirIter = req.params.find("dir");
+ while(dirIter != req.params.upper_bound("dir")) {
+ // this is simplified - Data might be more elaborate than a simple string atom
+ URL url = URL::resolve(dirIter->second.atom, _interpreter->getBaseURL());
+
+ if (!url.isAbsolute()) {
+ LOG(ERROR) << "Given directory '" << dirIter->second << "' cannot be transformed to absolute path";
+ } else {
+ _dir = url.path();
+ }
+ break;
+ }
+
+ _watcher = new DirectoryWatch(_dir, _recurse);
+ _watcher->addMonitor(this);
+ _watcher->updateEntries(true);
+
+ _isRunning = true;
+ _thread = new std::thread(DirMonInvoker::run, this);
+}
+
+void DirMonInvoker::uninvoke() {
+ _isRunning = false;
+ if (_thread) {
+ _thread->join();
+ delete _thread;
+ }
+}
+
+void DirMonInvoker::run(void* instance) {
+ while(((DirMonInvoker*)instance)->_isRunning) {
+ {
+ std::lock_guard<std::recursive_mutex> lock(((DirMonInvoker*)instance)->_mutex);
+ ((DirMonInvoker*)instance)->_watcher->updateEntries();
+ }
+ std::this_thread::sleep_for(std::chrono::milliseconds(20));
+ }
+}
+
+void DirMonInvoker::handleChanges(DirectoryWatch::Action action, const std::string reportedDir, const std::string reportedFilename, struct stat fileStat) {
+
+// std::cout << action << " on " << reportedFilename << std::endl;
+
+ std::string path; ///< complete path to the file including filename
+ std::string relPath; ///< path relative to monitored directory including filename
+ std::string dir; ///< the name of the directory we monitor
+ std::string relDir; ///< the directory from dir to the actual directory where we found a file
+ std::string basename; ///< filename including suffix
+ std::string strippedName; ///< filename without the suffix
+ std::string extension; ///< the extension
+
+ dir = reportedDir;
+
+ path = dir + reportedFilename;
+ boost::algorithm::replace_all(path, "\\", "/");
+ boost::algorithm::replace_all(path, "//", "/");
+
+ assert(boost::algorithm::starts_with(path, dir));
+ relPath = path.substr(dir.length());
+ assert(boost::equal(path, dir + relPath));
+
+ size_t lastSep;
+ if ((lastSep = path.find_last_of(PATH_SEPERATOR)) != std::string::npos) {
+ lastSep++;
+ basename = path.substr(lastSep, path.length() - lastSep);
+ } else {
+ assert(false);
+ }
+ assert(boost::algorithm::ends_with(relPath, basename));
+
+ // extension is the suffix and strippedName the basename without the suffix
+ size_t lastDot;
+ if ((lastDot = basename.find_last_of(".")) != std::string::npos) {
+ if (lastDot == 0) {
+ // hidden file
+ strippedName = basename;
+ } else {
+ extension = basename.substr(lastDot + 1);
+ strippedName = basename.substr(0, lastDot);
+ }
+ } else {
+ strippedName = basename;
+ }
+
+ relDir = relPath.substr(0, relPath.length() - basename.length());
+ assert(boost::equal(path, dir + relDir + basename));
+
+ // return if this is a hidden file
+ if (boost::algorithm::starts_with(basename, ".") && !_reportHidden)
+ return;
+
+ // ilter suffixes
+ if (_suffixes.size() > 0) {
+ bool validSuffix = false;
+ std::set<std::string>::iterator suffixIter = _suffixes.begin();
+ while(suffixIter != _suffixes.end()) {
+ if (boost::algorithm::ends_with(path, *suffixIter)) {
+ validSuffix = true;
+ break;
+ }
+ suffixIter++;
+ }
+ if (!validSuffix)
+ return;
+ }
+
+ Event event;
+ event.invokeid = _invokeId;
+
+ switch (action) {
+ case DirectoryWatch::EXISTING:
+ event.name = "file.existing";
+ break;
+ case DirectoryWatch::ADDED:
+ event.name = "file.added";
+ break;
+ case DirectoryWatch::DELETED:
+ event.name = "file.deleted";
+ break;
+ case DirectoryWatch::MODIFIED:
+ event.name = "file.modified";
+ break;
+ default:
+ break;
+ }
+
+ if (action != DirectoryWatch::DELETED) {
+ event.data.compound["file"].compound["mtime"] = Data(toStr(fileStat.st_mtime), Data::INTERPRETED);
+ event.data.compound["file"].compound["ctime"] = Data(toStr(fileStat.st_ctime), Data::INTERPRETED);
+ event.data.compound["file"].compound["atime"] = Data(toStr(fileStat.st_atime), Data::INTERPRETED);
+ event.data.compound["file"].compound["size"] = Data(toStr(fileStat.st_size), Data::INTERPRETED);
+ }
+
+ event.data.compound["file"].compound["name"] = Data(basename, Data::VERBATIM);
+ event.data.compound["file"].compound["extension"] = Data(extension, Data::VERBATIM);
+ event.data.compound["file"].compound["strippedName"] = Data(strippedName, Data::VERBATIM);
+ event.data.compound["file"].compound["relPath"] = Data(relPath, Data::VERBATIM);
+ event.data.compound["file"].compound["relDir"] = Data(relDir, Data::VERBATIM);
+ event.data.compound["file"].compound["path"] = Data(path, Data::VERBATIM);
+ event.data.compound["file"].compound["dir"] = Data(dir, Data::VERBATIM);
+
+ eventToSCXML(event, "dimon", "");
+}
+
+DirectoryWatch::~DirectoryWatch() {
+ std::map<std::string, DirectoryWatch*>::iterator dirIter = _knownDirs.begin();
+ while(dirIter != _knownDirs.end()) {
+ delete(dirIter->second);
+ dirIter++;
+ }
+
+}
+
+void DirectoryWatch::reportAsDeleted() {
+ std::map<std::string, struct stat>::iterator fileIter = _knownEntries.begin();
+ while(fileIter != _knownEntries.end()) {
+ if (fileIter->second.st_mode & S_IFDIR) {
+ _knownDirs[fileIter->first]->reportAsDeleted();
+ delete _knownDirs[fileIter->first];
+ _knownDirs.erase(fileIter->first);
+ } else {
+ _monitors_t::iterator monIter = _monitors.begin();
+ while(monIter != _monitors.end()) {
+ (*monIter)->handleChanges(DELETED, _dir, _relDir + PATH_SEPERATOR + fileIter->first, fileIter->second);
+ monIter++;
+ }
+ }
+ _knownEntries.erase(fileIter++);
+// fileIter++;
+ }
+ assert(_knownDirs.size() == 0);
+ assert(_knownEntries.size() == 0);
+}
+
+void DirectoryWatch::updateEntries(bool reportAsExisting) {
+ _monitors_t::iterator monIter;
+ if (_dir[_dir.length() - 1] == PATH_SEPERATOR)
+ _dir = _dir.substr(0, _dir.length() - 1);
+
+ // stat directory for modification date
+ struct stat dirStat;
+ if (stat((_dir + _relDir).c_str(), &dirStat) != 0) {
+ LOG(ERROR) << "Error with stat on directory " << _dir << ": " << strerror(errno);
+ return;
+ }
+
+ if ((unsigned)dirStat.st_mtime >= (unsigned)_lastChecked) {
+// std::cout << "dirStat.st_mtime: " << dirStat.st_mtime << " / _lastChecked: " << _lastChecked << std::endl;
+
+ // there are changes in the directory
+ std::set<std::string> currEntries;
+
+#ifndef WIN32
+ DIR *dp;
+ dp = opendir((_dir + _relDir).c_str());
+ if (dp == NULL) {
+ LOG(ERROR) << "Error opening directory " << _dir + _relDir << ": " << strerror(errno);
+ return;
+ }
+ // iterate all entries and see what changed
+ struct dirent* entry;
+ while((entry = readdir(dp))) {
+ std::string dname = entry->d_name;
+#else
+ WIN32_FIND_DATA ffd;
+ HANDLE hFind = INVALID_HANDLE_VALUE;
+ TCHAR szDir[MAX_PATH];
+ StringCchCopy(szDir, MAX_PATH, _dir.c_str());
+ StringCchCat(szDir, MAX_PATH, TEXT("\\*"));
+
+ hFind = FindFirstFile(szDir, &ffd);
+ do {
+ std::string dname = ffd.cFileName;
+#endif
+
+ // see if the file was changed
+ std::string filename = _dir + _relDir + "/" + dname;
+// asprintf(&filename, "%s/%s", (_dir + _relDir).c_str(), dname.c_str());
+
+ struct stat fileStat;
+ if (stat(filename.c_str(), &fileStat) != 0) {
+ LOG(ERROR) << "Error with stat on directory entry: " << filename << ": " << strerror(errno);
+ continue;
+ }
+
+ if (fileStat.st_mode & S_IFDIR) {
+ if (boost::equals(dname, ".") || boost::equals(dname, "..")) {
+ continue; // do not report . or ..
+ }
+ }
+
+ currEntries.insert(dname);
+
+ if (_knownEntries.find(dname) != _knownEntries.end()) {
+ // we have seen this entry before
+ struct stat oldStat = _knownEntries[dname];
+ if (oldStat.st_mtime < fileStat.st_mtime) {
+ monIter = _monitors.begin();
+ while(monIter != _monitors.end()) {
+ (*monIter)->handleChanges(MODIFIED, _dir, _relDir + PATH_SEPERATOR + dname, fileStat);
+ monIter++;
+ }
+ }
+ } else {
+ // we have not yet seen this entry
+ if (fileStat.st_mode & S_IFDIR) {
+ _knownDirs[dname] = new DirectoryWatch(_dir, _relDir + PATH_SEPERATOR + dname);
+ monIter = _monitors.begin();
+ while(monIter != _monitors.end()) {
+ _knownDirs[dname]->addMonitor(*monIter);
+ monIter++;
+ }
+ } else {
+ monIter = _monitors.begin();
+ while(monIter != _monitors.end()) {
+ if (reportAsExisting) {
+ (*monIter)->handleChanges(EXISTING, _dir, _relDir + PATH_SEPERATOR + dname, fileStat);
+ } else {
+ (*monIter)->handleChanges(ADDED, _dir, _relDir + PATH_SEPERATOR + dname, fileStat);
+ }
+ monIter++;
+ }
+ }
+ }
+
+ _knownEntries[dname] = fileStat; // gets copied on insertion
+#ifndef WIN32
+ }
+ closedir(dp);
+#else
+ }
+ while (FindNextFile(hFind, &ffd) != 0);
+ FindClose(hFind);
+#endif
+ // are there any known entries we have not seen this time around?
+ std::map<std::string, struct stat>::iterator fileIter = _knownEntries.begin();
+ while(fileIter != _knownEntries.end()) {
+ if (currEntries.find(fileIter->first) == currEntries.end()) {
+ // we used to know this file
+ if (fileIter->second.st_mode & S_IFDIR) {
+ if (_recurse) {
+ _knownDirs[fileIter->first]->reportAsDeleted();
+ delete _knownDirs[fileIter->first];
+ _knownDirs.erase(fileIter->first);
+ }
+ } else {
+ monIter = _monitors.begin();
+ while(monIter != _monitors.end()) {
+ (*monIter)->handleChanges(DELETED, _dir, _relDir + PATH_SEPERATOR + fileIter->first, fileIter->second);
+ monIter++;
+ }
+ }
+ _knownEntries.erase(fileIter++);
+ } else {
+ fileIter++;
+ }
+ }
+ // remember when we last checked the directory for modifications
+#ifndef WIN32
+ time(&_lastChecked);
+#else
+ // TODO: this will fail with sub-millisecond updates to the directory
+ _lastChecked = dirStat.st_mtime + 1;
+#endif
+ // update all directories
+ }
+ if (_recurse) {
+ std::map<std::string, DirectoryWatch*>::iterator dirIter = _knownDirs.begin();
+ while(dirIter != _knownDirs.end()) {
+ dirIter->second->updateEntries();
+ dirIter++;
+ }
+ }
+}
+
+} \ No newline at end of file
diff --git a/src/uscxml/plugins/invoker/dirmon/DirMonInvoker.h b/src/uscxml/plugins/invoker/dirmon/DirMonInvoker.h
new file mode 100644
index 0000000..be510d9
--- /dev/null
+++ b/src/uscxml/plugins/invoker/dirmon/DirMonInvoker.h
@@ -0,0 +1,140 @@
+/**
+ * @file
+ * @author 2012-2013 Stefan Radomski (stefan.radomski@cs.tu-darmstadt.de)
+ * @copyright Simplified BSD
+ *
+ * @cond
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the FreeBSD license as published by the FreeBSD
+ * project.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * You should have received a copy of the FreeBSD license along with this
+ * program. If not, see <http://www.opensource.org/licenses/bsd-license>.
+ * @endcond
+ */
+
+#ifndef DIRMONINVOKER_H_W09J90F0
+#define DIRMONINVOKER_H_W09J90F0
+
+#include "uscxml/plugins/InvokerImpl.h"
+
+#include <map>
+#include <set>
+#include <sys/stat.h>
+
+#ifdef BUILD_AS_PLUGINS
+#include "uscxml/plugins/Plugins.h"
+#endif
+
+namespace uscxml {
+
+class DirectoryWatchMonitor;
+
+class DirectoryWatch {
+public:
+ enum Action {
+ ADDED = 1,
+ MODIFIED = 2,
+ DELETED = 4,
+ EXISTING = 8
+ };
+
+ DirectoryWatch(const std::string& dir, bool recurse = false) : _dir(dir), _recurse(recurse), _lastChecked(0) {}
+ ~DirectoryWatch();
+
+ void addMonitor(DirectoryWatchMonitor* monitor) {
+ _monitors.insert(monitor);
+ }
+ void removeMonitor(DirectoryWatchMonitor* monitor) {
+ _monitors.erase(monitor);
+ }
+ void updateEntries(bool reportAsExisting = false);
+ void reportAsDeleted();
+
+ std::map<std::string, struct stat> getAllEntries() {
+ std::map<std::string, struct stat> entries;
+ entries.insert(_knownEntries.begin(), _knownEntries.end());
+
+ std::map<std::string, DirectoryWatch*>::iterator dirIter = _knownDirs.begin();
+ while(dirIter != _knownDirs.end()) {
+ std::map<std::string, struct stat> dirEntries = dirIter->second->getAllEntries();
+ std::map<std::string, struct stat>::iterator dirEntryIter = dirEntries.begin();
+ while(dirEntryIter != dirEntries.end()) {
+ entries[dirIter->first + '/' + dirEntryIter->first] = dirEntryIter->second;
+ dirEntryIter++;
+ }
+ dirIter++;
+ }
+
+ return entries;
+ }
+
+protected:
+ DirectoryWatch(const std::string& dir, const std::string& relDir) : _dir(dir), _relDir(relDir), _recurse(true), _lastChecked(0) {}
+
+ std::string _dir;
+ std::string _relDir;
+
+ bool _recurse;
+ std::map<std::string, struct stat> _knownEntries;
+ std::map<std::string, DirectoryWatch*> _knownDirs;
+ std::set<DirectoryWatchMonitor*> _monitors;
+ typedef std::set<DirectoryWatchMonitor*> _monitors_t;
+ time_t _lastChecked;
+};
+
+class DirectoryWatchMonitor {
+public:
+ virtual void handleChanges(DirectoryWatch::Action action, const std::string dir, const std::string file, struct stat fileStat) = 0;
+};
+
+class DirMonInvoker : public InvokerImpl, public DirectoryWatchMonitor {
+public:
+ DirMonInvoker();
+ virtual ~DirMonInvoker();
+ virtual std::shared_ptr<InvokerImpl> create(InterpreterImpl* interpreter);
+
+ virtual std::list<std::string> getNames() {
+ std::list<std::string> names;
+ names.push_back("dirmon");
+ names.push_back("DirectoryMonitor");
+ names.push_back("http://uscxml.tk.informatik.tu-darmstadt.de/#dirmon");
+ return names;
+ }
+
+ virtual Data getDataModelVariables();
+ virtual void eventFromSCXML(const Event& event);
+ virtual void invoke(const std::string& source, const Event& invokeEvent);
+ virtual void uninvoke();
+
+ virtual void handleChanges(DirectoryWatch::Action action, const std::string dir, const std::string file, struct stat fileStat);
+
+ static void run(void* instance);
+
+protected:
+ bool _reportExisting;
+ bool _reportHidden;
+ bool _recurse;
+
+ std::string _dir;
+ std::set<std::string> _suffixes;
+
+ bool _isRunning;
+ std::thread* _thread;
+ std::recursive_mutex _mutex;
+
+ DirectoryWatch* _watcher;
+};
+
+#ifdef BUILD_AS_PLUGINS
+PLUMA_INHERIT_PROVIDER(DirMonInvoker, InvokerImpl);
+#endif
+
+}
+
+
+#endif /* end of include guard: DIRMONINVOKER_H_W09J90F0 */