Порівняти коміти

...

13 Коміти

Автор SHA1 Повідомлення Дата
483ea04638 update README 2022-05-29 11:20:28 +02:00
aeafa9560e 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 11:20:28 +02:00
a82818dc43 gui: init previewProcessBar with 0 on start 2022-05-29 11:20:28 +02:00
c867652b6f gui: IPCPreviewWorker(): Don't allocate mapfunctor on heap 2022-05-29 11:20:28 +02:00
f8fe21d50b gui: Add label showing total number of preview pages 2022-05-29 11:20:28 +02:00
1e97f8dd26 gui: mainwindow: Fix preview page number calculation
The paging now works on the actual pages to be rendered.
2022-05-29 11:20:28 +02:00
ad0fc74439 ipc: Place socket in /tmp/.looqs/, remove ipc path settings 2022-05-29 11:20:28 +02:00
e44fb1a942 gui: main: Enable exile.h for IPC preview generation 2022-05-29 11:20:28 +02:00
472661bff6 gui: Begin simple IPC error reporting 2022-05-29 11:20:28 +02:00
4aa6d43674 gui: Open files/previews directly without IPC again
Since the main GUI process is not sandboxed again
2022-05-29 11:20:28 +02:00
2591a4ccba gui: ipc: Support cancellation of preview generation 2022-05-29 11:20:28 +02:00
d66e395fda gui: main: Kill IPCServer process on exit 2022-05-29 11:20:28 +02:00
0d6fb1d482 gui: mainwindow: Use new IPCPreviewClient 2022-05-29 11:20:28 +02:00
12 змінених файлів з 229 додано та 86 видалено

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

@ -61,7 +61,8 @@ void IPCPreviewClient::start(RenderConfig config, const QVector<RenderTarget> &t
if(!connect() || !socket->isOpen())
{
// TODO: ERROR
emit error("Could not connect to IPC worker");
return;
}
if(socket->isOpen() && socket->isWritable())
@ -88,7 +89,8 @@ void IPCPreviewClient::start(RenderConfig config, const QVector<RenderTarget> &t
}
else
{
// TODO: ERROR
emit error("Error while trying to process previews: " + socket->errorString());
return;
}
int processed = 0;
@ -107,7 +109,7 @@ void IPCPreviewClient::start(RenderConfig config, const QVector<RenderTarget> &t
}
if(processed != targets.count())
{
// TODO: ERROR
emit error("IPC worker didn't send enough previews. This is a bug, please report");
}
}
socket->disconnectFromServer();
@ -118,7 +120,8 @@ void IPCPreviewClient::stopGeneration()
{
if(!connect() || !socket->isOpen())
{
// TODO: ERROR
emit error("Could not connect to IPC worker");
return;
}
QDataStream stream(socket);
stream << StopGeneratePreviews;

@ -31,6 +31,7 @@ class IPCPreviewClient : public QObject
signals:
void previewReceived(QSharedPointer<PreviewResult> previewResult, unsigned int currentPreviewGeneration);
void finished();
void error(QString);
};
#endif // IPCPREVIEWCLIENT_H

@ -3,29 +3,22 @@
#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)
{
connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::resultReadyAt, this,
[peer, this](int index)
{
QDataStream stream{peer};
stream << previewWorkerWatcher.resultAt(index);
peer->flush();
});
connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::finished, this,
[peer]
{
/* TODO /
stop();
auto mapFunctor = PreviewGeneratorMapFunctor();
mapFunctor.setRenderConfig(config);
/*peer->waitForBytesWritten();
peer->disconnectFromServer();
peer->deleteLater();*/
});
auto mapFunctor = new PreviewGeneratorMapFunctor();
mapFunctor->setRenderConfig(config);
previewWorkerWatcher.setFuture(QtConcurrent::mapped(targets, *mapFunctor));
previewWorkerWatcher.setFuture(QtConcurrent::mapped(targets, mapFunctor));
}
void IPCPreviewWorker::stop()
{
previewWorkerWatcher.cancel();
previewWorkerWatcher.waitForFinished();
}

@ -15,6 +15,10 @@ class IPCPreviewWorker : public QObject
public:
IPCPreviewWorker();
void start(RenderConfig config, const QVector<RenderTarget> &targets, QLocalSocket *peer);
void stop();
signals:
void previewGenerated(QByteArray);
void finished();
};
#endif // IPCPREVIEWWORKER_H

@ -15,7 +15,11 @@
IpcServer::IpcServer()
{
/* 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)
@ -28,6 +32,7 @@ void IpcServer::spawnerNewConnection()
{
QLocalSocket *socket = this->spawningServer.nextPendingConnection();
connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
this->currentSocket = socket;
if(socket != nullptr)
{
if(!socket->waitForReadyRead())
@ -39,10 +44,8 @@ void IpcServer::spawnerNewConnection()
stream >> command;
if(command == GeneratePreviews)
{
IPCPreviewWorker *worker = new IPCPreviewWorker();
RenderConfig renderConfig;
QVector<RenderTarget> targets;
do
{
/* TODO: this is not entirely robust */
@ -53,7 +56,18 @@ void IpcServer::spawnerNewConnection()
stream << targets.count();
socket->flush();
worker->start(renderConfig, targets, socket);
previewWorker.start(renderConfig, targets, socket);
}
if(command == StopGeneratePreviews)
{
previewWorker.stop();
}
}
}
void IpcServer::handlePreviewGenerated(QByteArray ba)
{
QDataStream stream{this->currentSocket};
stream << ba;
this->currentSocket->flush();
}

@ -4,14 +4,19 @@
#include <QLocalServer>
#include "ipc.h"
#include "filesaver.h"
#include "ipcpreviewworker.h"
class IpcServer : public QObject
{
Q_OBJECT
private:
IPCPreviewWorker previewWorker;
QLocalServer spawningServer;
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,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);
@ -261,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]()
{
@ -357,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);
@ -376,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());
@ -409,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)
@ -430,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>

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