#include "vsearchengine.h" #include #include #include #include "utils/vutils.h" VSearchEngineWorker::VSearchEngineWorker(QObject *p_parent) : QThread(p_parent), m_stop(0), m_state(VSearchState::Idle) { } void VSearchEngineWorker::setData(const QStringList &p_files, const VSearchToken &p_token) { m_files = p_files; m_token = p_token; } void VSearchEngineWorker::stop() { m_stop.store(1); } void VSearchEngineWorker::run() { qDebug() << "worker" << QThread::currentThreadId() << m_files.size(); QMimeDatabase mimeDatabase; m_state = VSearchState::Busy; m_results.clear(); int nr = 0; for (auto const & fileName : m_files) { if (m_stop.load() == 1) { m_state = VSearchState::Cancelled; qDebug() << "worker" << QThread::currentThreadId() << "is asked to stop"; break; } const QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileName); if (mimeType.isValid() && !mimeType.inherits(QStringLiteral("text/plain"))) { appendError(tr("Skip binary file %1.").arg(fileName)); continue; } VSearchResultItem *item = searchFile(fileName); if (item) { m_results.append(QSharedPointer(item)); } if (++nr >= BATCH_ITEM_SIZE) { nr = 0; postAndClearResults(); } } postAndClearResults(); if (m_state == VSearchState::Busy) { m_state = VSearchState::Success; } } VSearchResultItem *VSearchEngineWorker::searchFile(const QString &p_fileName) { QFile file(p_fileName); if (!file.open(QIODevice::ReadOnly)) { return NULL; } int lineNum = 1; VSearchResultItem *item = NULL; QString line; QTextStream in(&file); bool singleToken = m_token.tokenSize() == 1; if (!singleToken) { m_token.startBatchMode(); } bool allMatched = false; while (!in.atEnd()) { if (m_stop.load() == 1) { m_state = VSearchState::Cancelled; qDebug() << "worker" << QThread::currentThreadId() << "is asked to stop"; break; } line = in.readLine(); bool matched = false; if (singleToken) { matched = m_token.matched(line); } else { matched = m_token.matchBatchMode(line); } if (matched) { if (!item) { item = new VSearchResultItem(VSearchResultItem::Note, VSearchResultItem::LineNumber, VUtils::fileNameFromPath(p_fileName), p_fileName); } VSearchResultSubItem sitem(lineNum, line); item->m_matches.append(sitem); } if (!singleToken && m_token.readyToEndBatchMode(allMatched)) { break; } ++lineNum; } if (!singleToken) { m_token.readyToEndBatchMode(allMatched); m_token.endBatchMode(); if (!allMatched && item) { delete item; item = NULL; } } return item; } void VSearchEngineWorker::postAndClearResults() { if (!m_results.isEmpty()) { emit resultItemsReady(m_results); m_results.clear(); } } VSearchEngine::VSearchEngine(QObject *p_parent) : ISearchEngine(p_parent), m_finishedWorkers(0) { } VSearchEngine::~VSearchEngine() { stop(); clear(); } void VSearchEngine::search(const QSharedPointer &p_config, const QSharedPointer &p_result) { int numThread = QThread::idealThreadCount(); if (numThread < 1) { numThread = 1; } const QStringList items = p_result->m_secondPhaseItems; Q_ASSERT(!items.isEmpty()); if (items.size() < numThread) { numThread = items.size(); } m_result = p_result; clearAllWorkers(); m_workers.reserve(numThread); m_finishedWorkers = 0; int totalSize = m_result->m_secondPhaseItems.size(); int step = totalSize / numThread; int remain = totalSize % numThread; int start = 0; for (int i = 0; i < numThread && start < totalSize; ++i) { int len = step; if (remain) { ++len; --remain; } if (start + len > totalSize) { len = totalSize - start; } VSearchEngineWorker *th = new VSearchEngineWorker(this); th->setData(m_result->m_secondPhaseItems.mid(start, len), p_config->m_contentToken); connect(th, &VSearchEngineWorker::finished, this, &VSearchEngine::handleWorkerFinished); connect(th, &VSearchEngineWorker::resultItemsReady, this, [this](const QList > &p_items) { emit resultItemsAdded(p_items); }); m_workers.append(th); th->start(); start += len; } qDebug() << "schedule tasks to threads" << m_workers.size() << totalSize << step; } void VSearchEngine::stop() { qDebug() << "VSearchEngine asked to stop"; for (auto const & th : m_workers) { th->stop(); } } void VSearchEngine::handleWorkerFinished() { ++m_finishedWorkers; qDebug() << m_finishedWorkers << "workers finished"; if (m_finishedWorkers == m_workers.size()) { VSearchState state = VSearchState::Success; for (auto const & th : m_workers) { if (th->m_state == VSearchState::Fail) { if (state != VSearchState::Cancelled) { state = VSearchState::Fail; } } else if (th->m_state == VSearchState::Cancelled) { state = VSearchState::Cancelled; } if (!th->m_error.isEmpty()) { m_result->logError(th->m_error); } Q_ASSERT(th->isFinished()); th->deleteLater(); } m_workers.clear(); m_finishedWorkers = 0; m_result->m_state = state; qDebug() << "SearchEngine finished" << (int)state; emit finished(m_result); } } void VSearchEngine::clear() { clearAllWorkers(); m_finishedWorkers = 0; m_result.clear(); } void VSearchEngine::clearAllWorkers() { for (auto const & th : m_workers) { th->quit(); th->wait(); delete th; } m_workers.clear(); }