/** * @file FileFinder/Engine/engine.h * @brief Module for the FileFinder application that searches for files * @author Enar Vaikene * * Copyright (c) 2011 Enar Vaikene * * This file is part of the eVaf C++ cross-platform application development framework. * * This file can be used under the terms of the GNU General Public License * version 3.0 as published by the Free Software Foundation and appearing in * the file LICENSE included in the packaging of this file. Please review the * the following information to ensure the GNU General Public License version * 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html. * * Alternatively, this file may be used in accordance with the Commercial License * Agreement provided with the Software. */ #include "engine.h" #include #include #include VER_EXPORT_VERSION_INFO() using namespace eVaf; using namespace eVaf::FileFinder; using namespace eVaf::FileFinder::Engine; //------------------------------------------------------------------- Module::Module() : Plugins::iPlugin() , mReady(false) { setObjectName(QString("%1.Module").arg(VER_MODULE_NAME_STR)); mEngine = new Internal::Engine; EVAF_INFO("%s created", qPrintable(objectName())); } Module::~Module() { delete mEngine; EVAF_INFO("%s destroyed", qPrintable(objectName())); } bool Module::init(QString const & args) { Q_UNUSED(args) if (!mEngine->init()) return false; mReady = true; EVAF_INFO("%s initialized", qPrintable(objectName())); return true; } void Module::done() { mReady = false; mEngine->done(); EVAF_INFO("%s finalized", qPrintable(objectName())); } //------------------------------------------------------------------- Internal::Engine::Engine() : iFileFinder() , mWorker(0) { setObjectName(QString("%1.Engine").arg(VER_MODULE_NAME_STR)); EVAF_INFO("%s created", qPrintable(objectName())); } Internal::Engine::~Engine() { EVAF_INFO("%s destroyed", qPrintable(objectName())); } bool Internal::Engine::init() { // Register the iFileFinder interface Common::iRegistry::instance()->registerInterface("iFileFinder", this); mWorker = new Internal::Worker(this); connect(mWorker, SIGNAL(found(QString, QString)), this, SIGNAL(found(QString,QString))); connect(mWorker, SIGNAL(finished(bool)), this, SIGNAL(finished(bool))); mWorker->start(); EVAF_INFO("%s initialized", qPrintable(objectName())); return true; } void Internal::Engine::done() { if (mWorker) { mWorker->stop(); delete mWorker; mWorker = 0; } EVAF_INFO("%s finalized", qPrintable(objectName())); } void Internal::Engine::search(QString const & dir, bool recursive, Filter const & filter) { if (mWorker) mWorker->search(dir, recursive, filter); } bool Internal::Engine::busy() const { if (mWorker) return mWorker->busy(); return false; } void Internal::Engine::cancel() { if (mWorker) mWorker->cancel(); } //------------------------------------------------------------------- Internal::RegExpChain::~RegExpChain() { clear(); } void Internal::RegExpChain::clear() { foreach (QRegExp * pattern, mPatterns) { delete pattern; } mPatterns.clear(); mValid = false; } void Internal::RegExpChain::setPattern(QString const & pattern) { clear(); QStringList patterns = split(pattern); foreach (QString s, patterns) { if (s.isEmpty()) continue; QRegExp * rx = new QRegExp(s, Qt::CaseSensitive, QRegExp::WildcardUnix); if (!rx->isValid()) { delete rx; continue; } mPatterns.append(rx); } mValid = !mPatterns.isEmpty(); } bool Internal::RegExpChain::exactMatch(const QString& str) const { if (mPatterns.isEmpty()) return false; foreach (QRegExp * pattern, mPatterns) { if (pattern->exactMatch(str)) return true; } return false; } QStringList Internal::RegExpChain::split(const QString & pattern) { QStringList rval; int offset = 0; int sz = pattern.size(); QString s; bool e = false; while (offset < sz) { QChar ch = pattern.at(offset++); if (e) { e = false; if (ch == '*' || ch == '?' || ch == '[' || ch == ']') s.append('\\'); s.append(ch); } else { if (ch == '\\') e = true; else if (ch == ',') { rval.append(s); s.clear(); } else s.append(ch); } } if (!s.isEmpty()) rval.append(s); return rval; } //------------------------------------------------------------------- int const Internal::Worker::ReadBufferSize = 4096; Internal::Worker::Worker(QObject * parent) : QThread(parent) , mNewRecursive(false) , mRecursive(false) , mDoSearch(false) , mDoTerminate(false) , mDoCancel(false) , mBusy(false) { setObjectName(QString("%1.Worker").arg(VER_MODULE_NAME_STR)); EVAF_INFO("%s created", qPrintable(objectName())); } Internal::Worker::~Worker() { EVAF_INFO("%s destroyed", qPrintable(objectName())); } void Internal::Worker::cancel() { mLock.lock(); mDoCancel = true; mSomethingToDo.wakeAll(); mLock.unlock(); } void Internal::Worker::stop() { mLock.lock(); mDoTerminate = true; mDoCancel = true; mSomethingToDo.wakeAll(); mLock.unlock(); wait(); } void Internal::Worker::search(QString const & dir, bool recursive, Filter const & filter) { mLock.lock(); mDoCancel = true; mNewDir = dir; mNewRecursive = recursive; mNewFilter = filter; mDoSearch = true; mSomethingToDo.wakeAll(); mLock.unlock(); } bool Internal::Worker::busy() const { QMutexLocker l(&mLock); return mBusy; } void Internal::Worker::run() { forever { QMutexLocker lock(&mLock); if (mDoTerminate) break; mSomethingToDo.wait(&mLock); if (mDoTerminate) break; mDoCancel = false; if (mDoSearch) { mDoSearch = false; mBusy = true; // Copy search arguments mDir.setPath(mNewDir); mRecursive = mNewRecursive; mRxIncludeNames.setPattern(mNewFilter.includeNames); mRxExcludeNames.setPattern(mNewFilter.excludeNames); mRxIncludeContent.setPattern(mNewFilter.includeContent); mRxExcludeContent.setPattern(mNewFilter.excludeContent); lock.unlock(); // Perform the actual search recursiveSearch(mDir.path()); lock.relock(); mBusy = false; bool c = mDoCancel; lock.unlock(); emit finished(c); } } } void Internal::Worker::recursiveSearch(QString const & path) { QString l = path; if (!l.endsWith(QChar('/'))) l.append(QChar('/')); QDir dir(l); // Get the list of files in this directory QStringList files = dir.entryList(QDir::Files | QDir::NoSymLinks); foreach (QString const & file, files) { // Check for the cancel flag { QMutexLocker l(&mLock); if (mDoCancel) return; } // Check for the file name to match the include filter and not the exclude filter if (mRxIncludeNames.isValid() && !mRxIncludeNames.isEmpty()) { if (!mRxIncludeNames.exactMatch(file)) continue; } if (mRxExcludeNames.isValid() && !mRxExcludeNames.isEmpty()) { if (mRxExcludeNames.exactMatch(file)) continue; } // Check for the file content to match the include filter and not the exclude filter if ((mRxIncludeContent.isValid() && !mRxIncludeContent.isEmpty()) || (mRxExcludeContent.isValid() && !mRxExcludeContent.isEmpty())) { QFile f(l + file); if (!f.open(QFile::ReadOnly)) continue; // Ignore silently if opening fails int includeFilterMatched = 0; if (!mRxIncludeContent.isValid() || mRxIncludeContent.isEmpty()) includeFilterMatched = 1; int excludeFilterMatched = 0; if (!mRxExcludeContent.isValid() || mRxExcludeContent.isEmpty()) excludeFilterMatched = -1; QByteArray buf; while (!f.atEnd() && (includeFilterMatched <= 0 || excludeFilterMatched <= 0)) { // Check for the cancel flag { QMutexLocker l(&mLock); if (mDoCancel) return; } /* We read ReadBufferSize bytes from the file and append to the buffer. * We keep max 2 x ReadBufferSize bytes in the buffer and throw away the oldest * ReadBufferSize bytes of data. Every block is checked twice, but we make sure that * also strings that stretch from one block to another are checked. */ QByteArray b = f.read(ReadBufferSize); buf.append(b); if (buf.size() > (2 * ReadBufferSize)) buf.remove(0, ReadBufferSize); if (includeFilterMatched == 0 && mRxIncludeContent.indexIn(buf) >= 0) ++includeFilterMatched; if (excludeFilterMatched == 0 && mRxExcludeContent.indexIn(buf) >= 0) ++excludeFilterMatched; } if (includeFilterMatched == 0 || excludeFilterMatched > 0) continue; } // Found a file emit found(mDir.relativeFilePath(l + file), mDir.path()); } // Process sub-directories if (mRecursive) { QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot | QDir::NoSymLinks); foreach (QString const & directory, dirs) { // Check for the cancel flag { QMutexLocker l(&mLock); if (mDoCancel) return; } if (mRxExcludeNames.isValid() && !mRxExcludeNames.isEmpty() && mRxExcludeNames.exactMatch(directory)) continue; recursiveSearch(l + directory); } } }