pdf preview generation: Use QtConcurrent::mapped + QFutureWatcher instead of own single-thread solution
This commit is contained in:
		@@ -50,7 +50,6 @@ HEADERS += \
 | 
			
		||||
    commanddelete.h \
 | 
			
		||||
    commandupdate.h \
 | 
			
		||||
    filesaver.h \
 | 
			
		||||
    filedata.h \
 | 
			
		||||
    databasefactory.h \
 | 
			
		||||
    sqlitedbservice.h \
 | 
			
		||||
    logger.h \
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
#
 | 
			
		||||
#-------------------------------------------------
 | 
			
		||||
 | 
			
		||||
QT       += core gui
 | 
			
		||||
QT       += core concurrent gui
 | 
			
		||||
 | 
			
		||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
 | 
			
		||||
CONFIG += c++14
 | 
			
		||||
 
 | 
			
		||||
@@ -26,9 +26,6 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWi
 | 
			
		||||
		qDebug() << "failed to open database";
 | 
			
		||||
		throw std::runtime_error("Failed to open database");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pdfWorker = new PdfWorker();
 | 
			
		||||
	pdfWorker->moveToThread(&pdfWorkerThread);
 | 
			
		||||
	connectSignals();
 | 
			
		||||
	searchThread.start();
 | 
			
		||||
	ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
 | 
			
		||||
@@ -51,15 +48,19 @@ void MainWindow::connectSignals()
 | 
			
		||||
				auto results = searchWatcher.future().result();
 | 
			
		||||
				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::searchError, this, &MainWindow::handleSearchError);
 | 
			
		||||
	connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
 | 
			
		||||
	connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this,
 | 
			
		||||
			&MainWindow::showSearchResultsContextMenu);
 | 
			
		||||
	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);
 | 
			
		||||
}
 | 
			
		||||
