Explorar o código

Searcher: use async first phase search

Le Tan %!s(int64=3) %!d(string=hai) anos
pai
achega
486950c1aa

+ 1 - 11
src/search/filesearchengine.cpp

@@ -9,7 +9,7 @@
 using namespace vnotex;
 
 FileSearchEngineWorker::FileSearchEngineWorker(QObject *p_parent)
-    : QThread(p_parent)
+    : AsyncWorker(p_parent)
 {
 }
 
@@ -22,16 +22,6 @@ void FileSearchEngineWorker::setData(const QVector<SearchSecondPhaseItem> &p_ite
     m_token = p_token;
 }
 
-void FileSearchEngineWorker::stop()
-{
-    m_askedToStop.store(1);
-}
-
-bool FileSearchEngineWorker::isAskedToStop() const
-{
-    return m_askedToStop.load() == 1;
-}
-
 void FileSearchEngineWorker::run()
 {
     const int c_batchSize = 100;

+ 3 - 8
src/search/filesearchengine.h

@@ -8,6 +8,8 @@
 #include <QAtomicInt>
 #include <QVector>
 
+#include <utils/asyncworker.h>
+
 #include "searchtoken.h"
 #include "searchdata.h"
 
@@ -15,7 +17,7 @@ namespace vnotex
 {
     struct SearchResultItem;
 
-    class FileSearchEngineWorker : public QThread
+    class FileSearchEngineWorker : public AsyncWorker
     {
         Q_OBJECT
         friend class FileSearchEngine;
@@ -28,9 +30,6 @@ namespace vnotex
                      const QSharedPointer<SearchOption> &p_option,
                      const SearchToken &p_token);
 
-    public slots:
-        void stop();
-
     signals:
         void resultItemsReady(const QVector<QSharedPointer<SearchResultItem>> &p_items);
 
@@ -44,10 +43,6 @@ namespace vnotex
 
         void processBatchResults();
 
-        bool isAskedToStop() const;
-
-        QAtomicInt m_askedToStop = 0;
-
         QVector<SearchSecondPhaseItem> m_items;
 
         SearchToken m_token;

+ 86 - 63
src/search/searcher.cpp

@@ -8,6 +8,7 @@
 #include <core/exception.h>
 #include <notebook/node.h>
 #include <notebook/notebook.h>
+#include <utils/asyncworker.h>
 
 #include "searchresultitem.h"
 #include "filesearchengine.h"
@@ -53,25 +54,31 @@ SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, const
 
     emit logRequested(tr("Searching %n buffer(s)", "", p_buffers.size()));
 
-    emit progressUpdated(0, p_buffers.size());
-    for (int i = 0; i < p_buffers.size(); ++i) {
-        if (!p_buffers[i]) {
-            continue;
-        }
+    m_firstPhaseWorker->doWork([this, p_buffers]() {
+        emit progressUpdated(0, p_buffers.size());
+        for (int i = 0; i < p_buffers.size(); ++i) {
+            if (!p_buffers[i]) {
+                continue;
+            }
 
-        if (isAskedToStop()) {
-            return SearchState::Stopped;
-        }
+            if (isAskedToStop()) {
+                emit finished(SearchState::Stopped);
+                return;
+            }
+
+            auto file = p_buffers[i]->getFile();
+            if (!firstPhaseSearch(file.data())) {
+                emit finished(SearchState::Failed);
+                return;
+            }
 
-        auto file = p_buffers[i]->getFile();
-        if (!firstPhaseSearch(file.data())) {
-            return SearchState::Failed;
+            emit progressUpdated(i + 1, p_buffers.size());
         }
 
-        emit progressUpdated(i + 1, p_buffers.size());
-    }
+        emit finished(SearchState::Finished);
+    });
 
-    return SearchState::Finished;
+    return SearchState::Busy;
 }
 
 SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, Node *p_folder)
@@ -88,29 +95,19 @@ SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, Node
 
     emit logRequested(tr("Searching folder (%1)").arg(p_folder->getName()));
 
-    QVector<SearchSecondPhaseItem> secondPhaseItems;
-    if (!firstPhaseSearchFolder(p_folder, secondPhaseItems)) {
-        return SearchState::Failed;
-    }
-
-    if (isAskedToStop()) {
-        return SearchState::Stopped;
-    }
-
-    if (!secondPhaseItems.isEmpty()) {
-        // Do second phase search.
-        if (!secondPhaseSearch(secondPhaseItems)) {
-            return SearchState::Failed;
+    m_firstPhaseWorker->doWork([this, p_folder]() {
+        if (!firstPhaseSearchFolder(p_folder, m_secondPhaseItems)) {
+            m_secondPhaseItems.clear();
+            emit finished(SearchState::Failed);
+            return;
         }
 
-        if (isAskedToStop()) {
-            return SearchState::Stopped;
+        if (m_secondPhaseItems.isEmpty()) {
+            emit finished(SearchState::Finished);
         }
+    });
 
-        return SearchState::Busy;
-    }
-
-    return SearchState::Finished;
+    return SearchState::Busy;
 }
 
 SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, const QVector<Notebook *> &p_notebooks)
@@ -119,41 +116,32 @@ SearchState Searcher::search(const QSharedPointer<SearchOption> &p_option, const
         return SearchState::Failed;
     }
 
-    QVector<SearchSecondPhaseItem> secondPhaseItems;
-
-    emit progressUpdated(0, p_notebooks.size());
-    for (int i = 0; i < p_notebooks.size(); ++i) {
-        if (isAskedToStop()) {
-            return SearchState::Stopped;
-        }
-
-        emit logRequested(tr("Searching notebook (%1)").arg(p_notebooks[i]->getName()));
-
-        if (!firstPhaseSearch(p_notebooks[i], secondPhaseItems)) {
-            return SearchState::Failed;
-        }
+    m_firstPhaseWorker->doWork([this, p_notebooks]() {
+        emit progressUpdated(0, p_notebooks.size());
+        for (int i = 0; i < p_notebooks.size(); ++i) {
+            if (isAskedToStop()) {
+                m_secondPhaseItems.clear();
+                emit finished(SearchState::Stopped);
+                return;
+            }
 
-        emit progressUpdated(i + 1, p_notebooks.size());
-    }
+            emit logRequested(tr("Searching notebook (%1)").arg(p_notebooks[i]->getName()));
 
-    if (isAskedToStop()) {
-        return SearchState::Stopped;
-    }
+            if (!firstPhaseSearch(p_notebooks[i], m_secondPhaseItems)) {
+                m_secondPhaseItems.clear();
+                emit finished(SearchState::Failed);
+                return;
+            }
 
-    if (!secondPhaseItems.isEmpty()) {
-        // Do second phase search.
-        if (!secondPhaseSearch(secondPhaseItems)) {
-            return SearchState::Failed;
+            emit progressUpdated(i + 1, p_notebooks.size());
         }
 
-        if (isAskedToStop()) {
-            return SearchState::Stopped;
+        if (m_secondPhaseItems.isEmpty()) {
+            emit finished(SearchState::Finished);
         }
+    });
 
-        return SearchState::Busy;
-    }
-
-    return SearchState::Finished;
+    return SearchState::Busy;
 }
 
 bool Searcher::prepare(const QSharedPointer<SearchOption> &p_option)
@@ -172,12 +160,24 @@ bool Searcher::prepare(const QSharedPointer<SearchOption> &p_option)
         m_filePattern = QRegularExpression(QRegularExpression::wildcardToRegularExpression(m_option->m_filePattern), QRegularExpression::CaseInsensitiveOption);
     }
 
+    if (!m_firstPhaseWorker) {
+        m_firstPhaseWorker = new AsyncWorkerWithFunctor(this);
+        connect(m_firstPhaseWorker, &AsyncWorkerWithFunctor::finished,
+                this, &Searcher::doSecondPhaseSearch);
+    }
+
+    if (m_firstPhaseWorker->isRunning()) {
+        emit logRequested(tr("Failed to search due to worker is busy"));
+        return false;
+    }
+
+    m_secondPhaseItems.clear();
+
     return true;
 }
 
 bool Searcher::isAskedToStop() const
 {
-    QCoreApplication::sendPostedEvents();
     return m_askedToStop;
 }
 
