Compare commits
	
		
			25 Commits
		
	
	
		
			9d160ed7a0
			...
			483ea04638
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 483ea04638 | |||
| aeafa9560e | |||
| a82818dc43 | |||
| c867652b6f | |||
| f8fe21d50b | |||
| 1e97f8dd26 | |||
| ad0fc74439 | |||
| e44fb1a942 | |||
| 472661bff6 | |||
| 4aa6d43674 | |||
| 2591a4ccba | |||
| d66e395fda | |||
| 0d6fb1d482 | |||
| 8f2e77b152 | |||
| 3bdcb76d8e | |||
| ee18142e36 | |||
| 3e03fed1a2 | |||
| 6439adffc6 | |||
| 02642a147a | |||
| fe29641d0a | |||
| 830226ae59 | |||
| 6a5cb69e27 | |||
| b137dec434 | |||
| f67a37bc21 | |||
| 4a492fb356 | 
| @@ -4,6 +4,8 @@ search terms have been found, as shown in the screenshots below. | ||||
|  | ||||
|  | ||||
| ## Screenshots | ||||
| The screenshots in this section may occasionally be slightly outdated, but they are usually recent enough to get an overall impression of the current state. | ||||
|  | ||||
| ### List | ||||
|  | ||||
|  | ||||
|   | ||||
							
								
								
									
										13
									
								
								gui/gui.pro
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								gui/gui.pro
									
									
									
									
									
								
							| @@ -23,7 +23,8 @@ DEFINES += QT_DEPRECATED_WARNINGS | ||||
| #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0 | ||||
|  | ||||
| SOURCES += \ | ||||
|     ipcclient.cpp \ | ||||
|     ipcpreviewclient.cpp \ | ||||
|     ipcpreviewworker.cpp \ | ||||
|     ipcserver.cpp \ | ||||
|         main.cpp \ | ||||
|         mainwindow.cpp \ | ||||
| @@ -35,11 +36,13 @@ SOURCES += \ | ||||
|     previewresult.cpp \ | ||||
|     previewresultpdf.cpp \ | ||||
|     previewresultplaintext.cpp \ | ||||
|     previewworker.cpp | ||||
|     renderconfig.cpp \ | ||||
|     rendertarget.cpp | ||||
|  | ||||
| HEADERS += \ | ||||
|     ipc.h \ | ||||
|     ipcclient.h \ | ||||
|     ipcpreviewclient.h \ | ||||
|     ipcpreviewworker.h \ | ||||
|     ipcserver.h \ | ||||
|         mainwindow.h \ | ||||
|     clicklabel.h \ | ||||
| @@ -50,8 +53,8 @@ HEADERS += \ | ||||
|     previewresult.h \ | ||||
|     previewresultpdf.h \ | ||||
|     previewresultplaintext.h \ | ||||
|     previewworker.h \ | ||||
|     renderconfig.h | ||||
|     renderconfig.h \ | ||||
|     rendertarget.h | ||||
|  | ||||
| FORMS += \ | ||||
|         mainwindow.ui | ||||
|   | ||||
							
								
								
									
										11
									
								
								gui/ipc.h
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								gui/ipc.h
									
									
									
									
									
								
							| @@ -3,8 +3,13 @@ | ||||
|  | ||||
| enum IPCCommand | ||||
| { | ||||
| 	DocOpen, | ||||
| 	FileOpen, | ||||
| 	AddFile, | ||||
| 	GeneratePreviews = 23, | ||||
| 	StopGeneratePreviews, | ||||
| }; | ||||
|  | ||||
| enum IPCReply | ||||
| { | ||||
| 	FinishedGeneratePreviews, | ||||
| }; | ||||
|  | ||||
| #endif // IPC_H | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| #include <QDataStream> | ||||
| #include "ipcclient.h" | ||||
|  | ||||
| IPCClient::IPCClient(QString socketPath) | ||||
| { | ||||
| 	this->socketPath = socketPath; | ||||
| } | ||||
|  | ||||
| bool IPCClient::sendCommand(IPCCommand command, QStringList args) | ||||
| { | ||||
| 	bool result = false; | ||||
| 	QLocalSocket socket; | ||||
| 	socket.connectToServer(socketPath); | ||||
| 	if(socket.isOpen() && socket.isWritable()) | ||||
| 	{ | ||||
| 		QDataStream stream(&socket); | ||||
| 		stream << command; | ||||
| 		stream << args; | ||||
| 		socket.flush(); | ||||
| 		result = true; | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		qDebug() << "Not connected to IPC server"; | ||||
| 	} | ||||
| 	return result; | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| #ifndef IPCCLIENT_H | ||||
| #define IPCCLIENT_H | ||||
| #include <QLocalSocket> | ||||
| #include <QString> | ||||
| #include <QStringList> | ||||
| #include "ipc.h" | ||||
|  | ||||
| class IPCClient | ||||
| { | ||||
|   private: | ||||
| 	QString socketPath; | ||||
|  | ||||
|   public: | ||||
| 	IPCClient(QString socketPath); | ||||
| 	bool sendCommand(IPCCommand command, QStringList args); | ||||
| }; | ||||
|  | ||||
| #endif // IPCCLIENT_H | ||||
							
								
								
									
										129
									
								
								gui/ipcpreviewclient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								gui/ipcpreviewclient.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| #include <QLocalSocket> | ||||
| #include <QApplication> | ||||
| #include "ipc.h" | ||||
| #include "ipcpreviewclient.h" | ||||
| #include "previewresultpdf.h" | ||||
| #include "previewresultplaintext.h" | ||||
|  | ||||
| bool IPCPreviewClient::connect() | ||||
| { | ||||
| 	if(socket->state() == QLocalSocket::ConnectedState) | ||||
| 	{ | ||||
| 		socket->disconnectFromServer(); | ||||
| 		if(socket->state() == QLocalSocket::ConnectedState) | ||||
| 		{ | ||||
| 			socket->waitForDisconnected(100); | ||||
| 		} | ||||
| 	} | ||||
| 	socket->connectToServer(socketPath); | ||||
| 	socket->waitForConnected(100); | ||||
| 	return socket->state() == QLocalSocket::ConnectedState; | ||||
| } | ||||
|  | ||||
| QSharedPointer<PreviewResult> IPCPreviewClient::deserialize(QByteArray &array) | ||||
| { | ||||
| 	QDataStream stream{&array, QIODevice::ReadOnly}; | ||||
|  | ||||
| 	PreviewResultType type; | ||||
| 	stream >> type; | ||||
| 	if(type == PreviewResultType::PDF) | ||||
| 	{ | ||||
| 		return PreviewResultPdf::deserialize(array); | ||||
| 	} | ||||
| 	if(type == PreviewResultType::PlainText) | ||||
| 	{ | ||||
| 		return PreviewResultPlainText::deserialize(array); | ||||
| 	} | ||||
| 	return QSharedPointer<PreviewResult>(nullptr); | ||||
| } | ||||
|  | ||||
| IPCPreviewClient::IPCPreviewClient() | ||||
| { | ||||
| 	this->socket = new QLocalSocket(this); | ||||
| } | ||||
|  | ||||
| void IPCPreviewClient::setSocketPath(QString socketPath) | ||||
| { | ||||
| 	this->socketPath = socketPath; | ||||
| } | ||||
|  | ||||
| void IPCPreviewClient::startGeneration(RenderConfig config, const QVector<RenderTarget> &targets) | ||||
| { | ||||
| 	this->start(config, targets); | ||||
| } | ||||
|  | ||||
| void IPCPreviewClient::start(RenderConfig config, const QVector<RenderTarget> &targets) | ||||
| { | ||||
| 	if(targets.count() == 0) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if(!connect() || !socket->isOpen()) | ||||
| 	{ | ||||
| 		emit error("Could not connect to IPC worker"); | ||||
| 		return; | ||||
| 	} | ||||
|  | ||||
| 	if(socket->isOpen() && socket->isWritable()) | ||||
| 	{ | ||||
| 		QDataStream stream(socket); | ||||
| 		stream << GeneratePreviews; | ||||
| 		stream << config; | ||||
| 		stream << targets; | ||||
| 		socket->flush(); | ||||
|  | ||||
| 		int numTarget = 0; | ||||
| 		if(socket->isOpen() && socket->isReadable() && socket->state() == QLocalSocket::ConnectedState) | ||||
| 		{ | ||||
| 			do | ||||
| 			{ | ||||
| 				socket->waitForReadyRead(100); | ||||
| 				stream.startTransaction(); | ||||
| 				stream >> numTarget; | ||||
| 			} while(!stream.commitTransaction() && socket->state() == QLocalSocket::ConnectedState); | ||||
| 			if(numTarget != targets.count()) | ||||
| 			{ | ||||
| 				throw std::runtime_error("Server reports less targets than it should"); | ||||
| 			} | ||||
| 		} | ||||
| 		else | ||||
| 		{ | ||||
| 			emit error("Error while trying to process previews: " + socket->errorString()); | ||||
| 			return; | ||||
| 		} | ||||
|  | ||||
| 		int processed = 0; | ||||
| 		++this->currentPreviewGeneration; | ||||
| 		while(socket->isOpen() && socket->isReadable() && processed < targets.count()) | ||||
| 		{ | ||||
| 			QByteArray array; | ||||
| 			do | ||||
| 			{ | ||||
| 				socket->waitForReadyRead(100); | ||||
| 				stream.startTransaction(); | ||||
| 				stream >> array; | ||||
| 			} while(!stream.commitTransaction() && socket->state() == QLocalSocket::ConnectedState); | ||||
| 			emit previewReceived(deserialize(array), this->currentPreviewGeneration); | ||||
| 			++processed; | ||||
| 		} | ||||
| 		if(processed != targets.count()) | ||||
| 		{ | ||||
| 			emit error("IPC worker didn't send enough previews. This is a bug, please report"); | ||||
| 		} | ||||
| 	} | ||||
| 	socket->disconnectFromServer(); | ||||
| 	emit finished(); | ||||
| } | ||||
|  | ||||
| void IPCPreviewClient::stopGeneration() | ||||
| { | ||||
| 	if(!connect() || !socket->isOpen()) | ||||
| 	{ | ||||
| 		emit error("Could not connect to IPC worker"); | ||||
| 		return; | ||||
| 	} | ||||
| 	QDataStream stream(socket); | ||||
| 	stream << StopGeneratePreviews; | ||||
| 	socket->flush(); | ||||
| } | ||||
							
								
								
									
										37
									
								
								gui/ipcpreviewclient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								gui/ipcpreviewclient.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| #ifndef IPCPREVIEWCLIENT_H | ||||
| #define IPCPREVIEWCLIENT_H | ||||
| #include <QObject> | ||||
| #include <QLocalSocket> | ||||
| #include "previewresult.h" | ||||
| #include "renderconfig.h" | ||||
| #include "rendertarget.h" | ||||
|  | ||||
| class IPCPreviewClient : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
|   private: | ||||
| 	unsigned int currentPreviewGeneration = 1; | ||||
| 	QLocalSocket *socket; | ||||
| 	QString socketPath; | ||||
|  | ||||
| 	bool connect(); | ||||
| 	QSharedPointer<PreviewResult> deserialize(QByteArray &array); | ||||
|  | ||||
|   public: | ||||
| 	IPCPreviewClient(); | ||||
| 	~IPCPreviewClient() | ||||
| 	{ | ||||
| 		delete socket; | ||||
| 	} | ||||
| 	void setSocketPath(QString socketPath); | ||||
|   public slots: | ||||
| 	void start(RenderConfig config, const QVector<RenderTarget> &targets); | ||||
| 	void startGeneration(RenderConfig config, const QVector<RenderTarget> &targets); | ||||
| 	void stopGeneration(); | ||||
|   signals: | ||||
| 	void previewReceived(QSharedPointer<PreviewResult> previewResult, unsigned int currentPreviewGeneration); | ||||
| 	void finished(); | ||||
| 	void error(QString); | ||||
| }; | ||||
|  | ||||
| #endif // IPCPREVIEWCLIENT_H | ||||
							
								
								
									
										24
									
								
								gui/ipcpreviewworker.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								gui/ipcpreviewworker.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| #include <QtConcurrent> | ||||
