Compare commits

...

9 Commits

Author SHA1 Message Date
Albert S. 31f0568a87 shared: LimitQueue: Change limit type to int
More consistent with "QQueue::size()" and silences warning
2022-08-28 13:10:39 +02:00
Albert S. 238f9add49 gui: Set a more reasonable maximum width for previews
They won't be this large but in particular for vertical scroll,
this makes way more sense.
2022-08-28 13:05:33 +02:00
Albert S. 7c63ee9178 gui: mainwindow: Set center alignment for previews
Noticable now that we have vertical scrolling
2022-08-28 13:04:06 +02:00
Albert S. 1edfcc8f23 gui: PreviewGeneratorPlainText: Escape html before working on text
We use this semi-HTML mode to highlight words, but if we already
have tags in the document this does not work quite well.

Thus, escape the string before further processing it
2022-08-28 13:01:46 +02:00
Albert S. 2df273dee3 gui: PreviewGenerator*: Fallback to partial highlighting if no whole word match 2022-08-28 12:44:42 +02:00
Albert S. 5a47f5949f gui: PreviewGeneratorPlainText: Rework snippets selection
The current snippet selection is useless for many queries.

Attempt a more reasonable snippet selection by prioritizing
those where all words are contained. The more words a snippet
has, the more important it is considered.Therefore, those will
be at the top.

Only highlight whole words
2022-08-27 22:18:43 +02:00
Albert S. e6a0c0daee gui: PreviewGeneratorPdf: Only highlight whole words
Only highlight whole words, which is less confusing
2022-08-27 22:17:10 +02:00
Albert S. 11b070ed42 gui: mainwindow: Improve preview status reporting
Better represent the state of available previews.

Only add paths to ui->comboPreviewFiles once
2022-08-27 12:04:35 +02:00
Albert S. 47874b3706 gui: ipcworker,ipcserver: Refactor
Crashes were observed, faulting in libQtNetwork.
Those were rather rare. We also have no traces.

Probably depends on some order signal/slots were
processed. Remove shared states between connections,
such as the IPCPreviewWorker and socket instance in IPCServer.
2022-08-27 11:15:45 +02:00
8 changed files with 222 additions and 64 deletions

View File

@ -1,16 +1,50 @@
#include <QtConcurrent>
#include "ipcpreviewworker.h"
#include "previewgeneratormapfunctor.h"
IPCPreviewWorker::IPCPreviewWorker()
IPCPreviewWorker::IPCPreviewWorker(QLocalSocket *peer)
{
this->peer = peer;
this->connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::resultReadyAt, this,
[this](int index) { emit previewGenerated(previewWorkerWatcher.resultAt(index)); });
connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::finished, this, [this] { emit finished(); });
[this](int index)
{
if(this->peer != nullptr)
{
QDataStream stream{this->peer};
stream << previewWorkerWatcher.resultAt(index);
this->peer->flush();
}
});
connect(&previewWorkerWatcher, &QFutureWatcher<QByteArray>::finished, this, &IPCPreviewWorker::shutdownSocket);
connect(this->peer, &QLocalSocket::disconnected, this, &IPCPreviewWorker::shutdownSocket);
}
void IPCPreviewWorker::start(RenderConfig config, const QVector<RenderTarget> &targets, QLocalSocket *peer)
void IPCPreviewWorker::shutdownSocket()
{
if(cleaned)
{
return;
}
cleaned = true;
if(this->peer != nullptr)
{
if(this->peer->state() == QLocalSocket::ConnectedState)
{
this->peer->flush();
this->peer->waitForBytesWritten();
this->peer->disconnectFromServer();
if(this->peer->state() != QLocalSocket::UnconnectedState)
{
this->peer->waitForDisconnected();
}
}
delete this->peer;
this->peer = nullptr;
}
emit finished();
}
void IPCPreviewWorker::start(RenderConfig config, const QVector<RenderTarget> &targets)
{
stop();
auto mapFunctor = PreviewGeneratorMapFunctor();
mapFunctor.setRenderConfig(config);

View File

@ -11,13 +11,21 @@ class IPCPreviewWorker : public QObject
Q_OBJECT
private:
QFutureWatcher<QByteArray> previewWorkerWatcher;
QLocalSocket *peer;
bool cleaned = false;
public:
IPCPreviewWorker();
void start(RenderConfig config, const QVector<RenderTarget> &targets, QLocalSocket *peer);
IPCPreviewWorker(QLocalSocket *peer);
void start(RenderConfig config, const QVector<RenderTarget> &targets);
void stop();
~IPCPreviewWorker()
{
delete this->peer;
}
private slots:
void shutdownSocket();
signals:
void previewGenerated(QByteArray);
void finished();
};