@@ -357,7 +357,7 @@ bool Searcher::firstPhaseSearchFolder(Node *p_node, QVector<SearchSecondPhaseIte
         return false;
     }
 
-    if (testTarget(SearchTarget::SearchFolder)) {
+    if (testTarget(SearchTarget::SearchFolder) && !p_node->isRoot()) {
         const auto name = p_node->getName();
         const auto folderPath = p_node->fetchAbsolutePath();
         const auto relativePath = p_node->fetchPath();
@@ -531,3 +531,26 @@ bool Searcher::searchTag(const Node *p_node) const
 
     return false;
 }
+
+void Searcher::doSecondPhaseSearch()
+{
+    if (m_secondPhaseItems.isEmpty()) {
+        return;
+    }
+
+    if (isAskedToStop()) {
+        emit finished(SearchState::Stopped);
+        return;
+    }
+
+    // Do second phase search.
+    if (!secondPhaseSearch(m_secondPhaseItems)) {
+        emit finished(SearchState::Failed);
+        return;
+    }
+
+    if (isAskedToStop()) {
+        emit finished(SearchState::Stopped);
+        return;
+    }
+}

+ 10 - 0
src/search/searcher.h

@@ -5,6 +5,7 @@
 #include <QSharedPointer>
 #include <QScopedPointer>
 #include <QRegularExpression>
+#include <QThread>
 
 #include "searchdata.h"
 #include "searchtoken.h"
@@ -17,6 +18,7 @@ namespace vnotex
     struct SearchResultItem;
     class Node;
     class Notebook;
+    class AsyncWorkerWithFunctor;
 
     class Searcher : public QObject
     {
@@ -83,6 +85,9 @@ namespace vnotex
 
         void createSearchEngine();
 
+        // Will be called after m_firstPhaseWorker finished.
+        void doSecondPhaseSearch();
+
         QSharedPointer<SearchOption> m_option;
 
         SearchToken m_token;
@@ -92,6 +97,11 @@ namespace vnotex
         bool m_askedToStop = false;
 
         QScopedPointer<ISearchEngine> m_engine;
+
+        AsyncWorkerWithFunctor *m_firstPhaseWorker = nullptr;
+
+        // Pending second phase items.
+        QVector<SearchSecondPhaseItem> m_secondPhaseItems;
     };
 }
 