| #include "ipcpreviewworker.h" | ||||
| #include "previewgeneratormapfunctor.h" | ||||
| IPCPreviewWorker::IPCPreviewWorker() | ||||
| { | ||||
| 	this->connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::resultReadyAt, this, | ||||
| 				  [this](int index) { emit previewGenerated(previewWorkerWatcher.resultAt(index)); }); | ||||
| 	connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::finished, this, [this] { emit finished(); }); | ||||
| } | ||||
|  | ||||
| void IPCPreviewWorker::start(RenderConfig config, const QVector<RenderTarget> &targets, QLocalSocket *peer) | ||||
| { | ||||
| 	stop(); | ||||
| 	auto mapFunctor = PreviewGeneratorMapFunctor(); | ||||
| 	mapFunctor.setRenderConfig(config); | ||||
|  | ||||
| 	previewWorkerWatcher.setFuture(QtConcurrent::mapped(targets, mapFunctor)); | ||||
| } | ||||
|  | ||||
| void IPCPreviewWorker::stop() | ||||
| { | ||||
| 	previewWorkerWatcher.cancel(); | ||||
| 	previewWorkerWatcher.waitForFinished(); | ||||
| } | ||||
							
								
								
									
										24
									
								
								gui/ipcpreviewworker.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								gui/ipcpreviewworker.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| #ifndef IPCPREVIEWWORKER_H | ||||