@@ -117,8 +118,6 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview)
 | 
			
		||||
	ClickLabel *label = new ClickLabel();
 | 
			
		||||
	label->setPixmap(QPixmap::fromImage(preview.previewImage));
 | 
			
		||||
	ui->scrollAreaWidgetContents->layout()->addWidget(label);
 | 
			
		||||
	ui->pdfProcessBar->setValue(++processedPdfPreviews);
 | 
			
		||||
 | 
			
		||||
	connect(label, &ClickLabel::clicked,
 | 
			
		||||
			[=]()
 | 
			
		||||
			{
 | 
			
		||||
@@ -199,11 +198,12 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
 | 
			
		||||
 | 
			
		||||
void MainWindow::makePdfPreview()
 | 
			
		||||
{
 | 
			
		||||
	if(!pdfWorkerThread.isRunning())
 | 
			
		||||
		pdfWorkerThread.start();
 | 
			
		||||
 | 
			
		||||
	pdfWorker->cancelAndWait();
 | 
			
		||||
	QCoreApplication::processEvents(); // Process not processed images
 | 
			
		||||
	this->pdfWorkerWatcher.cancel();
 | 
			
		||||
	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());
 | 
			
		||||
 | 
			
		||||
	ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
 | 
			
		||||
@@ -211,8 +211,10 @@ void MainWindow::makePdfPreview()
 | 
			
		||||
	processedPdfPreviews = 0;
 | 
			
		||||
	QString scaleText = ui->comboScale->currentText();
 | 
			
		||||
	scaleText.chop(1);
 | 
			
		||||
 | 
			
		||||
	emit startPdfPreviewGeneration(this->pdfSearchResults, scaleText.toInt() / 100.);
 | 
			
		||||
	PdfWorker worker;
 | 
			
		||||
	this->pdfWorkerWatcher.setFuture(worker.generatePreviews(this->pdfSearchResults, scaleText.toInt() / 100.));
 | 
			
		||||
	ui->pdfProcessBar->setMaximum(this->pdfWorkerWatcher.progressMaximum());
 | 
			
		||||
	ui->pdfProcessBar->setMinimum(this->pdfWorkerWatcher.progressMinimum());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void MainWindow::handleCancelledSearch()
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ class MainWindow : public QMainWindow
 | 
			
		||||
	QSqlDatabase db;
 | 
			
		||||
	QFuture<QVector<SearchResult>> searchFuture;
 | 
			
		||||
	QFutureWatcher<QVector<SearchResult>> searchWatcher;
 | 
			
		||||
	PdfWorker *pdfWorker;
 | 
			
		||||
	QFutureWatcher<PdfPreview> pdfWorkerWatcher;
 | 
			
		||||
	void add(QString path, unsigned int page);
 | 
			
		||||
	QThread searchThread;
 | 
			
		||||
	QThread pdfWorkerThread;
 | 
			
		||||
 
 | 
			
		||||
@@ -3,77 +3,80 @@
 | 
			
		||||
#include <QScreen>
 | 
			
		||||
#include <QDebug>
 | 
			
		||||
#include <QScopedPointer>
 | 
			
		||||
#include <QMutexLocker>
 | 
			
		||||
#include <QtConcurrent/QtConcurrent>
 | 
			
		||||
#include <QtConcurrent/QtConcurrentMap>
 | 
			
		||||
#include "pdfworker.h"
 | 
			
		||||
 | 
			
		||||
PdfWorker::PdfWorker()
 | 
			
		||||
static QMutex cacheMutex;
 | 
			
		||||
struct Renderer
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Poppler::Document *PdfWorker::document(QString path)
 | 
			
		||||
{
 | 
			
		||||
	if(this->documentcache.contains(path))
 | 
			
		||||
		return this->documentcache.value(path);
 | 
			
		||||
 | 
			
		||||
	Poppler::Document *result = Poppler::Document::load(path);
 | 
			
		||||
	if(result == nullptr)
 | 
			
		||||
	typedef PdfPreview result_type;
 | 
			
		||||
	double scaleX;
 | 
			
		||||
	double scaleY;
 | 
			
		||||
	QHash<QString, Poppler::Document *> documentcache;
 | 
			
		||||
	Renderer(double scaleX, double scaleY)
 | 
			
		||||
	{
 | 
			
		||||
		return nullptr;
 | 
			
		||||
		this->scaleX = scaleX;
 | 
			
		||||
		this->scaleY = scaleY;
 | 
			
		||||
	}
 | 
			
		||||
	result->setRenderHint(Poppler::Document::TextAntialiasing);
 | 
			
		||||
	this->documentcache.insert(path, result);
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
void PdfWorker::generatePreviews(QVector<SearchResult> paths, double scalefactor)
 | 
			
		||||
{
 | 
			
		||||
	this->cancelCurrent = false;
 | 
			
		||||
	this->generating = true;
 | 
			
		||||
	for(SearchResult &sr : paths)
 | 
			
		||||
	Poppler::Document *document(QString path)
 | 
			
		||||
	{
 | 
			
		||||
		if(this->cancelCurrent.load())
 | 
			
		||||
		if(documentcache.contains(path))
 | 
			
		||||
			return documentcache.value(path);
 | 
			
		||||
 | 
			
		||||
		Poppler::Document *result = Poppler::Document::load(path);
 | 
			
		||||
		if(result == nullptr)
 | 
			
		||||
		{
 | 
			
		||||
			break;
 | 
			
		||||
			return nullptr;
 | 
			
		||||
		}
 | 
			
		||||
		Poppler::Document *doc = document(sr.fileData.absPath);
 | 
			
		||||
		result->setRenderHint(Poppler::Document::TextAntialiasing);
 | 
			
		||||
		QMutexLocker locker(&cacheMutex);
 | 
			
		||||
		documentcache.insert(path, result);
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	PdfPreview operator()(const PdfPreview &preview)
 | 
			
		||||
	{
 | 
			
		||||
		Poppler::Document *doc = document(preview.documentPath);
 | 
			
		||||
		if(doc == nullptr)
 | 
			
		||||
		{
 | 
			
		||||
			continue;
 | 
			
		||||
			return preview;
 | 
			
		||||
		}
 | 
			
		||||
		if(doc->isLocked())
 | 
			
		||||
		{
 | 
			
		||||
			continue;
 | 
			
		||||
			return preview;
 | 
			
		||||
		}
 | 
			
		||||
		for(unsigned int page : sr.pages)
 | 
			
		||||
		int p = (int)preview.page - 1;
 | 
			
		||||
		if(p < 0)
 | 
			
		||||
		{
 | 
			
		||||
			int p = (int)page - 1;
 | 
			
		||||
			if(p < 0)
 | 
			
		||||
				p = 0;
 | 
			
		||||
			Poppler::Page *pdfPage = doc->page(p);
 | 
			
		||||
			QImage image =
 | 
			
		||||
				pdfPage->renderToImage(QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor,
 | 
			
		||||
									   QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor);
 | 
			
		||||
			p = 0;
 | 
			
		||||
		}
 | 
			
		||||
		Poppler::Page *pdfPage = doc->page(p);
 | 
			
		||||
		PdfPreview result = preview;
 | 
			
		||||
		result.previewImage = pdfPage->renderToImage(scaleX, scaleY);
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
			PdfPreview preview;
 | 
			
		||||
			preview.previewImage = image;
 | 
			
		||||
			preview.documentPath = sr.fileData.absPath;
 | 
			
		||||
			preview.page = page;
 | 
			
		||||
			emit previewReady(preview);
 | 
			
		||||
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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	isFreeMutex.lock();
 | 
			
		||||
	isFree.wakeOne();
 | 
			
		||||
	isFreeMutex.unlock();
 | 
			
		||||
	generating = false;
 | 
			
		||||
	emit previewsFinished();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PdfWorker::cancelAndWait()
 | 
			
		||||
{
 | 
			
		||||
	if(this->generating.load())
 | 
			
		||||
	{
 | 
			
		||||
		this->cancelCurrent = true;
 | 
			
		||||
	double scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor;
 | 
			
		||||
	double scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor;
 | 
			
		||||
 | 
			
		||||
		isFreeMutex.lock();
 | 
			
		||||
		isFree.wait(&isFreeMutex);
 | 
			
		||||
		isFreeMutex.unlock();
 | 
			
		||||
	}
 | 
			
		||||
	return QtConcurrent::mapped(previews, Renderer(scaleX, scaleY));
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -6,29 +6,17 @@
 | 
			
		||||
#include <QThread>
 | 
			
		||||
#include <QMutex>
 | 
			
		||||
#include <QWaitCondition>
 | 
			
		||||
#include <QMutex>
 | 
			
		||||
#include <QFuture>
 | 
			
		||||
#include <poppler-qt5.h>
 | 
			
		||||
#include "pdfpreview.h"
 | 
			
		||||
#include "searchresult.h"
 | 
			
		||||
 | 
			
		||||
class PdfWorker : public QObject
 | 
			
		||||
{
 | 
			
		||||
	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:
 | 
			
		||||
	PdfWorker();
 | 
			
		||||
	void cancelAndWait();
 | 
			
		||||
  public slots:
 | 
			
		||||
	void generatePreviews(QVector<SearchResult> paths, double scalefactor);
 | 
			
		||||
  signals:
 | 
			
		||||
	void previewReady(PdfPreview p);
 | 
			
		||||
	void previewsFinished();
 | 
			
		||||
	QFuture<PdfPreview> generatePreviews(QVector<SearchResult> paths, double scalefactor);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // PDFWORKER_H
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user