+ 37 - 0
src/utils/asyncworker.cpp

@@ -0,0 +1,37 @@
+#include "asyncworker.h"
+
+using namespace vnotex;
+
+AsyncWorker::AsyncWorker(QObject *p_parent)
+    : QThread(p_parent)
+{
+}
+
+void AsyncWorker::stop()
+{
+    m_askedToStop.store(1);
+}
+
+bool AsyncWorker::isAskedToStop() const
+{
+    return m_askedToStop.load() == 1;
+}
+
+
+AsyncWorkerWithFunctor::AsyncWorkerWithFunctor(QObject *p_parent)
+    : QThread(p_parent)
+{
+}
+
+void AsyncWorkerWithFunctor::doWork(const Functor &p_functor)
+{
+    Q_ASSERT(!isRunning());
+    m_functor = p_functor;
+    start();
+}
+
+void AsyncWorkerWithFunctor::run()
+{
+    Q_ASSERT(m_functor);
+    m_functor();
+}

+ 44 - 0
src/utils/asyncworker.h

@@ -0,0 +1,44 @@
+#ifndef ASYNCWORKER_H
+#define ASYNCWORKER_H
+
+#include <QThread>
+#include <QAtomicInt>
+
+namespace vnotex
+{
+    class AsyncWorker : public QThread
+    {
+        Q_OBJECT
+    public:
+        explicit AsyncWorker(QObject *p_parent = nullptr);
+
+    public slots:
+        void stop();
+
+    protected:
+        bool isAskedToStop() const;
+
+    private:
+        QAtomicInt m_askedToStop = 0;
+    };
+
+
+    class AsyncWorkerWithFunctor : public QThread
+    {
+        Q_OBJECT
+    public:
+        typedef std::function<void()> Functor;
+
+        explicit AsyncWorkerWithFunctor(QObject *p_parent = nullptr);
+
+        void doWork(const Functor &p_functor);
+
+    protected:
+        void run() Q_DECL_OVERRIDE;
+
+    private:
+        Functor m_functor;
+    };
+}
+
+#endif // ASYNCWORKER_H

+ 2 - 0
src/utils/utils.pri

@@ -1,6 +1,7 @@
 QT += widgets svg
 
 SOURCES += \
+    $$PWD/asyncworker.cpp \
     $$PWD/callbackpool.cpp \
     $$PWD/contentmediautils.cpp \
     $$PWD/docsutils.cpp \
@@ -18,6 +19,7 @@ SOURCES += \
     $$PWD/clipboardutils.cpp
 
 HEADERS += \
+    $$PWD/asyncworker.h \
     $$PWD/callbackpool.h \
     $$PWD/contentmediautils.h \
     $$PWD/docsutils.h \

+ 4 - 3
src/widgets/searchpanel.cpp

@@ -40,6 +40,10 @@ SearchPanel::SearchPanel(const QSharedPointer<ISearchInfoProvider> &p_provider,
 {
     qRegisterMetaType<QVector<QSharedPointer<SearchResultItem>>>("QVector<QSharedPointer<SearchResultItem>>");
 
+    qRegisterMetaType<QSharedPointer<SearchResultItem>>("QSharedPointer<SearchResultItem>");
+
+    qRegisterMetaType<SearchState>("SearchState");
+
     setupUI();
 
     initOptions();
@@ -442,9 +446,6 @@ SearchState SearchPanel::search(const QSharedPointer<SearchOption> &p_option)
             break;
         }
         auto folder = m_provider->getCurrentFolder();
-        if (folder && (folder->isRoot())) {
-            folder = nullptr;
-        }
         if (!folder) {
             break;
         }