| #define IPCPREVIEWWORKER_H | ||||
| #include <QLocalSocket> | ||||
| #include <QFutureWatcher> | ||||
| #include "renderconfig.h" | ||||
| #include "rendertarget.h" | ||||
| #include "previewgenerator.h" | ||||
|  | ||||
| class IPCPreviewWorker : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
|   private: | ||||
| 	QFutureWatcher<QByteArray> previewWorkerWatcher; | ||||
|  | ||||
|   public: | ||||
| 	IPCPreviewWorker(); | ||||
| 	void start(RenderConfig config, const QVector<RenderTarget> &targets, QLocalSocket *peer); | ||||
| 	void stop(); | ||||
|   signals: | ||||
| 	void previewGenerated(QByteArray); | ||||
| 	void finished(); | ||||
| }; | ||||
|  | ||||
| #endif // IPCPREVIEWWORKER_H | ||||
| @@ -9,13 +9,17 @@ | ||||
| #include "common.h" | ||||
| #include "databasefactory.h" | ||||
| #include "../shared/logger.h" | ||||
| #include "renderconfig.h" | ||||
| #include "rendertarget.h" | ||||
| #include "ipcpreviewworker.h" | ||||
|  | ||||
| IpcServer::IpcServer() | ||||
| { | ||||
| 	this->dbFactory = QSharedPointer<DatabaseFactory>(new DatabaseFactory(Common::databasePath())); | ||||
| 	this->dbService = QSharedPointer<SqliteDbService>(new SqliteDbService(*this->dbFactory.get())); | ||||
| 	this->fileSaver = QSharedPointer<FileSaver>(new FileSaver(*this->dbService.get())); | ||||
| 	/* Only 1, we are doing work for the GUI, not a service for general availability */ | ||||
| 	this->spawningServer.setMaxPendingConnections(1); | ||||
| 	connect(&this->spawningServer, &QLocalServer::newConnection, this, &IpcServer::spawnerNewConnection); | ||||
| 	connect(&this->previewWorker, &IPCPreviewWorker::previewGenerated, this, &IpcServer::handlePreviewGenerated); | ||||
| 	connect(&this->previewWorker, &IPCPreviewWorker::finished, this, [this] { this->currentSocket->flush(); }); | ||||
| } | ||||
|  | ||||
| bool IpcServer::startSpawner(QString socketPath) | ||||
| @@ -24,85 +28,46 @@ bool IpcServer::startSpawner(QString socketPath) | ||||
| 	return this->spawningServer.listen(socketPath); | ||||
| } | ||||
|  | ||||
| bool IpcServer::docOpen(QString path, int pagenum) | ||||
| { | ||||
|  | ||||
| 	QSettings settings; | ||||
| 	QString command = settings.value("pdfviewer").toString(); | ||||
| 	if(path.endsWith(".pdf") && command != "" && command.contains("%p") && command.contains("%f")) | ||||
| 	{ | ||||
| 		QStringList splitted = command.split(" "); | ||||
| 		if(splitted.size() > 1) | ||||
| 		{ | ||||
| 			QString cmd = splitted[0]; | ||||
| 			QStringList args = splitted.mid(1); | ||||
| 			args.replaceInStrings("%f", path); | ||||
| 			args.replaceInStrings("%p", QString::number(pagenum)); | ||||
|  | ||||
| 			QProcess::startDetached(cmd, args); | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||
| 	} | ||||
| 	return true; | ||||
| } | ||||
|  | ||||
| bool IpcServer::fileOpen(QString path) | ||||
| { | ||||
| 	return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||
| } | ||||
|  | ||||
| SaveFileResult IpcServer::addFile(QString file) | ||||
| { | ||||
| 	try | ||||
| 	{ | ||||
| 		return this->fileSaver->addFile(file); | ||||
| 	} | ||||
| 	catch(std::exception &e) | ||||
| 	{ | ||||
| 		Logger::error() << e.what() << Qt::endl; | ||||
| 		return PROCESSFAIL; | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IpcServer::spawnerNewConnection() | ||||
| { | ||||
| 	QScopedPointer<QLocalSocket> socket{this->spawningServer.nextPendingConnection()}; | ||||
| 	if(!socket.isNull()) | ||||
| 	QLocalSocket *socket = this->spawningServer.nextPendingConnection(); | ||||
| 	connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater); | ||||
| 	this->currentSocket = socket; | ||||
| 	if(socket != nullptr) | ||||
| 	{ | ||||
| 		if(!socket->waitForReadyRead()) | ||||
| 		{ | ||||
| 			return; | ||||
| 		} | ||||
| 		QDataStream stream(socket.get()); | ||||
| 		QDataStream stream(socket); | ||||
| 		IPCCommand command; | ||||
| 		QStringList args; | ||||
| 		stream >> command; | ||||
| 		stream >> args; | ||||
| 		if(args.size() < 1) | ||||
| 		if(command == GeneratePreviews) | ||||
| 		{ | ||||
| 			stream << "invalid"; | ||||
| 			return; | ||||
| 		} | ||||
| 		if(command == DocOpen) | ||||
| 		{ | ||||
| 			if(args.size() < 2) | ||||
| 			RenderConfig renderConfig; | ||||
| 			QVector<RenderTarget> targets; | ||||
| 			do | ||||
| 			{ | ||||
| 				stream << "invalid"; | ||||
| 				return; | ||||
| 			} | ||||
| 			docOpen(args[0], args[1].toInt()); | ||||
| 				/* TODO: this is not entirely robust */ | ||||
| 				socket->waitForReadyRead(100); | ||||
| 				stream.startTransaction(); | ||||
| 				stream >> renderConfig >> targets; | ||||
| 			} while(!stream.commitTransaction() && socket->state() == QLocalSocket::ConnectedState); | ||||
|  | ||||
| 			stream << targets.count(); | ||||
| 			socket->flush(); | ||||
| 			previewWorker.start(renderConfig, targets, socket); | ||||
| 		} | ||||
| 		if(command == FileOpen) | ||||
| 		if(command == StopGeneratePreviews) | ||||
| 		{ | ||||
| 			if(args.size() < 1) | ||||
| 			{ | ||||
| 				stream << "invalid"; | ||||
| 				return; | ||||
| 			} | ||||
| 			fileOpen(args[0]); | ||||
| 			previewWorker.stop(); | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void IpcServer::handlePreviewGenerated(QByteArray ba) | ||||
| { | ||||
| 	QDataStream stream{this->currentSocket}; | ||||
| 	stream << ba; | ||||
| 	this->currentSocket->flush(); | ||||
| } | ||||
|   | ||||
| @@ -4,19 +4,19 @@ | ||||
| #include <QLocalServer> | ||||
| #include "ipc.h" | ||||
| #include "filesaver.h" | ||||
| #include "ipcpreviewworker.h" | ||||
|  | ||||
| class IpcServer : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
|   private: | ||||
| 	QSharedPointer<DatabaseFactory> dbFactory; | ||||
| 	QSharedPointer<SqliteDbService> dbService; | ||||
| 	QSharedPointer<FileSaver> fileSaver; | ||||
| 	IPCPreviewWorker previewWorker; | ||||
| 	QLocalServer spawningServer; | ||||
| 	bool docOpen(QString path, int pagenum); | ||||
| 	bool fileOpen(QString path); | ||||
| 	QLocalSocket *currentSocket = nullptr; | ||||
| 	SaveFileResult addFile(QString file); | ||||
|   private slots: | ||||
| 	void spawnerNewConnection(); | ||||
| 	void handlePreviewGenerated(QByteArray ba); | ||||
|  | ||||
|   public: | ||||
| 	IpcServer(); | ||||
|   | ||||
							
								
								
									
										117
									
								
								gui/main.cpp
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								gui/main.cpp
									
									
									
									
									
								
							| @@ -5,6 +5,7 @@ | ||||
| #include <QProcess> | ||||
| #include <QDir> | ||||
| #include <QCommandLineParser> | ||||
| #include <QFileInfo> | ||||
|  | ||||
| #include "mainwindow.h" | ||||
| #include "searchresult.h" | ||||
| @@ -14,63 +15,16 @@ | ||||
| #include "../submodules/exile.h/exile.h" | ||||
| #include "ipcserver.h" | ||||
|  | ||||
| void enableSandbox(QString socketPath) | ||||
| void enableSandbox() | ||||
| { | ||||
| 	struct exile_policy *policy = exile_init_policy(); | ||||
| 	struct exile_policy *policy = exile_create_policy(); | ||||
| 	if(policy == NULL) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to init policy for sandbox"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	QDir dir; | ||||
| 	dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation)); | ||||
| 	dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::CacheLocation)); | ||||
|  | ||||
| 	std::string appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).toStdString(); | ||||
| 	std::string cacheDataLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toStdString(); | ||||
| 	std::string configDataLocation = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation).toStdString(); | ||||
|  | ||||
| 	std::string sockPath = socketPath.toStdString(); | ||||
| 	std::string dbPath = QFileInfo(Common::databasePath()).absolutePath().toStdString(); | ||||
| 	std::string mySelf = QFileInfo("/proc/self/exe").symLinkTarget().toStdString(); | ||||
| 	policy->namespace_options = EXILE_UNSHARE_USER; | ||||
| 	if(exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/") != 0) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to append a path to the path policy"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
|  | ||||
| 	if(exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, | ||||
| 								  appDataLocation.c_str()) != 0) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to append appDataLocation path to the path policy"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	if(exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, | ||||
| 								  cacheDataLocation.c_str()) != 0) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to append cacheDataLocation path to the path policy"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	if(exile_append_path_policies(policy, | ||||
| 								  EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_REMOVE_FILE | EXILE_FS_ALLOW_ALL_WRITE, | ||||
| 								  dbPath.c_str()) != 0) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to append dbPath path to the path policy"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	if(exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_EXEC, mySelf.c_str(), "/lib64", | ||||
| 								  "/lib") != 0) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to append mySelf path to the path policy"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	if(exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, | ||||
| 								  configDataLocation.c_str()) != 0) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to append configDataLocation path to the path policy"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	policy->namespace_options = 0; | ||||
| 	policy->no_new_privs = 1; | ||||
| 	int ret = exile_enable_policy(policy); | ||||
| 	if(ret != 0) | ||||
| 	{ | ||||
| @@ -79,15 +33,46 @@ void enableSandbox(QString socketPath) | ||||
| 	} | ||||
| 	exile_free_policy(policy); | ||||
| } | ||||
|  | ||||
| void enableIpcSandbox() | ||||
| { | ||||
| 	struct exile_policy *policy = exile_create_policy(); | ||||
| 	if(policy == NULL) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to init policy for sandbox"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	policy->namespace_options = EXILE_UNSHARE_NETWORK | EXILE_UNSHARE_USER; | ||||
| 	policy->no_new_privs = 1; | ||||
| 	policy->drop_caps = 1; | ||||
| 	policy->vow_promises = exile_vows_from_str("thread cpath wpath rpath unix stdio prot_exec proc shm fsnotify ioctl"); | ||||
|  | ||||
| 	QString ipcSocketPath = Common::ipcSocketPath(); | ||||
| 	QFileInfo info{ipcSocketPath}; | ||||
| 	QString ipcSocketPathDir = info.absolutePath(); | ||||
| 	std::string stdIpcSocketPath = ipcSocketPathDir.toStdString(); | ||||
|  | ||||
| 	exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, "/"); | ||||
| 	exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, stdIpcSocketPath.c_str()); | ||||
| 	int ret = exile_enable_policy(policy); | ||||
| 	if(ret != 0) | ||||
| 	{ | ||||
| 		qDebug() << "Failed to establish sandbox"; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
| 	exile_free_policy(policy); | ||||
| } | ||||
|  | ||||
| int main(int argc, char *argv[]) | ||||
| { | ||||
| 	QString socketPath = "/tmp/looqs-spawner"; | ||||
| 	QString socketPath = Common::ipcSocketPath(); | ||||
| 	if(argc > 1) | ||||
| 	{ | ||||
| 		QString arg = argv[1]; | ||||
| 		if(arg == "ipc") | ||||
| 		{ | ||||
| 			Common::setupAppInfo(); | ||||
| 			enableIpcSandbox(); | ||||
| 			QApplication a(argc, argv); | ||||
|  | ||||
| 			IpcServer *ipcserver = new IpcServer(); | ||||
| @@ -117,10 +102,24 @@ int main(int argc, char *argv[]) | ||||
| 			return processor.process(); | ||||
| 		} | ||||
| 	} | ||||
| 	QString ipcSocketPath = Common::ipcSocketPath(); | ||||
| 	QFileInfo info{ipcSocketPath}; | ||||
| 	QString ipcSocketPathDir = info.absolutePath(); | ||||
|  | ||||
| 	QDir dir; | ||||
| 	if(!dir.mkpath(ipcSocketPathDir)) | ||||
| 	{ | ||||
| 		qCritical() << "Failed to create dir for ipc socket" << Qt::endl; | ||||
| 		exit(EXIT_FAILURE); | ||||
| 	} | ||||
|  | ||||
| 	QProcess process; | ||||
| 	QStringList args; | ||||
| 	args << "ipc"; | ||||
| 	if(!process.startDetached("/proc/self/exe", args)) | ||||
| 	process.setProcessChannelMode(QProcess::ForwardedChannels); | ||||
|  | ||||
| 	process.start("/proc/self/exe", args); | ||||
| 	if(!process.waitForStarted(5000)) | ||||
| 	{ | ||||
| 		QString errorMsg = "Failed to start IPC server"; | ||||
| 		qDebug() << errorMsg; | ||||
| @@ -141,7 +140,7 @@ int main(int argc, char *argv[]) | ||||
| 		Common::ensureConfigured(); | ||||
| 		if(!parser.isSet("no-sandbox")) | ||||
| 		{ | ||||
| 			enableSandbox(socketPath); | ||||
| 			enableSandbox(); | ||||
| 			qInfo() << "Sandbox: on"; | ||||
| 		} | ||||
| 		else | ||||
| @@ -155,18 +154,18 @@ int main(int argc, char *argv[]) | ||||
| 		QMessageBox::critical(nullptr, "Error", e.message); | ||||
| 		return 1; | ||||
| 	} | ||||
| 	// Keep this post sandbox, afterwards does not work (suspect due to threads, but unconfirmed) | ||||
| 	QApplication a(argc, argv); | ||||
| 	a.setWindowIcon(QIcon(":/icon.svg")); | ||||
| 	QObject::connect(&a, &QApplication::aboutToQuit, &process, &QProcess::kill); | ||||
|  | ||||
| 	qRegisterMetaType<QVector<SearchResult>>("QVector<SearchResult>"); | ||||
| 	qRegisterMetaType<QVector<PreviewResultPdf>>("QVector<PreviewResultPdf>"); | ||||
| 	qRegisterMetaType<PreviewResultPdf>("PreviewResultPdf"); | ||||
| 	qRegisterMetaType<FileScanResult>("FileScanResult"); | ||||
|  | ||||
| 	IPCClient client{socketPath}; | ||||
| 	MainWindow w{0, client}; | ||||
| 	qRegisterMetaType<RenderConfig>("RenderConfig"); | ||||
| 	qRegisterMetaType<QVector<RenderTarget>>("QVector<RenderTarget>"); | ||||
| 	qRegisterMetaType<QSharedPointer<PreviewResult>>("QSharedPointer<PreviewResult>"); | ||||
| 	MainWindow w{0, socketPath}; | ||||
| 	w.showMaximized(); | ||||
|  | ||||
| 	return a.exec(); | ||||
| } | ||||
|   | ||||
| @@ -12,18 +12,44 @@ | ||||
| #include <QComboBox> | ||||
| #include <QtConcurrent/QtConcurrent> | ||||
| #include <QMessageBox> | ||||
| #include <QFileDialog> | ||||
| #include <QScreen> | ||||
| #include "mainwindow.h" | ||||
| #include "ui_mainwindow.h" | ||||
| #include "clicklabel.h" | ||||
| #include "../shared/sqlitesearch.h" | ||||
| #include "../shared/looqsgeneralexception.h" | ||||
| #include "../shared/common.h" | ||||
| #include "ipcpreviewclient.h" | ||||
| #include "previewgenerator.h" | ||||
|  | ||||
| MainWindow::MainWindow(QWidget *parent, IPCClient &client) : QMainWindow(parent), ui(new Ui::MainWindow) | ||||
| MainWindow::MainWindow(QWidget *parent, QString socketPath) : QMainWindow(parent), ui(new Ui::MainWindow) | ||||
| { | ||||
| 	ui->setupUi(this); | ||||
| 	setWindowTitle(QCoreApplication::applicationName()); | ||||
| 	this->ipcClient = &client; | ||||
| 	this->ipcPreviewClient.moveToThread(&this->ipcClientThread); | ||||
| 	this->ipcPreviewClient.setSocketPath(socketPath); | ||||
|  | ||||
| 	connect(&ipcPreviewClient, &IPCPreviewClient::previewReceived, this, &MainWindow::previewReceived, | ||||
| 			Qt::QueuedConnection); | ||||
| 	connect(&ipcPreviewClient, &IPCPreviewClient::finished, this, | ||||
| 			[&] | ||||
| 			{ | ||||
| 				this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->maximum()); | ||||
| 				this->ui->spinPreviewPage->setEnabled(true); | ||||
| 			}); | ||||
| 	connect(&ipcPreviewClient, &IPCPreviewClient::error, this, | ||||
| 			[this](QString msg) | ||||
| 			{ | ||||
| 				qCritical() << msg << Qt::endl; | ||||
| 				QMessageBox::critical(this, "IPC error", msg); | ||||
| 			}); | ||||
|  | ||||
| 	connect(this, &MainWindow::startIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::startGeneration, | ||||
| 			Qt::QueuedConnection); | ||||
| 	connect(this, &MainWindow::stopIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::stopGeneration, | ||||
| 			Qt::QueuedConnection); | ||||
| 	this->ipcClientThread.start(); | ||||
| 	QSettings settings; | ||||
|  | ||||
| 	this->dbFactory = new DatabaseFactory(Common::databasePath()); | ||||
| @@ -45,6 +71,9 @@ MainWindow::MainWindow(QWidget *parent, IPCClient &client) : QMainWindow(parent) | ||||
|  | ||||
| 	QStringList indexPaths = settings.value("indexPaths").toStringList(); | ||||
| 	ui->lstPaths->addItems(indexPaths); | ||||
|  | ||||
| 	ui->spinPreviewPage->setValue(1); | ||||
| 	ui->spinPreviewPage->setMinimum(1); | ||||
| } | ||||
|  | ||||
| void MainWindow::addPathToIndex() | ||||
| @@ -81,16 +110,6 @@ void MainWindow::connectSignals() | ||||
| 					handleSearchError(e.message); | ||||
| 				} | ||||
| 			}); | ||||
| 	connect(&previewWorkerWatcher, &QFutureWatcher<QSharedPointer<PreviewResult>>::resultReadyAt, this, | ||||
| 			[&](int index) { previewReceived(previewWorkerWatcher.resultAt(index)); }); | ||||
| 	connect(&previewWorkerWatcher, &QFutureWatcher<QSharedPointer<PreviewResult>>::progressValueChanged, | ||||
| 			ui->previewProcessBar, &QProgressBar::setValue); | ||||
| 	connect(&previewWorkerWatcher, &QFutureWatcher<QSharedPointer<PreviewResult>>::started, this, | ||||
| 			[&] { ui->indexerTab->setEnabled(false); }); | ||||
|  | ||||
| 	connect(&previewWorkerWatcher, &QFutureWatcher<QSharedPointer<PreviewResult>>::finished, this, | ||||
| 			[&] { ui->indexerTab->setEnabled(true); }); | ||||
|  | ||||
| 	connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated); | ||||
| 	connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this, | ||||
| 			&MainWindow::showSearchResultsContextMenu); | ||||
| @@ -129,6 +148,21 @@ void MainWindow::connectSignals() | ||||
| 			{ ui->btnDeletePath->setEnabled(this->ui->lstPaths->selectedItems().count() > 0); }); | ||||
|  | ||||
| 	connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); }); | ||||
| 	connect(ui->btnChoosePath, &QPushButton::clicked, this, | ||||
| 			[&] | ||||
| 			{ | ||||
| 				QFileDialog dialog(nullptr); | ||||
| 				dialog.setFileMode(QFileDialog::Directory); | ||||
| 				dialog.setOptions(QFileDialog::ShowDirsOnly); | ||||
| 				if(dialog.exec()) | ||||
| 				{ | ||||
| 					auto paths = dialog.selectedFiles(); | ||||
| 					if(paths.size() == 1) | ||||
| 					{ | ||||
| 						ui->lstPaths->addItem(paths[0]); | ||||
| 					} | ||||
| 				} | ||||
| 			}); | ||||
| } | ||||
|  | ||||
| void MainWindow::spinPreviewPageValueChanged(int val) | ||||
| @@ -245,16 +279,21 @@ void MainWindow::tabChanged() | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview) | ||||
| void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration) | ||||
| { | ||||
| 	if(preview->hasPreview()) | ||||
| 	if(previewGeneration < this->currentPreviewGeneration) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->value() + 1); | ||||
| 	if(!preview.isNull() && preview->hasPreview()) | ||||
| 	{ | ||||
| 		QString docPath = preview->getDocumentPath(); | ||||
| 		auto previewPage = preview->getPage(); | ||||
|  | ||||
| 		ClickLabel *label = dynamic_cast<ClickLabel *>(preview->createPreviewWidget()); | ||||
| 		ui->scrollAreaWidgetContents->layout()->addWidget(label); | ||||
| 		connect(label, &ClickLabel::leftClick, [this, docPath, previewPage]() { ipcDocOpen(docPath, previewPage); }); | ||||
| 		connect(label, &ClickLabel::leftClick, [this, docPath, previewPage]() { openDocument(docPath, previewPage); }); | ||||
| 		connect(label, &ClickLabel::rightClick, | ||||
| 				[this, docPath, previewPage]() | ||||
| 				{ | ||||
| @@ -341,10 +380,8 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results) | ||||
| 	ui->treeResultsList->resizeColumnToContents(1); | ||||
| 	previewDirty = !this->previewableSearchResults.empty(); | ||||
|  | ||||
| 	int numpages = ceil(static_cast<double>(this->previewableSearchResults.size()) / previewsPerPage); | ||||
| 	ui->spinPreviewPage->setMinimum(1); | ||||
| 	ui->spinPreviewPage->setMaximum(numpages); | ||||
| 	ui->spinPreviewPage->setValue(1); | ||||
|  | ||||
| 	if(previewTabActive() && previewDirty) | ||||
| 	{ | ||||
| 		makePreviews(1); | ||||
| @@ -360,12 +397,10 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results) | ||||
|  | ||||
| void MainWindow::makePreviews(int page) | ||||
| { | ||||
|  | ||||
| 	this->previewWorkerWatcher.cancel(); | ||||
| 	this->previewWorkerWatcher.waitForFinished(); | ||||
|  | ||||
| 	QCoreApplication::processEvents(); // Maybe not necessary anymore, depends on whether it's possible that a slot is | ||||
| 									   // still to be fired. | ||||
| 	if(this->previewableSearchResults.empty()) | ||||
| 	{ | ||||
| 		return; | ||||
| 	} | ||||
| 	qDeleteAll(ui->scrollAreaWidgetContents->children()); | ||||
|  | ||||
| 	ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout()); | ||||
| @@ -393,14 +428,43 @@ void MainWindow::makePreviews(int page) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	PreviewWorker worker; | ||||
| 	int end = previewsPerPage; | ||||
| 	int begin = page * previewsPerPage - previewsPerPage; | ||||
| 	this->previewWorkerWatcher.setFuture(worker.generatePreviews(this->previewableSearchResults.mid(begin, end), | ||||
| 																 wordsToHighlight, scaleText.toInt() / 100.)); | ||||
| 	ui->previewProcessBar->setMaximum(this->previewWorkerWatcher.progressMaximum()); | ||||
| 	ui->previewProcessBar->setMinimum(this->previewWorkerWatcher.progressMinimum()); | ||||
| 	if(begin < 0) | ||||
| 	{ | ||||
| 		// Should not happen actually | ||||
| 		begin = 0; | ||||
| 	} | ||||
|  | ||||
| 	RenderConfig renderConfig; | ||||
| 	renderConfig.scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * (scaleText.toInt() / 100.); | ||||
| 	renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * (scaleText.toInt() / 100.); | ||||
| 	renderConfig.wordsToHighlight = wordsToHighlight; | ||||
|  | ||||
| 	QVector<RenderTarget> targets; | ||||
| 	for(SearchResult &sr : this->previewableSearchResults) | ||||
| 	{ | ||||
| 		RenderTarget renderTarget; | ||||
| 		renderTarget.path = sr.fileData.absPath; | ||||
|  | ||||
| 		for(unsigned int pagenum : sr.pages) | ||||
| 		{ | ||||
| 			renderTarget.page = (int)pagenum; | ||||
| 			targets.append(renderTarget); | ||||
| 		} | ||||
| 	} | ||||
| 	int numpages = ceil(static_cast<double>(targets.size()) / previewsPerPage); | ||||
| 	ui->spinPreviewPage->setMaximum(numpages); | ||||
| 	targets = targets.mid(begin, end); | ||||
|  | ||||
| 	ui->lblTotalPreviewPagesCount->setText(QString::number(numpages)); | ||||
| 	ui->previewProcessBar->setMaximum(targets.count()); | ||||
| 	ui->previewProcessBar->setMinimum(0); | ||||
| 	ui->previewProcessBar->setValue(0); | ||||
| 	ui->previewProcessBar->setVisible(this->previewableSearchResults.size() > 0); | ||||
| 	++this->currentPreviewGeneration; | ||||
| 	this->ui->spinPreviewPage->setEnabled(false); | ||||
| 	emit startIpcPreviews(renderConfig, targets); | ||||
| } | ||||
|  | ||||
| void MainWindow::handleSearchError(QString error) | ||||
| @@ -414,27 +478,39 @@ void MainWindow::createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo) | ||||
| 				   [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); }); | ||||
| 	menu.addAction("Copy full path to clipboard", | ||||
| 				   [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); }); | ||||
| 	menu.addAction("Open containing folder", [this, &fileInfo] { this->ipcFileOpen(fileInfo.absolutePath()); }); | ||||
| 	menu.addAction("Open containing folder", [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); }); | ||||
| } | ||||
|  | ||||
| void MainWindow::ipcDocOpen(QString path, int num) | ||||
| void MainWindow::openDocument(QString path, int num) | ||||
| { | ||||
| 	QStringList args; | ||||
| 	args << path; | ||||
| 	args << QString::number(num); | ||||
| 	this->ipcClient->sendCommand(DocOpen, args); | ||||
| 	QSettings settings; | ||||
| 	QString command = settings.value("pdfviewer").toString(); | ||||
| 	if(path.endsWith(".pdf") && command != "" && command.contains("%p") && command.contains("%f")) | ||||
| 	{ | ||||
| 		QStringList splitted = command.split(" "); | ||||
| 		if(splitted.size() > 1) | ||||
| 		{ | ||||
| 			QString cmd = splitted[0]; | ||||
| 			QStringList args = splitted.mid(1); | ||||
| 			args.replaceInStrings("%f", path); | ||||
| 			args.replaceInStrings("%p", QString::number(num)); | ||||
| 			QProcess::startDetached(cmd, args); | ||||
| 		} | ||||
| 	} | ||||
| 	else | ||||
| 	{ | ||||
| 		QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| void MainWindow::ipcFileOpen(QString path) | ||||
| void MainWindow::openFile(QString path) | ||||
| { | ||||
| 	QStringList args; | ||||
| 	args << path; | ||||
| 	this->ipcClient->sendCommand(FileOpen, args); | ||||
| 	QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||
| } | ||||
|  | ||||
| void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i) | ||||
| { | ||||
| 	ipcFileOpen(item->text(1)); | ||||
| 	openFile(item->text(1)); | ||||
| } | ||||
|  | ||||
| void MainWindow::showSearchResultsContextMenu(const QPoint &point) | ||||
|   | ||||
| @@ -9,9 +9,8 @@ | ||||
| #include <QFutureWatcher> | ||||
| #include <QSqlDatabase> | ||||
| #include <QLocalSocket> | ||||
| #include "previewworker.h" | ||||
| #include "../shared/looqsquery.h" | ||||
| #include "ipcclient.h" | ||||
| #include "ipcpreviewclient.h" | ||||
| #include "indexer.h" | ||||
| namespace Ui | ||||
| { | ||||
| @@ -23,7 +22,7 @@ class MainWindow : public QMainWindow | ||||
| 	Q_OBJECT | ||||
|  | ||||
|   public: | ||||
| 	explicit MainWindow(QWidget *parent, IPCClient &client); | ||||
| 	explicit MainWindow(QWidget *parent, QString socketPath); | ||||
| 	~MainWindow(); | ||||
|   signals: | ||||
| 	void beginSearch(const QString &query); | ||||
| @@ -33,13 +32,14 @@ class MainWindow : public QMainWindow | ||||
| 	DatabaseFactory *dbFactory; | ||||
| 	SqliteDbService *dbService; | ||||
| 	Ui::MainWindow *ui; | ||||
| 	IPCClient *ipcClient; | ||||
| 	IPCPreviewClient ipcPreviewClient; | ||||
| 	QThread ipcClientThread; | ||||
|  | ||||
| 	Indexer *indexer; | ||||
| 	QFileIconProvider iconProvider; | ||||
| 	bool previewDirty; | ||||
| 	QSqlDatabase db; | ||||
| 	QFutureWatcher<QVector<SearchResult>> searchWatcher; | ||||
| 	QFutureWatcher<QSharedPointer<PreviewResult>> previewWorkerWatcher; | ||||
| 	void add(QString path, unsigned int page); | ||||
| 	QVector<SearchResult> previewableSearchResults; | ||||
| 	void connectSignals(); | ||||
| @@ -53,20 +53,24 @@ class MainWindow : public QMainWindow | ||||
| 	LooqsQuery contentSearchQuery; | ||||
| 	int previewsPerPage; | ||||
| 	void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo); | ||||
| 	void ipcDocOpen(QString path, int num); | ||||
| 	void ipcFileOpen(QString path); | ||||
|  | ||||
| 	void openDocument(QString path, int num); | ||||
| 	void openFile(QString path); | ||||
| 	unsigned int currentPreviewGeneration = 1; | ||||
|   private slots: | ||||
| 	void lineEditReturnPressed(); | ||||
| 	void treeSearchItemActivated(QTreeWidgetItem *item, int i); | ||||
| 	void showSearchResultsContextMenu(const QPoint &point); | ||||
| 	void tabChanged(); | ||||
| 	void previewReceived(QSharedPointer<PreviewResult> preview); | ||||
| 	void previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration); | ||||
| 	void comboScaleChanged(int i); | ||||
| 	void spinPreviewPageValueChanged(int val); | ||||
| 	void startIndexing(); | ||||
| 	void finishIndexing(); | ||||
| 	void addPathToIndex(); | ||||
|  | ||||
|   signals: | ||||
| 	void startIpcPreviews(RenderConfig config, const QVector<RenderTarget> &targets); | ||||
| 	void stopIpcPreviews(); | ||||
| }; | ||||
|  | ||||
| #endif // MAINWINDOW_H | ||||
|   | ||||
| @@ -27,7 +27,7 @@ | ||||
|        <enum>QTabWidget::South</enum> | ||||
|       </property> | ||||
|       <property name="currentIndex"> | ||||
|        <number>2</number> | ||||
|        <number>1</number> | ||||
|       </property> | ||||
|       <widget class="QWidget" name="resultsTab"> | ||||
|        <attribute name="title"> | ||||
| @@ -155,6 +155,13 @@ | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item> | ||||
|            <widget class="QLabel" name="lblTotalPreviewPagesCount"> | ||||
|             <property name="text"> | ||||
|              <string/> | ||||
|             </property> | ||||
|            </widget> | ||||
|           </item> | ||||
|           <item> | ||||
|            <spacer name="horizontalSpacer"> | ||||
|             <property name="orientation"> | ||||
| @@ -349,7 +356,7 @@ | ||||
|     <item> | ||||
|      <widget class="QProgressBar" name="previewProcessBar"> | ||||
|       <property name="value"> | ||||
|        <number>24</number> | ||||
|        <number>0</number> | ||||
|       </property> | ||||
|      </widget> | ||||
|     </item> | ||||
|   | ||||
| @@ -9,7 +9,7 @@ | ||||
| class PreviewGenerator | ||||
| { | ||||
|   public: | ||||
| 	virtual PreviewResult *generate(RenderConfig config, QString documentPath, unsigned int page) = 0; | ||||
| 	virtual QSharedPointer<PreviewResult> generate(RenderConfig config, QString documentPath, unsigned int page) = 0; | ||||
| 	virtual ~PreviewGenerator() | ||||
| 	{ | ||||
| 	} | ||||
|   | ||||
| @@ -10,16 +10,15 @@ void PreviewGeneratorMapFunctor::setRenderConfig(RenderConfig config) | ||||
| 	this->renderConfig = config; | ||||
| } | ||||
|  | ||||
| QSharedPointer<PreviewResult> PreviewGeneratorMapFunctor::operator()(const QSharedPointer<PreviewResult> &renderResult) | ||||
| QByteArray PreviewGeneratorMapFunctor::operator()(const RenderTarget &renderTarget) | ||||
| { | ||||
| 	QFileInfo info{renderResult->getDocumentPath()}; | ||||
| 	QFileInfo info{renderTarget.path}; | ||||
| 	PreviewGenerator *previewGenerator = PreviewGenerator::get(info); | ||||
| 	if(previewGenerator == nullptr) | ||||
| 	{ | ||||
| 		return QSharedPointer<PreviewResult>(); | ||||
| 		return QByteArray{}; | ||||
| 	} | ||||
| 	auto preview = | ||||
| 		previewGenerator->generate(this->renderConfig, renderResult->getDocumentPath(), renderResult->getPage()); | ||||
| 	auto preview = previewGenerator->generate(this->renderConfig, renderTarget.path, renderTarget.page); | ||||
|  | ||||
| 	return QSharedPointer<PreviewResult>(preview); | ||||
| 	return preview->serialize(); | ||||
| } | ||||
|   | ||||
| @@ -2,8 +2,9 @@ | ||||
| #define PREVIEWGENERATORMAPFUNCTOR_H | ||||
|  | ||||
| #include "renderconfig.h" | ||||
| #include "previewgenerator.h" | ||||
| #include "rendertarget.h" | ||||
|  | ||||
| #include "previewgenerator.h" | ||||
| class PreviewGeneratorMapFunctor | ||||
| { | ||||
|  | ||||
| @@ -16,13 +17,13 @@ class PreviewGeneratorMapFunctor | ||||
| 	RenderConfig renderConfig; | ||||
|  | ||||
|   public: | ||||
| 	typedef QSharedPointer<PreviewResult> result_type; | ||||
| 	typedef QByteArray result_type; | ||||
|  | ||||
| 	PreviewGeneratorMapFunctor(); | ||||
|  | ||||
| 	void setRenderConfig(RenderConfig config); | ||||
|  | ||||
| 	QSharedPointer<PreviewResult> operator()(const QSharedPointer<PreviewResult> &renderResult); | ||||
| 	QByteArray operator()(const RenderTarget &renderTarget); | ||||
| }; | ||||
|  | ||||
| #endif // PREVIEWGENERATORMAPFUNCTOR_H | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| #include <QMutexLocker> | ||||
| #include <QPainter> | ||||
|  | ||||
| #include "previewgeneratorpdf.h" | ||||
|  | ||||
| static QMutex cacheMutex; | ||||
| @@ -24,18 +23,18 @@ Poppler::Document *PreviewGeneratorPdf::document(QString path) | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| PreviewResult *PreviewGeneratorPdf::generate(RenderConfig config, QString documentPath, unsigned int page) | ||||
| QSharedPointer<PreviewResult> PreviewGeneratorPdf::generate(RenderConfig config, QString documentPath, | ||||
| 															unsigned int page) | ||||
| { | ||||
| 	PreviewResultPdf *result = new PreviewResultPdf(documentPath, page); | ||||
|  | ||||
| 	Poppler::Document *doc = document(documentPath); | ||||
| 	if(doc == nullptr) | ||||
| 	{ | ||||
| 		return result; | ||||
| 		return QSharedPointer<PreviewResult>(result); | ||||
| 	} | ||||
| 	if(doc->isLocked()) | ||||
| 	{ | ||||
| 		return result; | ||||
| 		return QSharedPointer<PreviewResult>(result); | ||||
| 	} | ||||
| 	int p = (int)page - 1; | ||||
| 	if(p < 0) | ||||
| @@ -55,5 +54,5 @@ PreviewResult *PreviewGeneratorPdf::generate(RenderConfig config, QString docume | ||||
| 		} | ||||
| 	} | ||||
| 	result->previewImage = img; | ||||
| 	return result; | ||||
| 	return QSharedPointer<PreviewResult>(result); | ||||
| } | ||||
|   | ||||
| @@ -13,7 +13,7 @@ class PreviewGeneratorPdf : public PreviewGenerator | ||||
|   public: | ||||
| 	using PreviewGenerator::PreviewGenerator; | ||||
|  | ||||
| 	PreviewResult *generate(RenderConfig config, QString documentPath, unsigned int page); | ||||
| 	QSharedPointer<PreviewResult> generate(RenderConfig config, QString documentPath, unsigned int page); | ||||
|  | ||||
| 	~PreviewGeneratorPdf() | ||||
| 	{ | ||||
|   | ||||
| @@ -3,13 +3,14 @@ | ||||
| #include "previewgeneratorplaintext.h" | ||||
| #include "previewresultplaintext.h" | ||||
|  | ||||
| PreviewResult *PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath, unsigned int page) | ||||
| QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath, | ||||
| 																  unsigned int page) | ||||
| { | ||||
| 	PreviewResultPlainText *result = new PreviewResultPlainText(documentPath, page); | ||||
| 	QFile file(documentPath); | ||||
| 	if(!file.open(QFile::ReadOnly | QFile::Text)) | ||||
| 	{ | ||||
| 		return result; | ||||
| 		return QSharedPointer<PreviewResultPlainText>(result); | ||||
| 	} | ||||
| 	QTextStream in(&file); | ||||
|  | ||||
| @@ -77,5 +78,5 @@ PreviewResult *PreviewGeneratorPlainText::generate(RenderConfig config, QString | ||||
| 	header += "<hr>"; | ||||
|  | ||||
| 	result->setText(header + resulText.replace("\n", "<br>")); | ||||
| 	return result; | ||||
| 	return QSharedPointer<PreviewResultPlainText>(result); | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ class PreviewGeneratorPlainText : public PreviewGenerator | ||||
| { | ||||
|   public: | ||||
| 	using PreviewGenerator::PreviewGenerator; | ||||
| 	PreviewResult *generate(RenderConfig config, QString documentPath, unsigned int page); | ||||
| 	QSharedPointer<PreviewResult> generate(RenderConfig config, QString documentPath, unsigned int page); | ||||
| }; | ||||
|  | ||||
| #endif // PREVIEWGENERATORPLAINTEXT_H | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "previewresult.h" | ||||
|  | ||||
| PreviewResult::PreviewResult() | ||||
| { | ||||
| } | ||||
| @@ -33,3 +32,11 @@ unsigned int PreviewResult::getPage() const | ||||
| { | ||||
| 	return this->page; | ||||
| } | ||||
|  | ||||
| QByteArray PreviewResult::serialize() const | ||||
| { | ||||
| 	QByteArray result; | ||||
| 	QDataStream stream{&result, QIODevice::WriteOnly}; | ||||
| 	stream << 0 << this->documentPath << this->page; | ||||
| 	return result; | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,12 @@ | ||||
| #define PREVIEWRESULT_H | ||||
| #include "clicklabel.h" | ||||
|  | ||||
| enum PreviewResultType | ||||
| { | ||||
| 	PDF = 1, | ||||
| 	PlainText | ||||
| }; | ||||
|  | ||||
| class PreviewResult | ||||
| { | ||||
|   protected: | ||||
| @@ -17,6 +23,7 @@ class PreviewResult | ||||
| 	virtual bool hasPreview(); | ||||
| 	QString getDocumentPath() const; | ||||
| 	unsigned int getPage() const; | ||||
| 	virtual QByteArray serialize() const; | ||||
| }; | ||||
|  | ||||
| #endif // PREVIEWRESULT_H | ||||
|   | ||||
| @@ -1,5 +1,4 @@ | ||||
| #include "previewresultpdf.h" | ||||
|  | ||||
| PreviewResultPdf::PreviewResultPdf(const PreviewResult &o) | ||||
| { | ||||
| 	this->documentPath = o.getDocumentPath(); | ||||
| @@ -19,3 +18,27 @@ bool PreviewResultPdf::hasPreview() | ||||
| 	bool result = !this->previewImage.isNull(); | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| QByteArray PreviewResultPdf::serialize() const | ||||
| { | ||||
| 	QByteArray result; | ||||
| 	QDataStream stream{&result, QIODevice::WriteOnly}; | ||||
| 	PreviewResultType type = PreviewResultType::PDF; | ||||
| 	stream << type << this->documentPath << this->page << this->previewImage; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| QSharedPointer<PreviewResultPdf> PreviewResultPdf::deserialize(QByteArray &ba) | ||||
| { | ||||
| 	PreviewResultPdf *result = new PreviewResultPdf(); | ||||
| 	PreviewResultType type; | ||||
|  | ||||
| 	QDataStream stream{&ba, QIODevice::ReadOnly}; | ||||
| 	stream >> type; | ||||
| 	if(type != PreviewResultType::PDF) | ||||
| 	{ | ||||
| 		throw std::runtime_error("Invalid byte array: Not a pdf preview"); | ||||
| 	} | ||||
| 	stream >> result->documentPath >> result->page >> result->previewImage; | ||||
| 	return QSharedPointer<PreviewResultPdf>(result); | ||||
| } | ||||
|   | ||||
| @@ -12,6 +12,10 @@ class PreviewResultPdf : public PreviewResult | ||||
|  | ||||
| 	QWidget *createPreviewWidget() override; | ||||
| 	bool hasPreview() override; | ||||
|  | ||||
| 	QByteArray serialize() const; | ||||
|  | ||||
| 	static QSharedPointer<PreviewResultPdf> deserialize(QByteArray &ba); | ||||
| }; | ||||
|  | ||||
| #endif // PREVIEWRESULTPDF_H | ||||
|   | ||||
| @@ -28,3 +28,27 @@ void PreviewResultPlainText::setText(QString text) | ||||
| { | ||||
| 	this->text = text; | ||||
| } | ||||
|  | ||||
| QByteArray PreviewResultPlainText::serialize() const | ||||
| { | ||||
| 	QByteArray result; | ||||
| 	QDataStream stream{&result, QIODevice::WriteOnly}; | ||||
| 	PreviewResultType type = PreviewResultType::PlainText; | ||||
| 	stream << type << this->documentPath << this->page << this->text; | ||||
| 	return result; | ||||
| } | ||||
|  | ||||
| QSharedPointer<PreviewResultPlainText> PreviewResultPlainText::deserialize(QByteArray &ba) | ||||
| { | ||||
| 	PreviewResultPlainText *result = new PreviewResultPlainText(); | ||||
| 	PreviewResultType type; | ||||
|  | ||||
| 	QDataStream stream{&ba, QIODevice::ReadOnly}; | ||||
| 	stream >> type; | ||||
| 	if(type != PreviewResultType::PlainText) | ||||
| 	{ | ||||
| 		throw std::runtime_error("Invalid byte array: Not a pdf preview"); | ||||
| 	} | ||||
| 	stream >> result->documentPath >> result->page >> result->text; | ||||
| 	return QSharedPointer<PreviewResultPlainText>(result); | ||||
| } | ||||
|   | ||||
| @@ -15,6 +15,9 @@ class PreviewResultPlainText : public PreviewResult | ||||
| 	bool hasPreview() override; | ||||
|  | ||||
| 	void setText(QString text); | ||||
|  | ||||
| 	QByteArray serialize() const; | ||||
| 	static QSharedPointer<PreviewResultPlainText> deserialize(QByteArray &ba); | ||||
| }; | ||||
|  | ||||
| #endif // PREVIEWRESULTPLAINTEXT_H | ||||
|   | ||||
| @@ -1,39 +0,0 @@ | ||||
| #include <QApplication> | ||||
| #include <QScreen> | ||||
| #include <QScopedPointer> | ||||
| #include <QMutexLocker> | ||||
| #include <QtConcurrent/QtConcurrent> | ||||
| #include <QtConcurrent/QtConcurrentMap> | ||||
| #include <atomic> | ||||
| #include "previewworker.h" | ||||
|  | ||||
| PreviewWorker::PreviewWorker() | ||||
| { | ||||
| } | ||||
|  | ||||
| QFuture<QSharedPointer<PreviewResult>> PreviewWorker::generatePreviews(const QVector<SearchResult> paths, | ||||
| 																	   QVector<QString> wordsToHighlight, | ||||
| 																	   double scalefactor) | ||||
| { | ||||
| 	QVector<QSharedPointer<PreviewResult>> previews; | ||||
|  | ||||
| 	for(const SearchResult &sr : paths) | ||||
| 	{ | ||||
| 		for(unsigned int page : sr.pages) | ||||
| 		{ | ||||
| 			QSharedPointer<PreviewResult> ptr = | ||||
| 				QSharedPointer<PreviewResult>(new PreviewResult{sr.fileData.absPath, page}); | ||||
| 			previews.append(ptr); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	RenderConfig renderConfig; | ||||
| 	renderConfig.scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor; | ||||
| 	renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor; | ||||
| 	renderConfig.wordsToHighlight = wordsToHighlight; | ||||
|  | ||||
| 	auto mapFunctor = new PreviewGeneratorMapFunctor(); | ||||
| 	mapFunctor->setRenderConfig(renderConfig); | ||||
|  | ||||
| 	return QtConcurrent::mapped(previews, *mapFunctor); | ||||
| } | ||||
| @@ -1,29 +0,0 @@ | ||||
| #ifndef PREVIEWWORKER_H | ||||
| #define PREVIEWWORKER_H | ||||
| #include <QObject> | ||||
| #include <QImage> | ||||
| #include <QHash> | ||||
| #include <QThread> | ||||
| #include <QMutex> | ||||
| #include <QWaitCondition> | ||||
| #include <QMutex> | ||||
| #include <QFuture> | ||||
| #include "previewresultpdf.h" | ||||
| #include "searchresult.h" | ||||
| #include "previewgenerator.h" | ||||
| #include "previewworker.h" | ||||
| #include "previewgeneratorpdf.h" | ||||
| #include "previewgeneratormapfunctor.h" | ||||
|  | ||||
| class PreviewWorker : public QObject | ||||
| { | ||||
| 	Q_OBJECT | ||||
|   public: | ||||
| 	PreviewWorker(); | ||||
| 	QSharedPointer<PreviewGenerator> createGenerator(QString path); | ||||
|  | ||||
| 	QFuture<QSharedPointer<PreviewResult>> generatePreviews(const QVector<SearchResult> paths, | ||||
| 															QVector<QString> wordsToHighlight, double scalefactor); | ||||
| }; | ||||
|  | ||||
| #endif // PREVIEWWORKER_H | ||||
							
								
								
									
										17
									
								
								gui/renderconfig.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								gui/renderconfig.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| #include "renderconfig.h" | ||||
|  | ||||
| QDataStream &operator<<(QDataStream &out, const RenderConfig &rc) | ||||
| { | ||||
| 	out << rc.scaleX; | ||||
| 	out << rc.scaleY; | ||||
| 	out << rc.wordsToHighlight; | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QDataStream &operator>>(QDataStream &in, RenderConfig &rc) | ||||
| { | ||||
| 	in >> rc.scaleX; | ||||
| 	in >> rc.scaleY; | ||||
| 	in >> rc.wordsToHighlight; | ||||
| 	return in; | ||||
| } | ||||
| @@ -1,12 +1,17 @@ | ||||
| #ifndef RENDERCONFIG_H | ||||
| #define RENDERCONFIG_H | ||||
| #include <QVector> | ||||
| #include <QDataStream> | ||||
|  | ||||
| struct RenderConfig | ||||
| { | ||||
| 	double scaleX = 50 / 100.; | ||||
| 	double scaleY = scaleX; | ||||
| 	QVector<QString> wordsToHighlight; | ||||
| 	friend QDataStream &operator<<(QDataStream &out, const RenderConfig &rc); | ||||
| 	friend QDataStream &operator>>(QDataStream &in, RenderConfig &rc); | ||||
| }; | ||||
|  | ||||
| QDataStream &operator<<(QDataStream &out, const RenderConfig &rc); | ||||
| QDataStream &operator>>(QDataStream &in, RenderConfig &rc); | ||||
| #endif // RENDERCONFIG_H | ||||
|   | ||||
							
								
								
									
										14
									
								
								gui/rendertarget.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								gui/rendertarget.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #include <QDataStream> | ||||
|  | ||||
| #include "rendertarget.h" | ||||
| QDataStream &operator<<(QDataStream &out, const RenderTarget &rc) | ||||
| { | ||||
| 	out << rc.path << rc.page; | ||||
| 	return out; | ||||
| } | ||||
|  | ||||
| QDataStream &operator>>(QDataStream &in, RenderTarget &rc) | ||||
| { | ||||
| 	in >> rc.path >> rc.page; | ||||
| 	return in; | ||||
| } | ||||
							
								
								
									
										15
									
								
								gui/rendertarget.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								gui/rendertarget.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| #ifndef RENDERTARGET_H | ||||
| #define RENDERTARGET_H | ||||
| #include <QString> | ||||
| struct RenderTarget | ||||
| { | ||||
|   public: | ||||
| 	QString path; | ||||
| 	int page; | ||||
| 	friend QDataStream &operator<<(QDataStream &out, const RenderTarget &rc); | ||||
| 	friend QDataStream &operator>>(QDataStream &in, RenderTarget &rc); | ||||
| }; | ||||
|  | ||||
| QDataStream &operator<<(QDataStream &out, const RenderTarget &rc); | ||||
| QDataStream &operator>>(QDataStream &in, RenderTarget &rc); | ||||
| #endif // RENDERTARGET_H | ||||
| @@ -156,6 +156,9 @@ QString Common::databasePath() | ||||
|  | ||||
| QString Common::ipcSocketPath() | ||||
| { | ||||
| 	QSettings settings; | ||||
| 	return settings.value(SETTINGS_KEY_IPCSOCKETPATH, "/tmp/looqs-spawner").toString(); | ||||
| 	return "/tmp/.looqs/looqs-ipc-socket"; | ||||
|  | ||||
| 	/* May not a good idea to set it in the settings and probably nobody would ever bother to change it anyway */ | ||||
| 	// QSettings settings; | ||||
| 	// return settings.value(SETTINGS_KEY_IPCSOCKETPATH, "/tmp/.looqs/looqs-ipc-socket").toString(); | ||||
| } | ||||
|   | ||||
| @@ -43,7 +43,6 @@ void ParallelDirScanner::handleWorkersProgress(unsigned int progress) | ||||
|  | ||||
| void ParallelDirScanner::handleWorkersFinish() | ||||
| { | ||||
| 	Logger::info() << "Worker finished"; | ||||
| 	// no mutexes required due to queued connection | ||||
| 	++finishedWorkers; | ||||
| 	if(this->stopToken.load(std::memory_order_seq_cst) || finishedWorkers == getThreadsNum()) | ||||
| @@ -65,7 +64,6 @@ unsigned int ParallelDirScanner::getThreadsNum() const | ||||
|  | ||||
| void ParallelDirScanner::scan() | ||||
| { | ||||
| 	Logger::info() << "I am scanning"; | ||||
| 	this->stopToken.store(false, std::memory_order_relaxed); | ||||
| 	this->finishedWorkers = 0; | ||||
| 	this->processedPaths = 0; | ||||
|   | ||||
		Reference in New Issue
	
	Block a user