View File

@ -18,8 +18,6 @@ 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)
@ -31,8 +29,6 @@ bool IpcServer::startSpawner(QString socketPath)
void IpcServer::spawnerNewConnection()
{
QLocalSocket *socket = this->spawningServer.nextPendingConnection();
connect(socket, &QLocalSocket::disconnected, socket, &QLocalSocket::deleteLater);
this->currentSocket = socket;
if(socket != nullptr)
{
if(!socket->waitForReadyRead())
@ -53,21 +49,22 @@ void IpcServer::spawnerNewConnection()
stream.startTransaction();
stream >> renderConfig >> targets;
} while(!stream.commitTransaction() && socket->state() == QLocalSocket::ConnectedState);
stream << targets.count();
socket->flush();
previewWorker.start(renderConfig, targets, socket);
if(socket->state() == QLocalSocket::ConnectedState)
{
stream << targets.count();
socket->flush();
IPCPreviewWorker *previewWorker = new IPCPreviewWorker(socket);
connect(previewWorker, &IPCPreviewWorker::finished, this, [previewWorker] { delete previewWorker; });
previewWorker->start(renderConfig, targets);
}
else
{
delete socket;
}
}
if(command == StopGeneratePreviews)
{
previewWorker.stop();
/* TODO: implement */
}
}
}
void IpcServer::handlePreviewGenerated(QByteArray ba)
{
QDataStream stream{this->currentSocket};
stream << ba;
this->currentSocket->flush();
}

View File

@ -10,13 +10,10 @@ 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();

View File

