diff options
Diffstat (limited to 'src/uscxml/plugins/invoker/dirmon/DirMonInvoker.cpp')
-rw-r--r-- | src/uscxml/plugins/invoker/dirmon/DirMonInvoker.cpp | 449 |
1 files changed, 449 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 |