pdf preview generation: Use QtConcurrent::mapped + QFutureWatcher instead of own single-thread solution

This commit is contained in:
Albert S. 2019-04-29 20:50:52 +02:00
parent ec1219acda
commit d435ec3bfd
6 changed files with 86 additions and 82 deletions

View File

@ -50,7 +50,6 @@ HEADERS += \
commanddelete.h \ commanddelete.h \
commandupdate.h \ commandupdate.h \
filesaver.h \ filesaver.h \
filedata.h \
databasefactory.h \ databasefactory.h \
sqlitedbservice.h \ sqlitedbservice.h \
logger.h \ logger.h \

View File

@ -4,7 +4,7 @@
# #
#------------------------------------------------- #-------------------------------------------------
QT += core gui QT += core concurrent gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++14 CONFIG += c++14

View File

@ -28,9 +28,6 @@ MainWindow::MainWindow(QWidget *parent) :
qDebug() << "failed to open database"; qDebug() << "failed to open database";
throw std::runtime_error("Failed to open database"); throw std::runtime_error("Failed to open database");
} }
pdfWorker = new PdfWorker();
pdfWorker->moveToThread(&pdfWorkerThread);
connectSignals(); connectSignals();
searchThread.start(); searchThread.start();
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
@ -51,14 +48,19 @@ void MainWindow::connectSignals()
auto results = searchWatcher.future().result(); auto results = searchWatcher.future().result();
handleSearchResults(results); handleSearchResults(results);
}); });
connect(&pdfWorkerWatcher, &QFutureWatcher<PdfPreview>::resultReadyAt, this, [&](int index) {
pdfPreviewReceived(pdfWorkerWatcher.resultAt(index));
});
connect(&pdfWorkerWatcher, &QFutureWatcher<PdfPreview>::progressValueChanged, ui->pdfProcessBar, &QProgressBar::setValue);
// connect(searchWorker, &SearchWorker::searchCancelled, this, &MainWindow::handleCancelledSearch); // connect(searchWorker, &SearchWorker::searchCancelled, this, &MainWindow::handleCancelledSearch);
// connect(searchWorker, &SearchWorker::searchError, this, &MainWindow::handleSearchError); // connect(searchWorker, &SearchWorker::searchError, this, &MainWindow::handleSearchError);
connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated); connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this, &MainWindow::showSearchResultsContextMenu); connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this, &MainWindow::showSearchResultsContextMenu);
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged); connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
connect(this, &MainWindow::startPdfPreviewGeneration, pdfWorker, &PdfWorker::generatePreviews);
connect(pdfWorker, &PdfWorker::previewReady, this, &MainWindow::pdfPreviewReceived);
connect(pdfWorker, &PdfWorker::previewsFinished, [&] { this->pdfDirty = false; });
connect(ui->comboScale, qOverload<const QString &>(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged); connect(ui->comboScale, qOverload<const QString &>(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged);
} }
@ -114,8 +116,6 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview)
ClickLabel *label = new ClickLabel(); ClickLabel *label = new ClickLabel();
label->setPixmap(QPixmap::fromImage(preview.previewImage)); label->setPixmap(QPixmap::fromImage(preview.previewImage));
ui->scrollAreaWidgetContents->layout()->addWidget(label); ui->scrollAreaWidgetContents->layout()->addWidget(label);
ui->pdfProcessBar->setValue(++processedPdfPreviews);
connect(label, &ClickLabel::clicked, [=]() { connect(label, &ClickLabel::clicked, [=]() {
QSettings settings; QSettings settings;
QString command = settings.value("pdfviewer").toString(); QString command = settings.value("pdfviewer").toString();
@ -194,11 +194,11 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
void MainWindow::makePdfPreview() void MainWindow::makePdfPreview()
{ {
if(!pdfWorkerThread.isRunning())
pdfWorkerThread.start();
pdfWorker->cancelAndWait(); this->pdfWorkerWatcher.cancel();
QCoreApplication::processEvents(); //Process not processed images this->pdfWorkerWatcher.waitForFinished();
QCoreApplication::processEvents(); //Maybe not necessary anymore, depends on whether it's possible that a slot is still to be fired.
qDeleteAll(ui->scrollAreaWidgetContents->children()); qDeleteAll(ui->scrollAreaWidgetContents->children());
ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout()); ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
@ -206,8 +206,11 @@ void MainWindow::makePdfPreview()
processedPdfPreviews = 0; processedPdfPreviews = 0;
QString scaleText = ui->comboScale->currentText(); QString scaleText = ui->comboScale->currentText();
scaleText.chop(1); scaleText.chop(1);
PdfWorker worker;
this->pdfWorkerWatcher.setFuture(worker.generatePreviews(this->pdfSearchResults, scaleText.toInt() / 100.));
ui->pdfProcessBar->setMaximum(this->pdfWorkerWatcher.progressMaximum());
ui->pdfProcessBar->setMinimum(this->pdfWorkerWatcher.progressMinimum());
emit startPdfPreviewGeneration(this->pdfSearchResults, scaleText.toInt() / 100.);
} }

View File

@ -30,7 +30,7 @@ private:
QSqlDatabase db; QSqlDatabase db;
QFuture<QVector<SearchResult>> searchFuture; QFuture<QVector<SearchResult>> searchFuture;
QFutureWatcher<QVector<SearchResult>> searchWatcher; QFutureWatcher<QVector<SearchResult>> searchWatcher;
PdfWorker *pdfWorker; QFutureWatcher<PdfPreview> pdfWorkerWatcher;
void add(QString path, unsigned int page); void add(QString path, unsigned int page);
QThread searchThread; QThread searchThread;
QThread pdfWorkerThread; QThread pdfWorkerThread;

View File

@ -3,17 +3,29 @@
#include <QScreen> #include <QScreen>
#include <QDebug> #include <QDebug>
#include <QScopedPointer> #include <QScopedPointer>
#include <QMutexLocker>
#include <QtConcurrent/QtConcurrent>
#include <QtConcurrent/QtConcurrentMap>
#include "pdfworker.h" #include "pdfworker.h"
PdfWorker::PdfWorker()
static QMutex cacheMutex;
struct Renderer
{ {
} typedef PdfPreview result_type;
double scaleX;
Poppler::Document * PdfWorker::document(QString path) double scaleY;
{ QHash<QString, Poppler::Document *> documentcache;
if(this->documentcache.contains(path)) Renderer(double scaleX, double scaleY)
return this->documentcache.value(path); {
this->scaleX = scaleX;
this->scaleY = scaleY;
}
Poppler::Document *document(QString path)
{
if(documentcache.contains(path))
return documentcache.value(path);
Poppler::Document *result = Poppler::Document::load(path); Poppler::Document *result = Poppler::Document::load(path);
if(result == nullptr) if(result == nullptr)
@ -21,59 +33,59 @@ Poppler::Document * PdfWorker::document(QString path)
return nullptr; return nullptr;
} }
result->setRenderHint(Poppler::Document::TextAntialiasing); result->setRenderHint(Poppler::Document::TextAntialiasing);
this->documentcache.insert(path, result); QMutexLocker locker(&cacheMutex);
documentcache.insert(path, result);
return result; return result;
}
void PdfWorker::generatePreviews(QVector<SearchResult> paths, double scalefactor)
{
this->cancelCurrent = false;
this->generating = true;
for(SearchResult &sr : paths)
{
if(this->cancelCurrent.load())
{
break;
} }
Poppler::Document *doc = document(sr.fileData.absPath);
PdfPreview operator()(const PdfPreview &preview)
{
Poppler::Document *doc = document(preview.documentPath);
if(doc == nullptr) if(doc == nullptr)
{ {
continue; return preview;
} }
if(doc->isLocked()) if(doc->isLocked())
{ {
continue; return preview;
} }
for(unsigned int page : sr.pages) int p = (int)preview.page - 1;
{
int p = (int)page - 1;
if(p < 0) if(p < 0)
p = 0;
Poppler::Page *pdfPage = doc->page(p);
QImage image = pdfPage->renderToImage(QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor, QGuiApplication::primaryScreen()->physicalDotsPerInchY() *scalefactor);
PdfPreview preview;
preview.previewImage = image;
preview.documentPath = sr.fileData.absPath;
preview.page = page;
emit previewReady(preview);
}
}
isFreeMutex.lock();
isFree.wakeOne();
isFreeMutex.unlock();
generating = false;
emit previewsFinished();
}
void PdfWorker::cancelAndWait()
{
if(this->generating.load())
{ {
this->cancelCurrent = true; p = 0;
isFreeMutex.lock();
isFree.wait(&isFreeMutex);
isFreeMutex.unlock();
} }
Poppler::Page *pdfPage = doc->page(p);
PdfPreview result = preview;
result.previewImage = pdfPage->renderToImage(scaleX, scaleY);
return result;
}
};
QFuture<PdfPreview> PdfWorker::generatePreviews(QVector<SearchResult> paths, double scalefactor)
{
QVector<PdfPreview> previews;
for(SearchResult &sr : paths)
{
for(int page : sr.pages)
{
PdfPreview p;
p.documentPath = sr.fileData.absPath;
p.page = page;
previews.append(p);
}
}
double scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor;
double scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor;
return QtConcurrent::mapped(previews, Renderer(scaleX, scaleY));
} }

View File

@ -6,28 +6,18 @@
#include <QThread> #include <QThread>
#include <QMutex> #include <QMutex>
#include <QWaitCondition> #include <QWaitCondition>
#include <QMutex>
#include <QFuture>
#include <poppler-qt5.h> #include <poppler-qt5.h>
#include "pdfpreview.h" #include "pdfpreview.h"
#include "searchresult.h" #include "searchresult.h"
class PdfWorker : public QObject class PdfWorker : public QObject
{ {
Q_OBJECT Q_OBJECT
private:
QHash<QString, Poppler::Document *> documentcache;
Poppler::Document *document(QString path);
std::atomic<bool> cancelCurrent { false } ;
std::atomic<bool> generating { false };
QMutex isFreeMutex;
QWaitCondition isFree;
public: public:
PdfWorker(); QFuture<PdfPreview> generatePreviews(QVector<SearchResult> paths, double scalefactor);
void cancelAndWait();
public slots:
void generatePreviews(QVector<SearchResult> paths, double scalefactor);
signals:
void previewReady(PdfPreview p);
void previewsFinished();
}; };
#endif // PDFWORKER_H #endif // PDFWORKER_H