@ -15,6 +15,7 @@
#include <QFileDialog>
#include <QScreen>
#include <QProgressDialog>
#include <QDesktopWidget>
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "clicklabel.h"
@ -647,6 +648,7 @@ void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned
headerLabel->setText(QString("Path: ") + preview->getDocumentPath());
ClickLabel *label = dynamic_cast<ClickLabel *>(preview->createPreviewWidget());
label->setMaximumWidth(QApplication::desktop()->availableGeometry().width() - 200);
QVBoxLayout *previewLayout = new QVBoxLayout();
@ -678,6 +680,7 @@ void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned
previewLayout->setMargin(0);
previewLayout->insertStretch(0, 1);
previewLayout->insertStretch(-1, 1);
previewLayout->setAlignment(Qt::AlignCenter);
QWidget *previewWidget = new QWidget();
previewWidget->setLayout(previewLayout);
@ -806,6 +809,7 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
ui->comboPreviewFiles->clear();
ui->comboPreviewFiles->addItem("All previews");
ui->comboPreviewFiles->setVisible(true);
ui->lblTotalPreviewPagesCount->setText("");
bool hasDeleted = false;
QHash<QString, bool> seenMap;
@ -816,7 +820,6 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
if(!seenMap.contains(absPath))
{
seenMap[absPath] = true;
QString fileName = pathInfo.fileName();
QTreeWidgetItem *item = new QTreeWidgetItem(ui->treeResultsList);
@ -830,17 +833,18 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
bool exists = pathInfo.exists();
if(exists)
{
if(!result.wasContentSearch)
if(result.wasContentSearch)
{
continue;
}
if(!pathInfo.suffix().contains("htm")) // hack until we can preview them properly...
{
if(PreviewGenerator::get(pathInfo) != nullptr)
if(!pathInfo.suffix().contains("htm")) // hack until we can preview them properly...
{
this->previewableSearchResults.append(result);
ui->comboPreviewFiles->addItem(result.fileData.absPath);
if(PreviewGenerator::get(pathInfo) != nullptr)
{
this->previewableSearchResults.append(result);
if(!seenMap.contains(result.fileData.absPath))
{
ui->comboPreviewFiles->addItem(result.fileData.absPath);
}
}
}
}
}
@ -848,6 +852,7 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
{
hasDeleted = true;
}
seenMap[absPath] = true;
}
ui->treeResultsList->resizeColumnToContents(0);
@ -863,6 +868,7 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
}
QString statusText = "Results: " + QString::number(results.size()) + " files";
statusText += ", previewable: " + QString::number(this->previewableSearchResults.count());
if(hasDeleted)
{
statusText += " WARNING: Some files are inaccessible. No preview available for those. Index may be out of sync";

View File

@ -1,5 +1,6 @@
#include <QMutexLocker>
#include <QPainter>
#include <QRegularExpression>
#include "previewgeneratorpdf.h"
static QMutex cacheMutex;
@ -17,6 +18,7 @@ Poppler::Document *PreviewGeneratorPdf::document(QString path)
return nullptr;
}
result->setRenderHint(Poppler::Document::TextAntialiasing);
QMutexLocker locker(&cacheMutex);
documentcache.insert(path, result);
locker.unlock();
@ -45,7 +47,12 @@ QSharedPointer<PreviewResult> PreviewGeneratorPdf::generate(RenderConfig config,
QImage img = pdfPage->renderToImage(config.scaleX, config.scaleY);
for(QString &word : config.wordsToHighlight)
{
QList<QRectF> rects = pdfPage->search(word, Poppler::Page::SearchFlag::IgnoreCase);
QList<QRectF> rects =
pdfPage->search(word, Poppler::Page::SearchFlag::IgnoreCase | Poppler::Page::SearchFlag::WholeWords);
if(rects.empty())
{
rects = pdfPage->search(word, Poppler::Page::SearchFlag::IgnoreCase);
}
for(QRectF &rect : rects)
{
QPainter painter(&img);

View File

@ -1,4 +1,5 @@
#include <QTextStream>
#include <QRegularExpression>
#include "previewgeneratorplaintext.h"
#include "previewresultplaintext.h"
@ -57,6 +58,7 @@ QString PreviewGeneratorPlainText::generatePreviewText(QString content, RenderCo
++i;
}
resulText = resulText.toHtmlEscaped();
QString header = "<b>" + fileName + "</b> ";
for(QString &word : config.wordsToHighlight)
{
@ -74,10 +76,19 @@ QString PreviewGeneratorPlainText::generatePreviewText(QString content, RenderCo
return header + resulText.replace("\n", "<br>").mid(0, 1000);
}
struct Snippet
{
/* Contains each line number and line of the snippet*/
QString snippetText;
/* How many times a word occurs in the snippetText */
QHash<QString, int> wordCountMap;
};
QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in, RenderConfig config, QString fileName)
{
QString resultText;
const unsigned int contextLinesCount = 2;
QVector<Snippet> snippets;
const int contextLinesCount = 2;
LimitQueue<QString> queue(contextLinesCount);
QString currentLine;
currentLine.reserve(512);
@ -85,38 +96,73 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
/* How many lines to read after a line with a match (like grep -A ) */
int justReadLinesCount = -1;
auto appendLine = [&resultText](int lineNumber, QString &line)
{ resultText.append(QString("<b>%1</b>%2<br>").arg(lineNumber).arg(line)); };
struct Snippet currentSnippet;
QHash<QString, int> countmap;
QString header = "<b>" + fileName + "</b> ";
unsigned int snippetsCount = 0;
unsigned int lineCount = 0;
while(in.readLineInto(&currentLine) && snippetsCount < MAX_SNIPPETS)
auto appendLine = [&currentSnippet, &config](int lineNumber, QString &line)
{
int foundWordsCount = 0;
for(QString &word : config.wordsToHighlight)
{
QRegularExpression searchRegex("\\b" + word + "\\b");
bool containsRegex = line.contains(searchRegex);
bool contains = false;
if(!containsRegex)
{
contains = line.contains(word, Qt::CaseInsensitive);
}
if(containsRegex || contains)
{
currentSnippet.wordCountMap[word] = currentSnippet.wordCountMap.value(word, 0) + 1;
QString replacementString = "<span style=\"background-color: yellow;\">" + word + "</span>";
if(containsRegex)
{
line.replace(searchRegex, replacementString);
}
else
{
line.replace(word, replacementString, Qt::CaseInsensitive);
}
++foundWordsCount;
}
}
currentSnippet.snippetText.append(QString("<b>%1</b>%2<br>").arg(lineNumber).arg(line));
return foundWordsCount;
};
unsigned int lineCount = 0;
while(in.readLineInto(&currentLine))
{
currentLine = currentLine.toHtmlEscaped();
++lineCount;
bool matched = false;
if(justReadLinesCount > 0)
{
appendLine(lineCount, currentLine);
--justReadLinesCount;
int result = appendLine(lineCount, currentLine);
if(justReadLinesCount == 1 && result > 0)
{
justReadLinesCount = contextLinesCount;
}
else
{
--justReadLinesCount;
}
continue;
}
if(justReadLinesCount == 0)
{
resultText += "---<br>";
currentSnippet.snippetText += "---<br>";
justReadLinesCount = -1;
++snippetsCount;
snippets.append(currentSnippet);
currentSnippet = {};
}
for(QString &word : config.wordsToHighlight)
{
if(currentLine.contains(word, Qt::CaseInsensitive))
{
countmap[word] = countmap.value(word, 0) + 1;
matched = true;
currentLine.replace(word, "<span style=\"background-color: yellow;\">" + word + "</span>",
Qt::CaseInsensitive);
break;
}
}
if(matched)
@ -125,7 +171,6 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
{
int queuedLineCount = lineCount - queue.size();
QString queuedLine = queue.dequeue();
appendLine(queuedLineCount, queuedLine);
}
appendLine(lineCount, currentLine);
@ -137,13 +182,77 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
}
}
if(!currentSnippet.snippetText.isEmpty())
{
currentSnippet.snippetText += "---<br>";
snippets.append(currentSnippet);
}
std::sort(snippets.begin(), snippets.end(),
[](Snippet &a, Snippet &b)
{
int differentWordsA = 0;
int totalWordsA = 0;
int differentWordsB = 0;
int totalWordsB = 0;
for(int count : a.wordCountMap.values())
{
if(count > 0)
{
++differentWordsA;
}
totalWordsA += count;
}
for(int count : b.wordCountMap.values())
{
if(count > 0)
{
++differentWordsB;
}
totalWordsB += count;
}
if(differentWordsA > differentWordsB)
{
return true;
}
if(differentWordsA == differentWordsB)
{
return totalWordsA > totalWordsB;
}
return false;
});
QString resultText = "";
unsigned int snippetsCount = 0;
QString header = "<b>" + fileName + "</b> ";
QHash<QString, int> totalWordCountMap;
bool isTruncated = false;
for(Snippet &snippet : snippets)
{
if(snippetsCount++ < MAX_SNIPPETS)
{
resultText += snippet.snippetText;
}
else
{
isTruncated = true;
}
for(auto it = snippet.wordCountMap.keyValueBegin(); it != snippet.wordCountMap.keyValueEnd(); it++)
{
totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
}
}
if(isTruncated)
{
header += "(truncated) ";
}
for(QString &word : config.wordsToHighlight)
{
header += word + ": " + QString::number(countmap[word]) + " ";
}
if(snippetsCount == MAX_SNIPPETS)
{
header += "(truncated)";
header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
}
header += "<hr>";

View File

@ -6,11 +6,11 @@ template <class T> class LimitQueue
{
protected:
QQueue<T> queue;
unsigned int limit = 0;
int limit = 0;
public:
LimitQueue();
LimitQueue(unsigned int limit)
LimitQueue(int limit)
{
this->limit = limit;
}
@ -34,7 +34,7 @@ template <class T> class LimitQueue
return queue.dequeue();
}
void setLimit(unsigned int limit)
void setLimit(int limit)
{
this->limit = limit;
}