コミットを比較

..

22 コミット

作成者 SHA1 メッセージ 日付
80a4551f8b update README 2022-05-29 11:12:43 +02:00
fa10cb606b mainwindow: Disable page switcher while generation is running
This prevents 'spam'. User may have scrolled 10 pages forward, while
we are still generating old pages. Then the user wonders why
they arrive so late.

So disable switching pages while the generation is still running

It is unlikely that a user will have to quickly go through search
results like that.
2022-05-29 10:51:48 +02:00
b90ff840d9 gui: init previewProcessBar with 0 on start 2022-05-29 10:44:19 +02:00
86cc16d15d gui: IPCPreviewWorker(): Don't allocate mapfunctor on heap 2022-05-29 10:44:19 +02:00
69c2956a1f gui: Add label showing total number of preview pages 2022-05-29 10:44:19 +02:00
82a4205c23 gui: mainwindow: Fix preview page number calculation
The paging now works on the actual pages to be rendered.
2022-05-29 10:44:19 +02:00
8d96f6e4ce ipc: Place socket in /tmp/.looqs/, remove ipc path settings 2022-05-29 10:44:19 +02:00
d39157b58d gui: main: Enable exile.h for IPC preview generation 2022-05-29 10:44:19 +02:00
a43ab169b5 gui: Begin simple IPC error reporting 2022-05-29 10:44:19 +02:00
ee19692a7a gui: Open files/previews directly without IPC again
Since the main GUI process is not sandboxed again
2022-05-29 10:44:19 +02:00
773325f2de gui: ipc: Support cancellation of preview generation 2022-05-29 10:44:19 +02:00
065dcf8906 gui: main: Kill IPCServer process on exit 2022-05-29 10:44:19 +02:00
13f28c37c6 gui: mainwindow: Use new IPCPreviewClient 2022-05-29 10:44:19 +02:00
8f2e77b152 gui: Introduce IPCPreviewClient 2022-05-29 10:44:19 +02:00
3bdcb76d8e gui: PreviewResult: Add serialization() methods for IPC 2022-05-27 09:29:28 +02:00
ee18142e36 gui: PreviewGenerator*: Wrap PreviewResult in QSharedPointer 2022-05-27 09:28:21 +02:00
3e03fed1a2 gui: IpcServer: Use IPCPreviewWorker 2022-05-27 09:26:37 +02:00
6439adffc6 gui: Begin IPCPreviewWorker 2022-05-27 09:26:03 +02:00
02642a147a gui: Retire IPCClient and PreviewWorker 2022-05-27 09:24:42 +02:00
fe29641d0a IpcServer: Remove docOpen(), fileOpen(). MainWindow will do it
As it is no longer subject to sandboxing as a whole, it is not
restricted and thus, should call these functions itself
2022-05-17 19:23:03 +02:00
830226ae59 RenderConfig: Add serialization, basically for IPC 2022-05-17 19:20:06 +02:00
6a5cb69e27 gui: Add RenderTarget struct, an IPC helper class 2022-05-17 19:19:02 +02:00
35個のファイルの変更603行の追加275行の削除

ファイルの表示

@ -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
![Screenshot looqs results](https://garage.quitesimple.org/assets/looqs/opearting_systems_looqs.png)

ファイルの表示

@ -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

ファイルの表示

@ -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 ノーマルファイル
ファイルの表示

@ -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 ノーマルファイル
ファイルの表示

@ -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 ノーマルファイル
ファイルの表示

@ -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 ノーマルファイル
ファイルの表示

@ -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();

ファイルの表示

@ -5,6 +5,7 @@
#include <QProcess>
#include <QDir>
#include <QCommandLineParser>
#include <QFileInfo>
#include "mainwindow.h"
#include "searchresult.h"
@ -32,15 +33,46 @@ void enableSandbox()
}
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();
@ -70,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;
@ -108,17 +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();
}

ファイルの表示

@ -13,18 +13,43 @@
#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());
@ -46,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()
@ -82,16 +110,18 @@ 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,
/* connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::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);
@ -261,16 +291,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]()
{
@ -357,10 +392,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);
@ -376,12 +409,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());
@ -409,14 +440,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)
@ -430,27 +490,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 ノーマルファイル
ファイルの表示

@ -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 ノーマルファイル
ファイルの表示

@ -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 ノーマルファイル
ファイルの表示

@ -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();
}