Compare commits
65 Commits
95d4a12005
...
a8184191b3
Author | SHA1 | Date | |
---|---|---|---|
a8184191b3 | |||
a132485924 | |||
9b51e00737 | |||
d2d576e617 | |||
30414e3da3 | |||
8734d56d09 | |||
08da6b4349 | |||
629c1efba5 | |||
d73674937d | |||
59aa02f0cd | |||
1536781bda | |||
57bb5c48c8 | |||
84e13e432b | |||
e8f095f821 | |||
c99827e854 | |||
4d0d9ba9c6 | |||
e3440beae7 | |||
8194476fa6 | |||
2a024a9b40 | |||
0503325c47 | |||
62d3eac498 | |||
45de97d8fb | |||
622916db04 | |||
ef3f7bc72a | |||
a349d9bfe0 | |||
1cc7053193 | |||
0af7d4a3dc | |||
be41fab5d5 | |||
c51fd3c555 | |||
715023a3ee | |||
4234967ef5 | |||
d483d05db1 | |||
564b5ddae8 | |||
d7705241ee | |||
f3fbf4a1dc | |||
56414ee5e2 | |||
478d57b342 | |||
d43c35819d | |||
3d8b086f53 | |||
294455b861 | |||
7066cc1a45 | |||
bb8906ace4 | |||
d4864d4810 | |||
2e3b008207 | |||
ea1d027621 | |||
b10c2edf05 | |||
c0657947b1 | |||
1f35e2120e | |||
404ce22ce6 | |||
0cbd0dd9eb | |||
d816603a1c | |||
95b3d1fce2 | |||
32286cae4b | |||
c51487c4b2 | |||
407ee1210c | |||
bb5a793300 | |||
ba636bf0fc | |||
88ee2383f7 | |||
b1f3e95622 | |||
890925929a | |||
3e387b99f8 | |||
530ad9c334 | |||
ad84c8acf7 | |||
ebea074fcb | |||
4dede9538c |
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "submodules/exile.h"]
|
||||||
|
path = submodules/exile.h
|
||||||
|
url = https://gitea.quitesimple.org/crtxcr/exile.h
|
44
README.md
44
README.md
@ -1,30 +1,56 @@
|
|||||||
# looqs - Looks for files. And looks inside them
|
# looqs - FTS for the Linux desktop with previews for search results
|
||||||
looqs creates a full text search for your files. It allows you to look at previews where your
|
looqs creates a full text search index for your files. It allows you to look at previews where your
|
||||||
search terms have been found.
|
search terms have been found, as shown in the screenshots below.
|
||||||
|
|
||||||
Currently, this allows you search all indexed pdfs and take a look at the pages side by side in an instant.
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
Coming soon™
|
### List
|
||||||
|
![Screenshot looqs results](https://garage.quitesimple.org/assets/looqs/opearting_systems_looqs.png)
|
||||||
|
|
||||||
|
### Preview
|
||||||
|
![Screenshot looqs](https://garage.quitesimple.org/assets/looqs/orwell.png)
|
||||||
|
![Screenshot looqs search fstream](https://garage.quitesimple.org/assets/looqs/fstream_write.png)
|
||||||
|
|
||||||
|
|
||||||
## Goals
|
## Current status
|
||||||
|
Last version: 2022-0X-XX, v0.1
|
||||||
|
|
||||||
|
Please see [Changelog](CHANGELOG.md) for a human readable list of changes.
|
||||||
|
|
||||||
|
|
||||||
|
## Goals and principles
|
||||||
* **Find & Preview**. Instead of merely telling you where your search phrase has been found, it should also render the corresponding portion/pages of the documents and highlight the searched words.
|
* **Find & Preview**. Instead of merely telling you where your search phrase has been found, it should also render the corresponding portion/pages of the documents and highlight the searched words.
|
||||||
* **No daemons**. As other solutions are prone to have annoying daemons running that eat system resources away, this solution should make do without daemons if possible.
|
* **No daemons**. As some other desktop search projects are prone to have annoying daemons running that eat system resources away, this solution should make do without daemons where possible.
|
||||||
* **Easy setup**. Similiarly, there should be no need for heavy-weight databases. Instead, this solution tries to squeeze out the most from simple approaches. In particular, it relies on sqlite.
|
* **Easy setup**. Similiarly, there should be no need for heavy-weight databases. Instead, this solution tries to squeeze out the most from simple approaches. In particular, it relies on sqlite.
|
||||||
* **GUI & CLI**. Provide CLI interfaces and GUI interfaces
|
* **GUI & CLI**. Provide CLI interfaces and GUI interfaces
|
||||||
|
* **Sandboxing**. As reading and rendering lots of formats naturally opens the door for security bugs, those tasks are offloaded to small, sandboxed sub-processes to mitigate the effect of exploited vulnerabilities.
|
||||||
|
|
||||||
|
## Supported platforms
|
||||||
|
Linux (on amd64) is currently the main focus. Currently, I don't plan on supporting anything else and the sandboxing architecture does not make it likely. I suppose a version without sandboxing might be conceivable for other platforms, but I have no plans or resources to actively target anything but Linux at this point.
|
||||||
|
|
||||||
|
### Licence
|
||||||
|
GPLv3.
|
||||||
|
|
||||||
|
### Contributing
|
||||||
|
Fow now, github issues and pull-requests are preferred, but you can also just email
|
||||||
|
your patches or issues to : looqs at quitesimple.org
|
||||||
|
|
||||||
|
|
||||||
## Build
|
## Build
|
||||||
### Ubuntu 21.04
|
|
||||||
|
### Ubuntu 21.10/22.04
|
||||||
```
|
```
|
||||||
|
git submodule init
|
||||||
|
git submodule update
|
||||||
sudo apt install build-essential qtbase5-dev libpoppler-qt5-dev libuchardet-dev libquazip5-dev
|
sudo apt install build-essential qtbase5-dev libpoppler-qt5-dev libuchardet-dev libquazip5-dev
|
||||||
qmake
|
qmake
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
Coming soon™
|
Please see [Usage.md](USAGE.md) for the user manual.
|
||||||
|
|
||||||
## Packages
|
## Packages
|
||||||
Coming soon™
|
Coming soon™
|
||||||
|
33
TODO
33
TODO
@ -1,33 +0,0 @@
|
|||||||
general database classes instead of sqlite specific code
|
|
||||||
database: set a number of paths to default index. will require an indexer code though
|
|
||||||
sandboxing!
|
|
||||||
allow from GUI to ues commandlin, e. g. "/add ..." would be the same as "qss add ..." in termial
|
|
||||||
|
|
||||||
PdfPreview: Use some kind of paging instead of memory limit
|
|
||||||
Consider injecting Logger (default stdout/stderr) to classes instead of using Logger::info()/Logger::error()::
|
|
||||||
sync/share GUI and CLI data-structures. Better to share codebase in general
|
|
||||||
- cli: tagging
|
|
||||||
- cli: command line parser: wrong position of [options]
|
|
||||||
- ability to set the WHERE condition (allow editing the SQL query)
|
|
||||||
- Basic OCR in images (screenshots)
|
|
||||||
- Index .ebup
|
|
||||||
- Preview for: .txt, source code files, .ebup, images
|
|
||||||
- index: DVD menus, media metadata in audio/files etc?
|
|
||||||
- Stats: Number of results found
|
|
||||||
- PdfPreview: Files per file, or directory (so basically filter the
|
|
||||||
results)
|
|
||||||
- Hide PdfPreview Tab if there are no pdfs in the results
|
|
||||||
-Tagging: add an "addtag" utility, to assign tags to folder and files
|
|
||||||
-gui: Search expclitly for tags, list by tags, remove tags, add tags
|
|
||||||
-gui: Filter already found results. For example, "show only folders",
|
|
||||||
or just search with those results.
|
|
||||||
-split each tab into own class, not everything in mainwindow.cpp?
|
|
||||||
Menu:
|
|
||||||
-Delete from index
|
|
||||||
-Delete from fs
|
|
||||||
-Reindex
|
|
||||||
Filter:
|
|
||||||
type:d
|
|
||||||
type:file
|
|
||||||
mtime:
|
|
||||||
tag:
|
|
36
cli/cli.pro
36
cli/cli.pro
@ -14,57 +14,29 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||||||
# In order to do so, uncomment the following line.
|
# In order to do so, uncomment the following line.
|
||||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
LIBS += -luchardet -lpoppler-qt5 -lquazip5
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
encodingdetector.cpp \
|
|
||||||
processor.cpp \
|
|
||||||
pdfprocessor.cpp \
|
|
||||||
defaulttextprocessor.cpp \
|
|
||||||
commandadd.cpp \
|
commandadd.cpp \
|
||||||
tagstripperprocessor.cpp \
|
|
||||||
nothingprocessor.cpp \
|
|
||||||
odtprocessor.cpp \
|
|
||||||
utils.cpp \
|
|
||||||
odsprocessor.cpp \
|
|
||||||
commanddelete.cpp \
|
commanddelete.cpp \
|
||||||
commandupdate.cpp \
|
commandupdate.cpp \
|
||||||
filesaver.cpp \
|
|
||||||
databasefactory.cpp \
|
|
||||||
sqlitedbservice.cpp \
|
|
||||||
logger.cpp \
|
|
||||||
commandsearch.cpp \
|
commandsearch.cpp \
|
||||||
commandlist.cpp
|
commandlist.cpp \
|
||||||
|
command.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
encodingdetector.h \
|
|
||||||
processor.h \
|
|
||||||
pagedata.h \
|
|
||||||
pdfprocessor.h \
|
|
||||||
defaulttextprocessor.h \
|
|
||||||
command.h \
|
command.h \
|
||||||
commandadd.h \
|
commandadd.h \
|
||||||
tagstripperprocessor.h \
|
|
||||||
nothingprocessor.h \
|
|
||||||
odtprocessor.h \
|
|
||||||
utils.h \
|
|
||||||
odsprocessor.h \
|
|
||||||
commanddelete.h \
|
commanddelete.h \
|
||||||
commandupdate.h \
|
commandupdate.h \
|
||||||
filesaver.h \
|
|
||||||
databasefactory.h \
|
|
||||||
sqlitedbservice.h \
|
|
||||||
logger.h \
|
|
||||||
commandsearch.h \
|
commandsearch.h \
|
||||||
commandlist.h
|
commandlist.h
|
||||||
INCLUDEPATH += /usr/include/poppler/qt5/ /usr/include/quazip5
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../shared/release/ -lshared
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../shared/release/ -lshared
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../shared/debug/ -lshared
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../shared/debug/ -lshared
|
||||||
else:unix: LIBS += -L$$OUT_PWD/../shared/ -lshared
|
else:unix: LIBS += -L$$OUT_PWD/../shared/ -lshared
|
||||||
|
|
||||||
|
LIBS += -luchardet -lpoppler-qt5 -lquazip5
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../shared
|
INCLUDEPATH += $$PWD/../shared
|
||||||
DEPENDPATH += $$PWD/../shared
|
DEPENDPATH += $$PWD/../shared
|
||||||
|
|
||||||
|
@ -3,3 +3,12 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "looqsgeneralexception.h"
|
#include "looqsgeneralexception.h"
|
||||||
|
|
||||||
|
void Command::execute()
|
||||||
|
{
|
||||||
|
int result = handle(arguments);
|
||||||
|
if(autoFinish)
|
||||||
|
{
|
||||||
|
emit finishedCmd(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,26 +1,39 @@
|
|||||||
#ifndef COMMAND_H
|
#ifndef COMMAND_H
|
||||||
#define COMMAND_H
|
#define COMMAND_H
|
||||||
#include <QStringList>
|
#include <QStringList>
|
||||||
#include <QSqlDatabase>
|
|
||||||
#include <QSqlQuery>
|
|
||||||
#include <QThreadStorage>
|
#include <QThreadStorage>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QWaitCondition>
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "sqlitedbservice.h"
|
#include "sqlitedbservice.h"
|
||||||
class Command
|
class Command : public QObject
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
signals:
|
||||||
|
void finishedCmd(int retval);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
SqliteDbService *dbService;
|
SqliteDbService *dbService;
|
||||||
QString dbConnectionString;
|
QString dbConnectionString;
|
||||||
|
QStringList arguments;
|
||||||
|
|
||||||
|
bool autoFinish = true;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Command(SqliteDbService &dbService)
|
Command(SqliteDbService &dbService)
|
||||||
{
|
{
|
||||||
this->dbService = &dbService;
|
this->dbService = &dbService;
|
||||||
}
|
}
|
||||||
|
void setArguments(QStringList arguments)
|
||||||
|
{
|
||||||
|
this->arguments = arguments;
|
||||||
|
}
|
||||||
virtual int handle(QStringList arguments) = 0;
|
virtual int handle(QStringList arguments) = 0;
|
||||||
virtual ~Command(){};
|
virtual ~Command(){};
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void execute();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COMMAND_H
|
#endif // COMMAND_H
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QSqlQuery>
|
|
||||||
#include <QSqlError>
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QTextStream>
|
#include <QTextStream>
|
||||||
@ -13,6 +11,28 @@
|
|||||||
#include "commandadd.h"
|
#include "commandadd.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
|
void CommandAdd::indexerFinished()
|
||||||
|
{
|
||||||
|
IndexResult result = indexer->getResult();
|
||||||
|
|
||||||
|
Logger::info() << "Total: " << result.total() << Qt::endl;
|
||||||
|
Logger::info() << "Added: " << result.addedPaths << Qt::endl;
|
||||||
|
Logger::info() << "Skipped: " << result.skippedPaths << Qt::endl;
|
||||||
|
auto failedPathsCount = result.erroredPaths;
|
||||||
|
Logger::info() << "Failed: " << failedPathsCount << Qt::endl;
|
||||||
|
if(failedPathsCount > 0)
|
||||||
|
{
|
||||||
|
Logger::info() << "Failed paths: " << Qt::endl;
|
||||||
|
for(QString paths : result.failedPaths())
|
||||||
|
{
|
||||||
|
Logger::info() << paths << Qt::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TODO maybe not 0 if keepGoing not given */
|
||||||
|
emit finishedCmd(0);
|
||||||
|
}
|
||||||
|
|
||||||
int CommandAdd::handle(QStringList arguments)
|
int CommandAdd::handle(QStringList arguments)
|
||||||
{
|
{
|
||||||
QCommandLineParser parser;
|
QCommandLineParser parser;
|
||||||
@ -53,15 +73,21 @@ int CommandAdd::handle(QStringList arguments)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSaver saver(*this->dbService);
|
indexer = new Indexer(*this->dbService);
|
||||||
int numFilesCount = files.size();
|
indexer->setTargetPaths(files.toVector());
|
||||||
int processedFilesCount = saver.addFiles(files.toVector(), keepGoing, verbose);
|
|
||||||
if(processedFilesCount != numFilesCount)
|
connect(indexer, &Indexer::pathsCountChanged, this,
|
||||||
{
|
[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; });
|
||||||
Logger::error() << "Errors occured while trying to add files to the database. Processed " << processedFilesCount
|
|
||||||
<< "out of" << numFilesCount << "files" << Qt::endl;
|
connect(indexer, &Indexer::indexProgress, this,
|
||||||
return 1;
|
[](int pathsCount, unsigned int added, unsigned int skipped, unsigned int failed, unsigned int totalCount)
|
||||||
}
|
{ Logger::info() << "Processed files: " << pathsCount << Qt::endl; });
|
||||||
|
connect(indexer, &Indexer::finished, this, &CommandAdd::indexerFinished);
|
||||||
|
|
||||||
|
/* TODO: keepGoing, verbose */
|
||||||
|
|
||||||
|
this->autoFinish = false;
|
||||||
|
indexer->beginIndexing();
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -3,15 +3,21 @@
|
|||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
#include "command.h"
|
#include "command.h"
|
||||||
#include "filesaver.h"
|
#include "filesaver.h"
|
||||||
|
#include "indexer.h"
|
||||||
|
|
||||||
class CommandAdd : public Command
|
class CommandAdd : public Command
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
SaveFileResult addFile(QString path);
|
SaveFileResult addFile(QString path);
|
||||||
|
Indexer *indexer;
|
||||||
|
|
||||||
|
protected:
|
||||||
public:
|
public:
|
||||||
using Command::Command;
|
using Command::Command;
|
||||||
|
|
||||||
int handle(QStringList arguments) override;
|
int handle(QStringList arguments) override;
|
||||||
|
private slots:
|
||||||
|
void indexerFinished();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // COMMANDADD_H
|
#endif // COMMANDADD_H
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <QSqlError>
|
|
||||||
#include "commanddelete.h"
|
#include "commanddelete.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ int CommandList::handle(QStringList arguments)
|
|||||||
|
|
||||||
QStringList files = parser.positionalArguments();
|
QStringList files = parser.positionalArguments();
|
||||||
QString queryStrings = files.join(' ');
|
QString queryStrings = files.join(' ');
|
||||||
auto results = dbService->search(LooqsQuery::build(queryStrings));
|
auto results = dbService->search(LooqsQuery::build(queryStrings, TokenType::FILTER_PATH_CONTAINS, false));
|
||||||
|
|
||||||
for(SearchResult &result : results)
|
for(SearchResult &result : results)
|
||||||
{
|
{
|
||||||
|
@ -16,7 +16,7 @@ int CommandSearch::handle(QStringList arguments)
|
|||||||
|
|
||||||
QStringList files = parser.positionalArguments();
|
QStringList files = parser.positionalArguments();
|
||||||
QString queryStrings = files.join(' ');
|
QString queryStrings = files.join(' ');
|
||||||
LooqsQuery query = LooqsQuery::build(queryStrings);
|
LooqsQuery query = LooqsQuery::build(queryStrings, TokenType::FILTER_PATH_CONTAINS, false);
|
||||||
bool reverse = parser.isSet("reverse");
|
bool reverse = parser.isSet("reverse");
|
||||||
if(reverse)
|
if(reverse)
|
||||||
{
|
{
|
||||||
|
@ -2,7 +2,6 @@
|
|||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QThreadPool>
|
#include <QThreadPool>
|
||||||
#include <QtConcurrentRun>
|
|
||||||
#include "commandupdate.h"
|
#include "commandupdate.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
|
||||||
|
35
cli/main.cpp
35
cli/main.cpp
@ -5,13 +5,12 @@
|
|||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QProcessEnvironment>
|
#include <QProcessEnvironment>
|
||||||
#include <QSqlDatabase>
|
|
||||||
#include <QSqlQuery>
|
|
||||||
#include <QSqlError>
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include "encodingdetector.h"
|
#include "encodingdetector.h"
|
||||||
#include "pdfprocessor.h"
|
#include "pdfprocessor.h"
|
||||||
@ -24,7 +23,9 @@
|
|||||||
#include "commandsearch.h"
|
#include "commandsearch.h"
|
||||||
#include "databasefactory.h"
|
#include "databasefactory.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "sandboxedprocessor.h"
|
||||||
#include "../shared/common.h"
|
#include "../shared/common.h"
|
||||||
|
#include "../shared/filescanworker.h"
|
||||||
|
|
||||||
void printUsage(QString argv0)
|
void printUsage(QString argv0)
|
||||||
{
|
{
|
||||||
@ -59,6 +60,7 @@ int main(int argc, char *argv[])
|
|||||||
QCoreApplication app(argc, argv);
|
QCoreApplication app(argc, argv);
|
||||||
QStringList args = app.arguments();
|
QStringList args = app.arguments();
|
||||||
QString argv0 = args.takeFirst();
|
QString argv0 = args.takeFirst();
|
||||||
|
|
||||||
if(args.length() < 1)
|
if(args.length() < 1)
|
||||||
{
|
{
|
||||||
printUsage(argv0);
|
printUsage(argv0);
|
||||||
@ -74,26 +76,45 @@ int main(int argc, char *argv[])
|
|||||||
Logger::error() << "Error: " << e.message;
|
Logger::error() << "Error: " << e.message;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
qRegisterMetaType<PageData>();
|
||||||
|
qRegisterMetaType<FileScanResult>("FileScanResult");
|
||||||
|
|
||||||
QString connectionString = Common::databasePath();
|
QString connectionString = Common::databasePath();
|
||||||
DatabaseFactory dbFactory(connectionString);
|
DatabaseFactory dbFactory(connectionString);
|
||||||
SqliteDbService dbService(dbFactory);
|
SqliteDbService dbService(dbFactory);
|
||||||
QString commandName = args.first();
|
QString commandName = args.first();
|
||||||
|
if(commandName == "process")
|
||||||
|
{
|
||||||
|
if(args.length() < 1)
|
||||||
|
{
|
||||||
|
qDebug() << "Filename is required";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString file = args.at(1);
|
||||||
|
SandboxedProcessor processor(file);
|
||||||
|
return processor.process();
|
||||||
|
}
|
||||||
Command *cmd = commandFromName(commandName, dbService);
|
Command *cmd = commandFromName(commandName, dbService);
|
||||||
if(cmd != nullptr)
|
if(cmd != nullptr)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return cmd->handle(args);
|
QObject::connect(cmd, &Command::finishedCmd, [](int retval) { QCoreApplication::exit(retval); });
|
||||||
|
cmd->setArguments(args);
|
||||||
|
QTimer::singleShot(0, cmd, &Command::execute);
|
||||||
}
|
}
|
||||||
catch(const LooqsGeneralException &e)
|
catch(const LooqsGeneralException &e)
|
||||||
{
|
{
|
||||||
Logger::error() << "Exception caught, message: " << e.message << Qt::endl;
|
Logger::error() << "Exception caught, message:" << e.message << Qt::endl;
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger::error() << "Unknown command " << commandName << Qt::endl;
|
Logger::error() << "Unknown command:" << commandName << Qt::endl;
|
||||||
}
|
|
||||||
return 1;
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
36
gui/gui.pro
36
gui/gui.pro
@ -4,7 +4,7 @@
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------
|
#-------------------------------------------------
|
||||||
|
|
||||||
QT += core concurrent gui
|
QT += core concurrent gui network
|
||||||
|
|
||||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||||
CONFIG += c++17
|
CONFIG += c++17
|
||||||
@ -23,29 +23,49 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
SOURCES += \
|
SOURCES += \
|
||||||
|
ipcclient.cpp \
|
||||||
|
ipcserver.cpp \
|
||||||
main.cpp \
|
main.cpp \
|
||||||
mainwindow.cpp \
|
mainwindow.cpp \
|
||||||
pdfworker.cpp \
|
clicklabel.cpp \
|
||||||
pdfpreview.cpp \
|
previewgenerator.cpp \
|
||||||
clicklabel.cpp
|
previewgeneratormapfunctor.cpp \
|
||||||
|
previewgeneratorpdf.cpp \
|
||||||
|
previewgeneratorplaintext.cpp \
|
||||||
|
previewresult.cpp \
|
||||||
|
previewresultpdf.cpp \
|
||||||
|
previewresultplaintext.cpp \
|
||||||
|
previewworker.cpp
|
||||||
|
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
|
ipc.h \
|
||||||
|
ipcclient.h \
|
||||||
|
ipcserver.h \
|
||||||
mainwindow.h \
|
mainwindow.h \
|
||||||
pdfworker.h \
|
clicklabel.h \
|
||||||
pdfpreview.h \
|
previewgenerator.h \
|
||||||
clicklabel.h
|
previewgeneratormapfunctor.h \
|
||||||
|
previewgeneratorpdf.h \
|
||||||
|
previewgeneratorplaintext.h \
|
||||||
|
previewresult.h \
|
||||||
|
previewresultpdf.h \
|
||||||
|
previewresultplaintext.h \
|
||||||
|
previewworker.h \
|
||||||
|
renderconfig.h
|
||||||
|
|
||||||
FORMS += \
|
FORMS += \
|
||||||
mainwindow.ui
|
mainwindow.ui
|
||||||
|
|
||||||
INCLUDEPATH += /usr/include/poppler/qt5/
|
INCLUDEPATH += /usr/include/poppler/qt5/
|
||||||
LIBS += -lpoppler-qt5
|
|
||||||
QT += widgets sql
|
QT += widgets sql
|
||||||
|
|
||||||
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../shared/release/ -lshared
|
win32:CONFIG(release, debug|release): LIBS += -L$$OUT_PWD/../shared/release/ -lshared
|
||||||
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../shared/debug/ -lshared
|
else:win32:CONFIG(debug, debug|release): LIBS += -L$$OUT_PWD/../shared/debug/ -lshared
|
||||||
else:unix: LIBS += -L$$OUT_PWD/../shared/ -lshared
|
else:unix: LIBS += -L$$OUT_PWD/../shared/ -lshared
|
||||||
|
|
||||||
|
LIBS += -luchardet -lpoppler-qt5 -lquazip5
|
||||||
|
|
||||||
INCLUDEPATH += $$PWD/../shared
|
INCLUDEPATH += $$PWD/../shared
|
||||||
DEPENDPATH += $$PWD/../shared
|
DEPENDPATH += $$PWD/../shared
|
||||||
|
|
||||||
|
10
gui/ipc.h
Normal file
10
gui/ipc.h
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#ifndef IPC_H
|
||||||
|
#define IPC_H
|
||||||
|
|
||||||
|
enum IPCCommand
|
||||||
|
{
|
||||||
|
DocOpen,
|
||||||
|
FileOpen,
|
||||||
|
AddFile,
|
||||||
|
};
|
||||||
|
#endif // IPC_H
|
27
gui/ipcclient.cpp
Normal file
27
gui/ipcclient.cpp
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#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;
|
||||||
|
}
|
18
gui/ipcclient.h
Normal file
18
gui/ipcclient.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#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
|
108
gui/ipcserver.cpp
Normal file
108
gui/ipcserver.cpp
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#include <QFile>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QSettings>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QLocalSocket>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include "ipcserver.h"
|
||||||
|
#include "common.h"
|
||||||
|
#include "databasefactory.h"
|
||||||
|
#include "../shared/logger.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()));
|
||||||
|
connect(&this->spawningServer, &QLocalServer::newConnection, this, &IpcServer::spawnerNewConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IpcServer::startSpawner(QString socketPath)
|
||||||
|
{
|
||||||
|
QFile::remove(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())
|
||||||
|
{
|
||||||
|
if(!socket->waitForReadyRead())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QDataStream stream(socket.get());
|
||||||
|
IPCCommand command;
|
||||||
|
QStringList args;
|
||||||
|
stream >> command;
|
||||||
|
stream >> args;
|
||||||
|
if(args.size() < 1)
|
||||||
|
{
|
||||||
|
stream << "invalid";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(command == DocOpen)
|
||||||
|
{
|
||||||
|
if(args.size() < 2)
|
||||||
|
{
|
||||||
|
stream << "invalid";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
docOpen(args[0], args[1].toInt());
|
||||||
|
}
|
||||||
|
if(command == FileOpen)
|
||||||
|
{
|
||||||
|
if(args.size() < 1)
|
||||||
|
{
|
||||||
|
stream << "invalid";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fileOpen(args[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
26
gui/ipcserver.h
Normal file
26
gui/ipcserver.h
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
#ifndef IPCSERVER_H
|
||||||
|
#define IPCSERVER_H
|
||||||
|
#include <QString>
|
||||||
|
#include <QLocalServer>
|
||||||
|
#include "ipc.h"
|
||||||
|
#include "filesaver.h"
|
||||||
|
class IpcServer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private:
|
||||||
|
QSharedPointer<DatabaseFactory> dbFactory;
|
||||||
|
QSharedPointer<SqliteDbService> dbService;
|
||||||
|
QSharedPointer<FileSaver> fileSaver;
|
||||||
|
QLocalServer spawningServer;
|
||||||
|
bool docOpen(QString path, int pagenum);
|
||||||
|
bool fileOpen(QString path);
|
||||||
|
SaveFileResult addFile(QString file);
|
||||||
|
private slots:
|
||||||
|
void spawnerNewConnection();
|
||||||
|
|
||||||
|
public:
|
||||||
|
IpcServer();
|
||||||
|
bool startSpawner(QString socketPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // IPCSERVER_H
|
149
gui/main.cpp
149
gui/main.cpp
@ -1,18 +1,153 @@
|
|||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
#include <QMessageBox>
|
#include <QMessageBox>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QCommandLineParser>
|
||||||
|
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "searchresult.h"
|
#include "searchresult.h"
|
||||||
#include "pdfpreview.h"
|
#include "previewresultpdf.h"
|
||||||
#include "../shared/common.h"
|
#include "../shared/common.h"
|
||||||
|
#include "../shared/sandboxedprocessor.h"
|
||||||
|
#include "../submodules/exile.h/exile.h"
|
||||||
|
#include "ipcserver.h"
|
||||||
|
|
||||||
|
void enableSandbox(QString socketPath)
|
||||||
|
{
|
||||||
|
struct exile_policy *policy = exile_init_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);
|
||||||
|
}
|
||||||
|
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[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
QString socketPath = "/tmp/looqs-spawner";
|
||||||
|
if(argc > 1)
|
||||||
|
{
|
||||||
|
QString arg = argv[1];
|
||||||
|
if(arg == "ipc")
|
||||||
|
{
|
||||||
Common::setupAppInfo();
|
Common::setupAppInfo();
|
||||||
QApplication a(argc, argv);
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
IpcServer *ipcserver = new IpcServer();
|
||||||
|
qDebug() << "Launching IPC Server";
|
||||||
|
if(!ipcserver->startSpawner(socketPath))
|
||||||
|
{
|
||||||
|
qCritical() << "Error failed to spawn";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
qDebug() << "Launched IPC Server";
|
||||||
|
return a.exec();
|
||||||
|
}
|
||||||
|
if(arg == "process")
|
||||||
|
{
|
||||||
|
Common::setupAppInfo();
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
|
QStringList args = a.arguments();
|
||||||
|
if(args.length() < 1)
|
||||||
|
{
|
||||||
|
qDebug() << "Filename is required";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString file = args.at(1);
|
||||||
|
SandboxedProcessor processor(file);
|
||||||
|
return processor.process();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QProcess process;
|
||||||
|
QStringList args;
|
||||||
|
args << "ipc";
|
||||||
|
if(!process.startDetached("/proc/self/exe", args))
|
||||||
|
{
|
||||||
|
QString errorMsg = "Failed to start IPC server";
|
||||||
|
qDebug() << errorMsg;
|
||||||
|
QMessageBox::critical(nullptr, "Error", errorMsg);
|
||||||
|
}
|
||||||
|
Common::setupAppInfo();
|
||||||
|
QCommandLineParser parser;
|
||||||
|
parser.addOption({{"s", "no-sandbox"}, "Disable sandboxing"});
|
||||||
|
QStringList appArgs;
|
||||||
|
for(int i = 0; i < argc; i++)
|
||||||
|
{
|
||||||
|
appArgs.append(argv[i]);
|
||||||
|
}
|
||||||
|
parser.parse(appArgs);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Common::ensureConfigured();
|
Common::ensureConfigured();
|
||||||
|
if(!parser.isSet("no-sandbox"))
|
||||||
|
{
|
||||||
|
enableSandbox(socketPath);
|
||||||
|
qInfo() << "Sandbox: on";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
qInfo() << "Sandbox: off";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch(LooqsGeneralException &e)
|
catch(LooqsGeneralException &e)
|
||||||
{
|
{
|
||||||
@ -20,10 +155,16 @@ int main(int argc, char *argv[])
|
|||||||
QMessageBox::critical(nullptr, "Error", e.message);
|
QMessageBox::critical(nullptr, "Error", e.message);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
// Keep this post sandbox, afterwards does not work (suspect due to threads, but unconfirmed)
|
||||||
|
QApplication a(argc, argv);
|
||||||
|
|
||||||
qRegisterMetaType<QVector<SearchResult>>("QVector<SearchResult>");
|
qRegisterMetaType<QVector<SearchResult>>("QVector<SearchResult>");
|
||||||
qRegisterMetaType<QVector<PdfPreview>>("QVector<PdfPreview>");
|
qRegisterMetaType<QVector<PreviewResultPdf>>("QVector<PreviewResultPdf>");
|
||||||
qRegisterMetaType<PdfPreview>("PdfPreview");
|
qRegisterMetaType<PreviewResultPdf>("PreviewResultPdf");
|
||||||
MainWindow w;
|
qRegisterMetaType<FileScanResult>("FileScanResult");
|
||||||
|
|
||||||
|
IPCClient client{socketPath};
|
||||||
|
MainWindow w{0, client};
|
||||||
w.showMaximized();
|
w.showMaximized();
|
||||||
|
|
||||||
return a.exec();
|
return a.exec();
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
#include <QProcess>
|
#include <QProcess>
|
||||||
#include <QComboBox>
|
#include <QComboBox>
|
||||||
#include <QtConcurrent/QtConcurrent>
|
#include <QtConcurrent/QtConcurrent>
|
||||||
|
#include <QMessageBox>
|
||||||
#include "mainwindow.h"
|
#include "mainwindow.h"
|
||||||
#include "ui_mainwindow.h"
|
#include "ui_mainwindow.h"
|
||||||
#include "clicklabel.h"
|
#include "clicklabel.h"
|
||||||
@ -18,30 +19,51 @@
|
|||||||
#include "../shared/looqsgeneralexception.h"
|
#include "../shared/looqsgeneralexception.h"
|
||||||
#include "../shared/common.h"
|
#include "../shared/common.h"
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow)
|
MainWindow::MainWindow(QWidget *parent, IPCClient &client) : QMainWindow(parent), ui(new Ui::MainWindow)
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
setWindowTitle(QCoreApplication::applicationName());
|
setWindowTitle(QCoreApplication::applicationName());
|
||||||
|
this->ipcClient = &client;
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
|
|
||||||
db = QSqlDatabase::addDatabase("QSQLITE");
|
this->dbFactory = new DatabaseFactory(Common::databasePath());
|
||||||
db.setDatabaseName(Common::databasePath());
|
|
||||||
if(!db.open())
|
db = this->dbFactory->forCurrentThread();
|
||||||
{
|
this->dbService = new SqliteDbService(*this->dbFactory);
|
||||||
qDebug() << "failed to open database";
|
|
||||||
throw std::runtime_error("Failed to open database");
|
indexer = new Indexer(*(this->dbService));
|
||||||
}
|
indexer->setParent(this);
|
||||||
connectSignals();
|
connectSignals();
|
||||||
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||||
ui->tabWidget->setCurrentIndex(0);
|
ui->tabWidget->setCurrentIndex(0);
|
||||||
ui->statusBar->addWidget(ui->lblSearchResults);
|
ui->statusBar->addWidget(ui->lblSearchResults);
|
||||||
ui->statusBar->addWidget(ui->pdfProcessBar);
|
ui->statusBar->addWidget(ui->previewProcessBar);
|
||||||
ui->pdfProcessBar->hide();
|
ui->previewProcessBar->hide();
|
||||||
ui->comboScale->setCurrentText(settings.value("currentScale").toString());
|
ui->comboScale->setCurrentText(settings.value("currentScale").toString());
|
||||||
pdfPreviewsPerPage = settings.value("pdfPreviewsPerPage", 20).toInt();
|
previewsPerPage = settings.value("previewsPerPage", 20).toInt();
|
||||||
ui->spinPdfPreviewPage->setMinimum(1);
|
ui->spinPreviewPage->setMinimum(1);
|
||||||
|
|
||||||
|
QStringList indexPaths = settings.value("indexPaths").toStringList();
|
||||||
|
ui->lstPaths->addItems(indexPaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::addPathToIndex()
|
||||||
|
{
|
||||||
|
QString path = this->ui->txtPathScanAdd->text();
|
||||||
|
QFileInfo fileInfo{path};
|
||||||
|
if(!fileInfo.exists(path))
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, "Invalid path", "Path does not seem to exist");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!fileInfo.isReadable())
|
||||||
|
{
|
||||||
|
QMessageBox::critical(this, "Invalid path", "Path cannot be read");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->ui->lstPaths->addItem(path);
|
||||||
|
this->ui->txtPathScanAdd->clear();
|
||||||
|
}
|
||||||
void MainWindow::connectSignals()
|
void MainWindow::connectSignals()
|
||||||
{
|
{
|
||||||
connect(ui->txtSearch, &QLineEdit::returnPressed, this, &MainWindow::lineEditReturnPressed);
|
connect(ui->txtSearch, &QLineEdit::returnPressed, this, &MainWindow::lineEditReturnPressed);
|
||||||
@ -59,36 +81,127 @@ void MainWindow::connectSignals()
|
|||||||
handleSearchError(e.message);
|
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(&pdfWorkerWatcher, &QFutureWatcher<PdfPreview>::resultReadyAt, this,
|
|
||||||
[&](int index) { pdfPreviewReceived(pdfWorkerWatcher.resultAt(index)); });
|
|
||||||
connect(&pdfWorkerWatcher, &QFutureWatcher<PdfPreview>::progressValueChanged, ui->pdfProcessBar,
|
|
||||||
&QProgressBar::setValue);
|
|
||||||
connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
|
connect(ui->treeResultsList, &QTreeWidget::itemActivated, this, &MainWindow::treeSearchItemActivated);
|
||||||
connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this,
|
connect(ui->treeResultsList, &QTreeWidget::customContextMenuRequested, this,
|
||||||
&MainWindow::showSearchResultsContextMenu);
|
&MainWindow::showSearchResultsContextMenu);
|
||||||
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
|
connect(ui->tabWidget, &QTabWidget::currentChanged, this, &MainWindow::tabChanged);
|
||||||
connect(ui->comboScale, qOverload<int>(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged);
|
connect(ui->comboScale, qOverload<int>(&QComboBox::currentIndexChanged), this, &MainWindow::comboScaleChanged);
|
||||||
connect(ui->spinPdfPreviewPage, qOverload<int>(&QSpinBox::valueChanged), this,
|
connect(ui->spinPreviewPage, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||||
&MainWindow::spinPdfPreviewPageValueChanged);
|
&MainWindow::spinPreviewPageValueChanged);
|
||||||
|
|
||||||
|
connect(ui->btnAddPath, &QPushButton::clicked, this, &MainWindow::addPathToIndex);
|
||||||
|
connect(ui->txtPathScanAdd, &QLineEdit::returnPressed, this, &MainWindow::addPathToIndex);
|
||||||
|
connect(ui->btnStartIndexing, &QPushButton::clicked, this, &MainWindow::startIndexing);
|
||||||
|
|
||||||
|
connect(this->indexer, &Indexer::pathsCountChanged, this,
|
||||||
|
[&](int number)
|
||||||
|
{
|
||||||
|
ui->lblSearchResults->setText("Found paths: " + QString::number(number));
|
||||||
|
ui->lblPathsFoundValue->setText(QString::number(number));
|
||||||
|
ui->previewProcessBar->setMaximum(number);
|
||||||
|
});
|
||||||
|
connect(this->indexer, &Indexer::indexProgress, this,
|
||||||
|
|
||||||
|
[&](int number, unsigned int added, unsigned int skipped, unsigned int failed, unsigned int totalCount)
|
||||||
|
{
|
||||||
|
ui->lblSearchResults->setText("Processed " + QString::number(number) + " files");
|
||||||
|
ui->previewProcessBar->setValue(number);
|
||||||
|
ui->previewProcessBar->setMaximum(totalCount);
|
||||||
|
ui->lblAddedValue->setText(QString::number(added));
|
||||||
|
ui->lblSkippedValue->setText(QString::number(skipped));
|
||||||
|
ui->lblFailedValue->setText(QString::number(failed));
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(this->indexer, &Indexer::finished, this, &MainWindow::finishIndexing);
|
||||||
|
|
||||||
|
connect(ui->lstPaths->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||||
|
[&](const QItemSelection &selected, const QItemSelection &deselected)
|
||||||
|
{ ui->btnDeletePath->setEnabled(this->ui->lstPaths->selectedItems().count() > 0); });
|
||||||
|
|
||||||
|
connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::spinPdfPreviewPageValueChanged(int val)
|
void MainWindow::spinPreviewPageValueChanged(int val)
|
||||||
{
|
{
|
||||||
makePdfPreview(val);
|
makePreviews(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::startIndexing()
|
||||||
|
{
|
||||||
|
if(this->indexer->isRunning())
|
||||||
|
{
|
||||||
|
ui->btnStartIndexing->setEnabled(false);
|
||||||
|
ui->btnStartIndexing->setText("Start indexing");
|
||||||
|
this->indexer->requestCancellation();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->previewsTab->setEnabled(false);
|
||||||
|
ui->resultsTab->setEnabled(false);
|
||||||
|
ui->txtPathScanAdd->setEnabled(false);
|
||||||
|
ui->txtSearch->setEnabled(false);
|
||||||
|
ui->previewProcessBar->setValue(0);
|
||||||
|
ui->previewProcessBar->setVisible(true);
|
||||||
|
|
||||||
|
QVector<QString> paths;
|
||||||
|
QStringList pathSettingsValue;
|
||||||
|
for(int i = 0; i < ui->lstPaths->count(); i++)
|
||||||
|
{
|
||||||
|
QString path = ui->lstPaths->item(i)->text();
|
||||||
|
paths.append(path);
|
||||||
|
pathSettingsValue.append(path);
|
||||||
|
}
|
||||||
|
this->indexer->setTargetPaths(paths);
|
||||||
|
this->indexer->beginIndexing();
|
||||||
|
QSettings settings;
|
||||||
|
settings.setValue("indexPaths", pathSettingsValue);
|
||||||
|
ui->btnStartIndexing->setText("Stop indexing");
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::finishIndexing()
|
||||||
|
{
|
||||||
|
IndexResult result = this->indexer->getResult();
|
||||||
|
|
||||||
|
ui->lblSearchResults->setText("Indexing finished");
|
||||||
|
ui->previewProcessBar->setValue(ui->previewProcessBar->maximum());
|
||||||
|
ui->lblFailedValue->setText(QString::number(result.erroredPaths));
|
||||||
|
ui->lblSkippedValue->setText(QString::number(result.skippedPaths));
|
||||||
|
ui->lblAddedValue->setText(QString::number(result.addedPaths));
|
||||||
|
ui->btnStartIndexing->setEnabled(true);
|
||||||
|
ui->btnStartIndexing->setText("Start indexing");
|
||||||
|
ui->previewsTab->setEnabled(true);
|
||||||
|
ui->resultsTab->setEnabled(true);
|
||||||
|
ui->txtPathScanAdd->setEnabled(true);
|
||||||
|
ui->txtSearch->setEnabled(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::comboScaleChanged(int i)
|
void MainWindow::comboScaleChanged(int i)
|
||||||
{
|
{
|
||||||
QSettings scaleSetting;
|
QSettings scaleSetting;
|
||||||
scaleSetting.setValue("currentScale", ui->comboScale->currentText());
|
scaleSetting.setValue("currentScale", ui->comboScale->currentText());
|
||||||
makePdfPreview(ui->spinPdfPreviewPage->value());
|
makePreviews(ui->spinPreviewPage->value());
|
||||||
}
|
}
|
||||||
bool MainWindow::pdfTabActive()
|
|
||||||
|
bool MainWindow::previewTabActive()
|
||||||
{
|
{
|
||||||
return ui->tabWidget->currentIndex() == 1;
|
return ui->tabWidget->currentIndex() == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MainWindow::indexerTabActive()
|
||||||
|
{
|
||||||
|
return ui->tabWidget->currentIndex() == 2;
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::keyPressEvent(QKeyEvent *event)
|
void MainWindow::keyPressEvent(QKeyEvent *event)
|
||||||
{
|
{
|
||||||
bool quit =
|
bool quit =
|
||||||
@ -112,53 +225,36 @@ void MainWindow::keyPressEvent(QKeyEvent *event)
|
|||||||
|
|
||||||
void MainWindow::tabChanged()
|
void MainWindow::tabChanged()
|
||||||
{
|
{
|
||||||
if(pdfTabActive())
|
if(ui->tabWidget->currentIndex() == 0)
|
||||||
{
|
{
|
||||||
if(pdfDirty)
|
ui->previewProcessBar->hide();
|
||||||
{
|
|
||||||
makePdfPreview(ui->spinPdfPreviewPage->value());
|
|
||||||
}
|
|
||||||
ui->pdfProcessBar->show();
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ui->pdfProcessBar->hide();
|
if(ui->previewProcessBar->value() > 0)
|
||||||
|
{
|
||||||
|
ui->previewProcessBar->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(previewTabActive())
|
||||||
|
{
|
||||||
|
if(previewDirty)
|
||||||
|
{
|
||||||
|
makePreviews(ui->spinPreviewPage->value());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::pdfPreviewReceived(PdfPreview preview)
|
void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview)
|
||||||
{
|
{
|
||||||
if(preview.hasPreviewImage())
|
if(preview->hasPreview())
|
||||||
{
|
{
|
||||||
ClickLabel *label = new ClickLabel();
|
QString docPath = preview->getDocumentPath();
|
||||||
QString docPath = preview.documentPath;
|
auto previewPage = preview->getPage();
|
||||||
auto previewPage = preview.page;
|
|
||||||
label->setPixmap(QPixmap::fromImage(preview.previewImage));
|
|
||||||
label->setToolTip(preview.documentPath);
|
|
||||||
ui->scrollAreaWidgetContents->layout()->addWidget(label);
|
|
||||||
connect(label, &ClickLabel::leftClick,
|
|
||||||
[docPath, previewPage]()
|
|
||||||
{
|
|
||||||
QSettings settings;
|
|
||||||
QString command = settings.value("pdfviewer").toString();
|
|
||||||
if(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", docPath);
|
|
||||||
args.replaceInStrings("%p", QString::number(previewPage));
|
|
||||||
|
|
||||||
QProcess::startDetached(cmd, args);
|
ClickLabel *label = dynamic_cast<ClickLabel *>(preview->createPreviewWidget());
|
||||||
}
|
ui->scrollAreaWidgetContents->layout()->addWidget(label);
|
||||||
}
|
connect(label, &ClickLabel::leftClick, [this, docPath, previewPage]() { ipcDocOpen(docPath, previewPage); });
|
||||||
else
|
|
||||||
{
|
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(docPath));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
connect(label, &ClickLabel::rightClick,
|
connect(label, &ClickLabel::rightClick,
|
||||||
[this, docPath, previewPage]()
|
[this, docPath, previewPage]()
|
||||||
{
|
{
|
||||||
@ -181,21 +277,37 @@ void MainWindow::lineEditReturnPressed()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// TODO: validate q;
|
// TODO: validate q;
|
||||||
|
ui->treeResultsList->clear();
|
||||||
ui->lblSearchResults->setText("Searching...");
|
ui->lblSearchResults->setText("Searching...");
|
||||||
this->ui->txtSearch->setEnabled(false);
|
this->ui->txtSearch->setEnabled(false);
|
||||||
QFuture<QVector<SearchResult>> searchFuture = QtConcurrent::run(
|
QFuture<QVector<SearchResult>> searchFuture = QtConcurrent::run(
|
||||||
[&, q]()
|
[&, q]()
|
||||||
{
|
{
|
||||||
SqliteSearch searcher(db);
|
SqliteSearch searcher(db);
|
||||||
this->currentQuery = LooqsQuery::build(q);
|
QVector<SearchResult> results;
|
||||||
return searcher.search(this->currentQuery);
|
this->contentSearchQuery = LooqsQuery::build(q, TokenType::FILTER_CONTENT_CONTAINS, true);
|
||||||
|
|
||||||
|
/* We can have a path search in contentsearch too (if given explicitly), so no need to do it twice.
|
||||||
|
Make sure path results are listed first. */
|
||||||
|
bool addContentSearch = this->contentSearchQuery.hasContentSearch();
|
||||||
|
bool addPathSearch = !this->contentSearchQuery.hasPathSearch() || !addContentSearch;
|
||||||
|
if(addPathSearch)
|
||||||
|
{
|
||||||
|
LooqsQuery filesQuery = LooqsQuery::build(q, TokenType::FILTER_PATH_CONTAINS, false);
|
||||||
|
results.append(searcher.search(filesQuery));
|
||||||
|
}
|
||||||
|
if(addContentSearch)
|
||||||
|
{
|
||||||
|
results.append(searcher.search(this->contentSearchQuery));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
});
|
});
|
||||||
searchWatcher.setFuture(searchFuture);
|
searchWatcher.setFuture(searchFuture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
||||||
{
|
{
|
||||||
this->pdfSearchResults.clear();
|
this->previewableSearchResults.clear();
|
||||||
ui->treeResultsList->clear();
|
ui->treeResultsList->clear();
|
||||||
|
|
||||||
bool hasDeleted = false;
|
bool hasDeleted = false;
|
||||||
@ -215,9 +327,9 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
|||||||
bool exists = pathInfo.exists();
|
bool exists = pathInfo.exists();
|
||||||
if(exists)
|
if(exists)
|
||||||
{
|
{
|
||||||
if(result.fileData.absPath.endsWith(".pdf"))
|
if(PreviewGenerator::get(pathInfo) != nullptr)
|
||||||
{
|
{
|
||||||
this->pdfSearchResults.append(result);
|
this->previewableSearchResults.append(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -227,15 +339,15 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
|||||||
}
|
}
|
||||||
ui->treeResultsList->resizeColumnToContents(0);
|
ui->treeResultsList->resizeColumnToContents(0);
|
||||||
ui->treeResultsList->resizeColumnToContents(1);
|
ui->treeResultsList->resizeColumnToContents(1);
|
||||||
pdfDirty = !this->pdfSearchResults.empty();
|
previewDirty = !this->previewableSearchResults.empty();
|
||||||
|
|
||||||
int numpages = ceil(static_cast<double>(this->pdfSearchResults.size()) / pdfPreviewsPerPage);
|
int numpages = ceil(static_cast<double>(this->previewableSearchResults.size()) / previewsPerPage);
|
||||||
ui->spinPdfPreviewPage->setMinimum(1);
|
ui->spinPreviewPage->setMinimum(1);
|
||||||
ui->spinPdfPreviewPage->setMaximum(numpages);
|
ui->spinPreviewPage->setMaximum(numpages);
|
||||||
ui->spinPdfPreviewPage->setValue(1);
|
ui->spinPreviewPage->setValue(1);
|
||||||
if(pdfTabActive() && pdfDirty)
|
if(previewTabActive() && previewDirty)
|
||||||
{
|
{
|
||||||
makePdfPreview(1);
|
makePreviews(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString statusText = "Results: " + QString::number(results.size()) + " files";
|
QString statusText = "Results: " + QString::number(results.size()) + " files";
|
||||||
@ -246,25 +358,25 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
|||||||
ui->lblSearchResults->setText(statusText);
|
ui->lblSearchResults->setText(statusText);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::makePdfPreview(int page)
|
void MainWindow::makePreviews(int page)
|
||||||
{
|
{
|
||||||
|
|
||||||
this->pdfWorkerWatcher.cancel();
|
this->previewWorkerWatcher.cancel();
|
||||||
this->pdfWorkerWatcher.waitForFinished();
|
this->previewWorkerWatcher.waitForFinished();
|
||||||
|
|
||||||
QCoreApplication::processEvents(); // Maybe not necessary anymore, depends on whether it's possible that a slot is
|
QCoreApplication::processEvents(); // Maybe not necessary anymore, depends on whether it's possible that a slot is
|
||||||
// still to be fired.
|
// still to be fired.
|
||||||
qDeleteAll(ui->scrollAreaWidgetContents->children());
|
qDeleteAll(ui->scrollAreaWidgetContents->children());
|
||||||
|
|
||||||
ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
|
ui->scrollAreaWidgetContents->setLayout(new QHBoxLayout());
|
||||||
ui->pdfProcessBar->setMaximum(this->pdfSearchResults.size());
|
ui->previewProcessBar->setMaximum(this->previewableSearchResults.size());
|
||||||
processedPdfPreviews = 0;
|
processedPdfPreviews = 0;
|
||||||
QString scaleText = ui->comboScale->currentText();
|
QString scaleText = ui->comboScale->currentText();
|
||||||
scaleText.chop(1);
|
scaleText.chop(1);
|
||||||
|
|
||||||
QVector<QString> wordsToHighlight;
|
QVector<QString> wordsToHighlight;
|
||||||
QRegularExpression extractor(R"#("([^"]*)"|(\w+))#");
|
QRegularExpression extractor(R"#("([^"]*)"|(\w+))#");
|
||||||
for(const Token &token : this->currentQuery.getTokens())
|
for(const Token &token : this->contentSearchQuery.getTokens())
|
||||||
{
|
{
|
||||||
if(token.type == FILTER_CONTENT_CONTAINS)
|
if(token.type == FILTER_CONTENT_CONTAINS)
|
||||||
{
|
{
|
||||||
@ -281,13 +393,14 @@ void MainWindow::makePdfPreview(int page)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PdfWorker worker;
|
PreviewWorker worker;
|
||||||
int end = pdfPreviewsPerPage;
|
int end = previewsPerPage;
|
||||||
int begin = page * pdfPreviewsPerPage - pdfPreviewsPerPage;
|
int begin = page * previewsPerPage - previewsPerPage;
|
||||||
this->pdfWorkerWatcher.setFuture(
|
this->previewWorkerWatcher.setFuture(worker.generatePreviews(this->previewableSearchResults.mid(begin, end),
|
||||||
worker.generatePreviews(this->pdfSearchResults.mid(begin, end), wordsToHighlight, scaleText.toInt() / 100.));
|
wordsToHighlight, scaleText.toInt() / 100.));
|
||||||
ui->pdfProcessBar->setMaximum(this->pdfWorkerWatcher.progressMaximum());
|
ui->previewProcessBar->setMaximum(this->previewWorkerWatcher.progressMaximum());
|
||||||
ui->pdfProcessBar->setMinimum(this->pdfWorkerWatcher.progressMinimum());
|
ui->previewProcessBar->setMinimum(this->previewWorkerWatcher.progressMinimum());
|
||||||
|
ui->previewProcessBar->setVisible(this->previewableSearchResults.size() > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::handleSearchError(QString error)
|
void MainWindow::handleSearchError(QString error)
|
||||||
@ -301,17 +414,27 @@ void MainWindow::createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo)
|
|||||||
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); });
|
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); });
|
||||||
menu.addAction("Copy full path to clipboard",
|
menu.addAction("Copy full path to clipboard",
|
||||||
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); });
|
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); });
|
||||||
menu.addAction("Open containing folder",
|
menu.addAction("Open containing folder", [this, &fileInfo] { this->ipcFileOpen(fileInfo.absolutePath()); });
|
||||||
[&fileInfo]
|
}
|
||||||
{
|
|
||||||
QString dir = fileInfo.absolutePath();
|
void MainWindow::ipcDocOpen(QString path, int num)
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(dir));
|
{
|
||||||
});
|
QStringList args;
|
||||||
|
args << path;
|
||||||
|
args << QString::number(num);
|
||||||
|
this->ipcClient->sendCommand(DocOpen, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MainWindow::ipcFileOpen(QString path)
|
||||||
|
{
|
||||||
|
QStringList args;
|
||||||
|
args << path;
|
||||||
|
this->ipcClient->sendCommand(FileOpen, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i)
|
void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i)
|
||||||
{
|
{
|
||||||
QDesktopServices::openUrl(QUrl::fromLocalFile(item->text(1)));
|
ipcFileOpen(item->text(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::showSearchResultsContextMenu(const QPoint &point)
|
void MainWindow::showSearchResultsContextMenu(const QPoint &point)
|
||||||
|
@ -8,8 +8,11 @@
|
|||||||
#include <QKeyEvent>
|
#include <QKeyEvent>
|
||||||
#include <QFutureWatcher>
|
#include <QFutureWatcher>
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include "pdfworker.h"
|
#include <QLocalSocket>
|
||||||
|
#include "previewworker.h"
|
||||||
#include "../shared/looqsquery.h"
|
#include "../shared/looqsquery.h"
|
||||||
|
#include "ipcclient.h"
|
||||||
|
#include "indexer.h"
|
||||||
namespace Ui
|
namespace Ui
|
||||||
{
|
{
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
@ -20,39 +23,50 @@ class MainWindow : public QMainWindow
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit MainWindow(QWidget *parent = 0);
|
explicit MainWindow(QWidget *parent, IPCClient &client);
|
||||||
~MainWindow();
|
~MainWindow();
|
||||||
signals:
|
signals:
|
||||||
void beginSearch(const QString &query);
|
void beginSearch(const QString &query);
|
||||||
void startPdfPreviewGeneration(QVector<SearchResult> paths, double scalefactor);
|
void startPdfPreviewGeneration(QVector<SearchResult> paths, double scalefactor);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
DatabaseFactory *dbFactory;
|
||||||
|
SqliteDbService *dbService;
|
||||||
Ui::MainWindow *ui;
|
Ui::MainWindow *ui;
|
||||||
|
IPCClient *ipcClient;
|
||||||
|
Indexer *indexer;
|
||||||
QFileIconProvider iconProvider;
|
QFileIconProvider iconProvider;
|
||||||
bool pdfDirty;
|
bool previewDirty;
|
||||||
QSqlDatabase db;
|
QSqlDatabase db;
|
||||||
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
||||||
QFutureWatcher<PdfPreview> pdfWorkerWatcher;
|
QFutureWatcher<QSharedPointer<PreviewResult>> previewWorkerWatcher;
|
||||||
void add(QString path, unsigned int page);
|
void add(QString path, unsigned int page);
|
||||||
QVector<SearchResult> pdfSearchResults;
|
QVector<SearchResult> previewableSearchResults;
|
||||||
void connectSignals();
|
void connectSignals();
|
||||||
void makePdfPreview(int page);
|
void makePreviews(int page);
|
||||||
bool pdfTabActive();
|
bool previewTabActive();
|
||||||
|
bool indexerTabActive();
|
||||||
void keyPressEvent(QKeyEvent *event) override;
|
void keyPressEvent(QKeyEvent *event) override;
|
||||||
unsigned int processedPdfPreviews;
|
unsigned int processedPdfPreviews;
|
||||||
void handleSearchResults(const QVector<SearchResult> &results);
|
void handleSearchResults(const QVector<SearchResult> &results);
|
||||||
void handleSearchError(QString error);
|
void handleSearchError(QString error);
|
||||||
LooqsQuery currentQuery;
|
LooqsQuery contentSearchQuery;
|
||||||
int pdfPreviewsPerPage;
|
int previewsPerPage;
|
||||||
void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo);
|
void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo);
|
||||||
|
void ipcDocOpen(QString path, int num);
|
||||||
|
void ipcFileOpen(QString path);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void lineEditReturnPressed();
|
void lineEditReturnPressed();
|
||||||
void treeSearchItemActivated(QTreeWidgetItem *item, int i);
|
void treeSearchItemActivated(QTreeWidgetItem *item, int i);
|
||||||
void showSearchResultsContextMenu(const QPoint &point);
|
void showSearchResultsContextMenu(const QPoint &point);
|
||||||
void tabChanged();
|
void tabChanged();
|
||||||
void pdfPreviewReceived(PdfPreview preview);
|
void previewReceived(QSharedPointer<PreviewResult> preview);
|
||||||
void comboScaleChanged(int i);
|
void comboScaleChanged(int i);
|
||||||
void spinPdfPreviewPageValueChanged(int val);
|
void spinPreviewPageValueChanged(int val);
|
||||||
|
void startIndexing();
|
||||||
|
void finishIndexing();
|
||||||
|
void addPathToIndex();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // MAINWINDOW_H
|
#endif // MAINWINDOW_H
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1221</width>
|
<width>1221</width>
|
||||||
<height>614</height>
|
<height>674</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<enum>QTabWidget::South</enum>
|
<enum>QTabWidget::South</enum>
|
||||||
</property>
|
</property>
|
||||||
<property name="currentIndex">
|
<property name="currentIndex">
|
||||||
<number>1</number>
|
<number>2</number>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="resultsTab">
|
<widget class="QWidget" name="resultsTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
@ -60,9 +60,9 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QWidget" name="pdfPreviewTab">
|
<widget class="QWidget" name="previewsTab">
|
||||||
<attribute name="title">
|
<attribute name="title">
|
||||||
<string>PDF-Preview</string>
|
<string>Previews</string>
|
||||||
</attribute>
|
</attribute>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0">
|
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,0">
|
||||||
<item>
|
<item>
|
||||||
@ -81,8 +81,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1179</width>
|
<width>1185</width>
|
||||||
<height>370</height>
|
<height>419</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout"/>
|
<layout class="QHBoxLayout" name="horizontalLayout"/>
|
||||||
@ -143,7 +143,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QSpinBox" name="spinPdfPreviewPage">
|
<widget class="QSpinBox" name="spinPreviewPage">
|
||||||
<property name="buttonSymbols">
|
<property name="buttonSymbols">
|
||||||
<enum>QAbstractSpinBox::PlusMinus</enum>
|
<enum>QAbstractSpinBox::PlusMinus</enum>
|
||||||
</property>
|
</property>
|
||||||
@ -172,6 +172,171 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
|
<widget class="QWidget" name="indexerTab">
|
||||||
|
<attribute name="title">
|
||||||
|
<string>Index</string>
|
||||||
|
</attribute>
|
||||||
|
<layout class="QGridLayout" name="gridLayout">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBoxPaths">
|
||||||
|
<property name="title">
|
||||||
|
<string>Add paths to scan</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QGridLayout" name="gridLayout_2">
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLineEdit" name="txtPathScanAdd"/>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="0" colspan="5">
|
||||||
|
<widget class="QListWidget" name="lstPaths"/>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="3">
|
||||||
|
<widget class="QToolButton" name="btnDeletePath">
|
||||||
|
<property name="enabled">
|
||||||
|
<bool>false</bool>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Delete</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QPushButton" name="btnChoosePath">
|
||||||
|
<property name="text">
|
||||||
|
<string>...</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="2">
|
||||||
|
<widget class="QPushButton" name="btnAddPath">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="5" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Ignore patterns, separated by ';'. Example: *.js;*Downloads*</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="9" column="0">
|
||||||
|
<widget class="QGroupBox" name="groupBoxIndexProgress">
|
||||||
|
<property name="contextMenuPolicy">
|
||||||
|
<enum>Qt::PreventContextMenu</enum>
|
||||||
|
</property>
|
||||||
|
<property name="title">
|
||||||
|
<string>Index Progress</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_4">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_7">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblPathsFound">
|
||||||
|
<property name="text">
|
||||||
|
<string>Paths found:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblPathsFoundValue">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblAdded">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Added:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblAddedValue">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_5">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblSkipped">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Skipped:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblSkippedValue">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_6">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblFailed">
|
||||||
|
<property name="font">
|
||||||
|
<font>
|
||||||
|
<weight>50</weight>
|
||||||
|
<bold>false</bold>
|
||||||
|
</font>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Failed:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="lblFailedValue">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="6" column="0">
|
||||||
|
<widget class="QLineEdit" name="lineEdit"/>
|
||||||
|
</item>
|
||||||
|
<item row="10" column="0">
|
||||||
|
<widget class="QPushButton" name="btnStartIndexing">
|
||||||
|
<property name="text">
|
||||||
|
<string>Start indexing</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -182,7 +347,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QProgressBar" name="pdfProcessBar">
|
<widget class="QProgressBar" name="previewProcessBar">
|
||||||
<property name="value">
|
<property name="value">
|
||||||
<number>24</number>
|
<number>24</number>
|
||||||
</property>
|
</property>
|
||||||
@ -190,24 +355,6 @@
|
|||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenuBar" name="menuBar">
|
|
||||||
<property name="geometry">
|
|
||||||
<rect>
|
|
||||||
<x>0</x>
|
|
||||||
<y>0</y>
|
|
||||||
<width>1221</width>
|
|
||||||
<height>20</height>
|
|
||||||
</rect>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
<widget class="QToolBar" name="mainToolBar">
|
|
||||||
<attribute name="toolBarArea">
|
|
||||||
<enum>TopToolBarArea</enum>
|
|
||||||
</attribute>
|
|
||||||
<attribute name="toolBarBreak">
|
|
||||||
<bool>false</bool>
|
|
||||||
</attribute>
|
|
||||||
</widget>
|
|
||||||
<widget class="QStatusBar" name="statusBar"/>
|
<widget class="QStatusBar" name="statusBar"/>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
#include "pdfpreview.h"
|
|
||||||
|
|
||||||
PdfPreview::PdfPreview()
|
|
||||||
{
|
|
||||||
}
|
|
@ -1,19 +0,0 @@
|
|||||||
#ifndef PDFPREVIEW_H
|
|
||||||
#define PDFPREVIEW_H
|
|
||||||
#include <QImage>
|
|
||||||
|
|
||||||
class PdfPreview
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PdfPreview();
|
|
||||||
QImage previewImage;
|
|
||||||
QString documentPath;
|
|
||||||
unsigned int page;
|
|
||||||
|
|
||||||
bool hasPreviewImage()
|
|
||||||
{
|
|
||||||
return !previewImage.isNull();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PDFPREVIEW_H
|
|
@ -1,108 +0,0 @@
|
|||||||
|
|
||||||
#include <QApplication>
|
|
||||||
#include <QScreen>
|
|
||||||
#include <QDebug>
|
|
||||||
#include <QScopedPointer>
|
|
||||||
#include <QMutexLocker>
|
|
||||||
#include <QtConcurrent/QtConcurrent>
|
|
||||||
#include <QtConcurrent/QtConcurrentMap>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <atomic>
|
|
||||||
#include "pdfworker.h"
|
|
||||||
|
|
||||||
static QMutex cacheMutex;
|
|
||||||
struct Renderer
|
|
||||||
{
|
|
||||||
|
|
||||||
typedef PdfPreview result_type;
|
|
||||||
double scaleX;
|
|
||||||
double scaleY;
|
|
||||||
QHash<QString, Poppler::Document *> documentcache;
|
|
||||||
QVector<QString> wordsToHighlight;
|
|
||||||
Renderer(double scaleX, double scaleY, QVector<QString> wordsToHighlight)
|
|
||||||
{
|
|
||||||
this->scaleX = scaleX;
|
|
||||||
this->scaleY = scaleY;
|
|
||||||
this->wordsToHighlight = wordsToHighlight;
|
|
||||||
}
|
|
||||||
|
|
||||||
~Renderer()
|
|
||||||
{
|
|
||||||
qDeleteAll(documentcache);
|
|
||||||
}
|
|
||||||
|
|
||||||
Poppler::Document *document(QString path)
|
|
||||||
{
|
|
||||||
if(documentcache.contains(path))
|
|
||||||
{
|
|
||||||
return documentcache.value(path);
|
|
||||||
}
|
|
||||||
Poppler::Document *result = Poppler::Document::load(path);
|
|
||||||
if(result == nullptr)
|
|
||||||
{
|
|
||||||
// TODO: some kind of user feedback would be nice
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
result->setRenderHint(Poppler::Document::TextAntialiasing);
|
|
||||||
QMutexLocker locker(&cacheMutex);
|
|
||||||
documentcache.insert(path, result);
|
|
||||||
locker.unlock();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
PdfPreview operator()(const PdfPreview &preview)
|
|
||||||
{
|
|
||||||
PdfPreview result = preview;
|
|
||||||
Poppler::Document *doc = document(preview.documentPath);
|
|
||||||
if(doc == nullptr)
|
|
||||||
{
|
|
||||||
return preview;
|
|
||||||
}
|
|
||||||
if(doc->isLocked())
|
|
||||||
{
|
|
||||||
return preview;
|
|
||||||
}
|
|
||||||
int p = (int)preview.page - 1;
|
|
||||||
if(p < 0)
|
|
||||||
{
|
|
||||||
p = 0;
|
|
||||||
}
|
|
||||||
Poppler::Page *pdfPage = doc->page(p);
|
|
||||||
QImage img = pdfPage->renderToImage(scaleX, scaleY);
|
|
||||||
for(QString &word : wordsToHighlight)
|
|
||||||
{
|
|
||||||
QList<QRectF> rects = pdfPage->search(word, Poppler::Page::SearchFlag::IgnoreCase);
|
|
||||||
for(QRectF &rect : rects)
|
|
||||||
{
|
|
||||||
QPainter painter(&img);
|
|
||||||
painter.scale(scaleX / 72.0, scaleY / 72.0);
|
|
||||||
painter.fillRect(rect, QColor(255, 255, 0, 64));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.previewImage = img;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
QFuture<PdfPreview> PdfWorker::generatePreviews(const QVector<SearchResult> paths, QVector<QString> wordsToHighlight,
|
|
||||||
double scalefactor)
|
|
||||||
{
|
|
||||||
QVector<PdfPreview> previews;
|
|
||||||
|
|
||||||
for(const SearchResult &sr : paths)
|
|
||||||
{
|
|
||||||
for(int page : sr.pages)
|
|
||||||
{
|
|
||||||
PdfPreview p;
|
|
||||||
p.documentPath = sr.fileData.absPath;
|
|
||||||
p.page = page;
|
|
||||||
previews.append(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
double scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor;
|
|
||||||
double scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor;
|
|
||||||
|
|
||||||
QSettings setting;
|
|
||||||
return QtConcurrent::mapped(previews, Renderer(scaleX, scaleY, wordsToHighlight));
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
#ifndef PDFWORKER_H
|
|
||||||
#define PDFWORKER_H
|
|
||||||
#include <QObject>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QHash>
|
|
||||||
#include <QThread>
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QWaitCondition>
|
|
||||||
#include <QMutex>
|
|
||||||
#include <QFuture>
|
|
||||||
#include <poppler-qt5.h>
|
|
||||||
#include "pdfpreview.h"
|
|
||||||
#include "searchresult.h"
|
|
||||||
|
|
||||||
class PdfWorker : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
QFuture<PdfPreview> generatePreviews(const QVector<SearchResult> paths, QVector<QString> wordsToHighlight,
|
|
||||||
double scalefactor);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PDFWORKER_H
|
|
15
gui/previewgenerator.cpp
Normal file
15
gui/previewgenerator.cpp
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
#include "previewgenerator.h"
|
||||||
|
#include "previewgeneratorpdf.h"
|
||||||
|
#include "previewgeneratorplaintext.h"
|
||||||
|
|
||||||
|
static PreviewGenerator *plainTextGenerator = new PreviewGeneratorPlainText();
|
||||||
|
|
||||||
|
static QMap<QString, PreviewGenerator *> generators{
|
||||||
|
{"pdf", new PreviewGeneratorPdf()}, {"txt", plainTextGenerator}, {"md", plainTextGenerator},
|
||||||
|
{"py", plainTextGenerator}, {"java", plainTextGenerator}, {"js", plainTextGenerator},
|
||||||
|
{"cpp", plainTextGenerator}, {"c", plainTextGenerator}, {"sql", plainTextGenerator}};
|
||||||
|
|
||||||
|
PreviewGenerator *PreviewGenerator::get(QFileInfo &info)
|
||||||
|
{
|
||||||
|
return generators.value(info.suffix(), nullptr);
|
||||||
|
}
|
20
gui/previewgenerator.h
Normal file
20
gui/previewgenerator.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef PREVIEWGENERATOR_H
|
||||||
|
#define PREVIEWGENERATOR_H
|
||||||
|
#include <QVector>
|
||||||
|
#include <QSharedPointer>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include "previewresult.h"
|
||||||
|
#include "renderconfig.h"
|
||||||
|
|
||||||
|
class PreviewGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual PreviewResult *generate(RenderConfig config, QString documentPath, unsigned int page) = 0;
|
||||||
|
virtual ~PreviewGenerator()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static PreviewGenerator *get(QFileInfo &info);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWGENERATOR_H
|
25
gui/previewgeneratormapfunctor.cpp
Normal file
25
gui/previewgeneratormapfunctor.cpp
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#include "previewgeneratormapfunctor.h"
|
||||||
|
#include "previewgeneratorpdf.h"
|
||||||
|
|
||||||
|
PreviewGeneratorMapFunctor::PreviewGeneratorMapFunctor()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreviewGeneratorMapFunctor::setRenderConfig(RenderConfig config)
|
||||||
|
{
|
||||||
|
this->renderConfig = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSharedPointer<PreviewResult> PreviewGeneratorMapFunctor::operator()(const QSharedPointer<PreviewResult> &renderResult)
|
||||||
|
{
|
||||||
|
QFileInfo info{renderResult->getDocumentPath()};
|
||||||
|
PreviewGenerator *previewGenerator = PreviewGenerator::get(info);
|
||||||
|
if(previewGenerator == nullptr)
|
||||||
|
{
|
||||||
|
return QSharedPointer<PreviewResult>();
|
||||||
|
}
|
||||||
|
auto preview =
|
||||||
|
previewGenerator->generate(this->renderConfig, renderResult->getDocumentPath(), renderResult->getPage());
|
||||||
|
|
||||||
|
return QSharedPointer<PreviewResult>(preview);
|
||||||
|
}
|
28
gui/previewgeneratormapfunctor.h
Normal file
28
gui/previewgeneratormapfunctor.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
#ifndef PREVIEWGENERATORMAPFUNCTOR_H
|
||||||
|
#define PREVIEWGENERATORMAPFUNCTOR_H
|
||||||
|
|
||||||
|
#include "renderconfig.h"
|
||||||
|
#include "previewgenerator.h"
|
||||||
|
|
||||||
|
class PreviewGeneratorMapFunctor
|
||||||
|
{
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum GeneratorIndex
|
||||||
|
{
|
||||||
|
PDF = 0,
|
||||||
|
LAST_DUMMY
|
||||||
|
};
|
||||||
|
RenderConfig renderConfig;
|
||||||
|
|
||||||
|
public:
|
||||||
|
typedef QSharedPointer<PreviewResult> result_type;
|
||||||
|
|
||||||
|
PreviewGeneratorMapFunctor();
|
||||||
|
|
||||||
|
void setRenderConfig(RenderConfig config);
|
||||||
|
|
||||||
|
QSharedPointer<PreviewResult> operator()(const QSharedPointer<PreviewResult> &renderResult);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWGENERATORMAPFUNCTOR_H
|
59
gui/previewgeneratorpdf.cpp
Normal file
59
gui/previewgeneratorpdf.cpp
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#include <QMutexLocker>
|
||||||
|
#include <QPainter>
|
||||||
|
|
||||||
|
#include "previewgeneratorpdf.h"
|
||||||
|
|
||||||
|
static QMutex cacheMutex;
|
||||||
|
|
||||||
|
Poppler::Document *PreviewGeneratorPdf::document(QString path)
|
||||||
|
{
|
||||||
|
if(documentcache.contains(path))
|
||||||
|
{
|
||||||
|
return documentcache.value(path);
|
||||||
|
}
|
||||||
|
Poppler::Document *result = Poppler::Document::load(path);
|
||||||
|
if(result == nullptr)
|
||||||
|
{
|
||||||
|
// TODO: some kind of user feedback would be nice
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
result->setRenderHint(Poppler::Document::TextAntialiasing);
|
||||||
|
QMutexLocker locker(&cacheMutex);
|
||||||
|
documentcache.insert(path, result);
|
||||||
|
locker.unlock();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if(doc->isLocked())
|
||||||
|
{
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
int p = (int)page - 1;
|
||||||
|
if(p < 0)
|
||||||
|
{
|
||||||
|
p = 0;
|
||||||
|
}
|
||||||
|
Poppler::Page *pdfPage = doc->page(p);
|
||||||
|
QImage img = pdfPage->renderToImage(config.scaleX, config.scaleY);
|
||||||
|
for(QString &word : config.wordsToHighlight)
|
||||||
|
{
|
||||||
|
QList<QRectF> rects = pdfPage->search(word, Poppler::Page::SearchFlag::IgnoreCase);
|
||||||
|
for(QRectF &rect : rects)
|
||||||
|
{
|
||||||
|
QPainter painter(&img);
|
||||||
|
painter.scale(config.scaleX / 72.0, config.scaleY / 72.0);
|
||||||
|
painter.fillRect(rect, QColor(255, 255, 0, 64));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result->previewImage = img;
|
||||||
|
return result;
|
||||||
|
}
|
24
gui/previewgeneratorpdf.h
Normal file
24
gui/previewgeneratorpdf.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef PREVIEWGENERATORPDF_H
|
||||||
|
#define PREVIEWGENERATORPDF_H
|
||||||
|
#include <poppler-qt5.h>
|
||||||
|
#include "previewgenerator.h"
|
||||||
|
#include "previewresultpdf.h"
|
||||||
|
|
||||||
|
class PreviewGeneratorPdf : public PreviewGenerator
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
QHash<QString, Poppler::Document *> documentcache;
|
||||||
|
Poppler::Document *document(QString path);
|
||||||
|
|
||||||
|
public:
|
||||||
|
using PreviewGenerator::PreviewGenerator;
|
||||||
|
|
||||||
|
PreviewResult *generate(RenderConfig config, QString documentPath, unsigned int page);
|
||||||
|
|
||||||
|
~PreviewGeneratorPdf()
|
||||||
|
{
|
||||||
|
qDeleteAll(documentcache);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWGENERATORPDF_H
|
81
gui/previewgeneratorplaintext.cpp
Normal file
81
gui/previewgeneratorplaintext.cpp
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#include <QTextStream>
|
||||||
|
|
||||||
|
#include "previewgeneratorplaintext.h"
|
||||||
|
#include "previewresultplaintext.h"
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
QTextStream in(&file);
|
||||||
|
|
||||||
|
QString resulText = "";
|
||||||
|
QString content = in.readAll();
|
||||||
|
QMap<int, QString> snippet;
|
||||||
|
|
||||||
|
int coveredRange = 0;
|
||||||
|
|
||||||
|
int lastWordPos = 0;
|
||||||
|
QHash<QString, int> countmap;
|
||||||
|
|
||||||
|
for(QString &word : config.wordsToHighlight)
|
||||||
|
{
|
||||||
|
|
||||||
|
int lastPos = 0;
|
||||||
|
int index = content.indexOf(word, lastPos, Qt::CaseInsensitive);
|
||||||
|
while(index != -1)
|
||||||
|
{
|
||||||
|
countmap[word] = countmap.value(word, 0) + 1;
|
||||||
|
|
||||||
|
if(index >= lastWordPos && index <= coveredRange)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
int begin = index - 50;
|
||||||
|
if(begin < 0)
|
||||||
|
{
|
||||||
|
begin = 0;
|
||||||
|
}
|
||||||
|
int after = index + 50;
|
||||||
|
if(after > content.size())
|
||||||
|
{
|
||||||
|
after = content.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
snippet[index] = "...<br>" + content.mid(begin, after) + "...<br>";
|
||||||
|
coveredRange = after;
|
||||||
|
lastPos = index;
|
||||||
|
|
||||||
|
index = content.indexOf(word, lastPos + 1, Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
lastWordPos = lastPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto i = snippet.constBegin();
|
||||||
|
while(i != snippet.constEnd())
|
||||||
|
{
|
||||||
|
resulText.append(i.value());
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(QString &word : config.wordsToHighlight)
|
||||||
|
{
|
||||||
|
resulText.replace(word, "<span style=\"background-color: yellow;\">" + word + "</span>", Qt::CaseInsensitive);
|
||||||
|
}
|
||||||
|
|
||||||
|
QFileInfo info{documentPath};
|
||||||
|
|
||||||
|
QString header = "<b>" + info.fileName() + "</b> ";
|
||||||
|
for(QString &word : config.wordsToHighlight)
|
||||||
|
{
|
||||||
|
header += word + ": " + QString::number(countmap[word]) + " ";
|
||||||
|
}
|
||||||
|
header += "<hr>";
|
||||||
|
|
||||||
|
result->setText(header + resulText.replace("\n", "<br>"));
|
||||||
|
return result;
|
||||||
|
}
|
12
gui/previewgeneratorplaintext.h
Normal file
12
gui/previewgeneratorplaintext.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef PREVIEWGENERATORPLAINTEXT_H
|
||||||
|
#define PREVIEWGENERATORPLAINTEXT_H
|
||||||
|
#include "previewgenerator.h"
|
||||||
|
|
||||||
|
class PreviewGeneratorPlainText : public PreviewGenerator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PreviewGenerator::PreviewGenerator;
|
||||||
|
PreviewResult *generate(RenderConfig config, QString documentPath, unsigned int page);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWGENERATORPLAINTEXT_H
|
35
gui/previewresult.cpp
Normal file
35
gui/previewresult.cpp
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
#include "previewresult.h"
|
||||||
|
|
||||||
|
PreviewResult::PreviewResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviewResult::~PreviewResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *PreviewResult::createPreviewWidget()
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreviewResult::hasPreview()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PreviewResult::PreviewResult(QString documentPath, unsigned int page)
|
||||||
|
{
|
||||||
|
this->documentPath = documentPath;
|
||||||
|
this->page = page;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString PreviewResult::getDocumentPath() const
|
||||||
|
{
|
||||||
|
return this->documentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int PreviewResult::getPage() const
|
||||||
|
{
|
||||||
|
return this->page;
|
||||||
|
}
|
22
gui/previewresult.h
Normal file
22
gui/previewresult.h
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
#ifndef PREVIEWRESULT_H
|
||||||
|
#define PREVIEWRESULT_H
|
||||||
|
#include "clicklabel.h"
|
||||||
|
|
||||||
|
class PreviewResult
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
QString documentPath;
|
||||||
|
unsigned int page;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PreviewResult();
|
||||||
|
PreviewResult(QString documentPath, unsigned int page);
|
||||||
|
PreviewResult(const PreviewResult &o) = default;
|
||||||
|
virtual ~PreviewResult();
|
||||||
|
virtual QWidget *createPreviewWidget();
|
||||||
|
virtual bool hasPreview();
|
||||||
|
QString getDocumentPath() const;
|
||||||
|
unsigned int getPage() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWRESULT_H
|
21
gui/previewresultpdf.cpp
Normal file
21
gui/previewresultpdf.cpp
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "previewresultpdf.h"
|
||||||
|
|
||||||
|
PreviewResultPdf::PreviewResultPdf(const PreviewResult &o)
|
||||||
|
{
|
||||||
|
this->documentPath = o.getDocumentPath();
|
||||||
|
this->page = o.getPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *PreviewResultPdf::createPreviewWidget()
|
||||||
|
{
|
||||||
|
ClickLabel *label = new ClickLabel();
|
||||||
|
label->setPixmap(QPixmap::fromImage(previewImage));
|
||||||
|
label->setToolTip(getDocumentPath());
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreviewResultPdf::hasPreview()
|
||||||
|
{
|
||||||
|
bool result = !this->previewImage.isNull();
|
||||||
|
return result;
|
||||||
|
}
|
17
gui/previewresultpdf.h
Normal file
17
gui/previewresultpdf.h
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#ifndef PREVIEWRESULTPDF_H
|
||||||
|
#define PREVIEWRESULTPDF_H
|
||||||
|
#include <QImage>
|
||||||
|
#include "previewresult.h"
|
||||||
|
|
||||||
|
class PreviewResultPdf : public PreviewResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PreviewResult::PreviewResult;
|
||||||
|
PreviewResultPdf(const PreviewResult &o);
|
||||||
|
QImage previewImage;
|
||||||
|
|
||||||
|
QWidget *createPreviewWidget() override;
|
||||||
|
bool hasPreview() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWRESULTPDF_H
|
30
gui/previewresultplaintext.cpp
Normal file
30
gui/previewresultplaintext.cpp
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#include "previewresultplaintext.h"
|
||||||
|
|
||||||
|
PreviewResultPlainText::PreviewResultPlainText(const PreviewResult &o)
|
||||||
|
{
|
||||||
|
this->documentPath = o.getDocumentPath();
|
||||||
|
this->page = o.getPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
QWidget *PreviewResultPlainText::createPreviewWidget()
|
||||||
|
{
|
||||||
|
|
||||||
|
ClickLabel *label = new ClickLabel();
|
||||||
|
label->setText(this->text);
|
||||||
|
label->setToolTip(getDocumentPath());
|
||||||
|
label->setStyleSheet("border: 1px solid black");
|
||||||
|
label->setMaximumWidth(768);
|
||||||
|
label->setMaximumHeight(512);
|
||||||
|
label->setTextFormat(Qt::RichText);
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PreviewResultPlainText::hasPreview()
|
||||||
|
{
|
||||||
|
return !text.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreviewResultPlainText::setText(QString text)
|
||||||
|
{
|
||||||
|
this->text = text;
|
||||||
|
}
|
20
gui/previewresultplaintext.h
Normal file
20
gui/previewresultplaintext.h
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#ifndef PREVIEWRESULTPLAINTEXT_H
|
||||||
|
#define PREVIEWRESULTPLAINTEXT_H
|
||||||
|
#include "previewresult.h"
|
||||||
|
|
||||||
|
class PreviewResultPlainText : public PreviewResult
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
QString text;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using PreviewResult::PreviewResult;
|
||||||
|
PreviewResultPlainText(const PreviewResult &o);
|
||||||
|
|
||||||
|
QWidget *createPreviewWidget() override;
|
||||||
|
bool hasPreview() override;
|
||||||
|
|
||||||
|
void setText(QString text);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PREVIEWRESULTPLAINTEXT_H
|
39
gui/previewworker.cpp
Normal file
39
gui/previewworker.cpp
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
#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);
|
||||||
|
}
|
29
gui/previewworker.h
Normal file
29
gui/previewworker.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#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
|
12
gui/renderconfig.h
Normal file
12
gui/renderconfig.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
#ifndef RENDERCONFIG_H
|
||||||
|
#define RENDERCONFIG_H
|
||||||
|
#include <QVector>
|
||||||
|
|
||||||
|
struct RenderConfig
|
||||||
|
{
|
||||||
|
double scaleX = 50 / 100.;
|
||||||
|
double scaleY = scaleX;
|
||||||
|
QVector<QString> wordsToHighlight;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // RENDERCONFIG_H
|
9
looqs.desktop
Normal file
9
looqs.desktop
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Name=looqs
|
||||||
|
Exec=/usr/bin/looqs-gui
|
||||||
|
Terminal=false
|
||||||
|
Type=Application
|
||||||
|
Icon=looqs
|
||||||
|
StartupWMClass=looqs
|
||||||
|
Comment=FTS desktop search with previews
|
||||||
|
Categories=Qt;Utility;
|
@ -9,13 +9,17 @@
|
|||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include "looqsgeneralexception.h"
|
#include "looqsgeneralexception.h"
|
||||||
#include "common.h"
|
#include "common.h"
|
||||||
|
#include "dbmigrator.h"
|
||||||
|
#include "databasefactory.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
#define SETTINGS_KEY_DBPATH "dbpath"
|
#define SETTINGS_KEY_DBPATH "dbpath"
|
||||||
#define SETTINGS_KEY_FIRSTRUN "firstrun"
|
#define SETTINGS_KEY_FIRSTRUN "firstrun"
|
||||||
|
#define SETTINGS_KEY_IPCSOCKETPATH "ipcsocketpath"
|
||||||
|
|
||||||
inline void initResources()
|
inline void initResources()
|
||||||
{
|
{
|
||||||
Q_INIT_RESOURCE(create);
|
Q_INIT_RESOURCE(migrations);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Common::initSqliteDatabase(QString path)
|
bool Common::initSqliteDatabase(QString path)
|
||||||
@ -28,28 +32,9 @@ bool Common::initSqliteDatabase(QString path)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
initResources();
|
initResources();
|
||||||
QFile file(":./create.sql");
|
DBMigrator migrator{db};
|
||||||
if(!file.open(QIODevice::ReadOnly))
|
migrator.performMigrations();
|
||||||
{
|
|
||||||
qDebug() << "Failed to load SQL creation script from embedded resource";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
QTextStream stream(&file);
|
|
||||||
db.transaction();
|
|
||||||
while(!stream.atEnd())
|
|
||||||
{
|
|
||||||
QString sql = stream.readLine();
|
|
||||||
QSqlQuery sqlQuery;
|
|
||||||
if(!sqlQuery.exec(sql))
|
|
||||||
{
|
|
||||||
qDebug() << "Failed to execute sql statement while initializing database: " << sqlQuery.lastError();
|
|
||||||
db.rollback();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
db.commit();
|
|
||||||
db.close();
|
db.close();
|
||||||
file.close();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,6 +69,21 @@ void Common::ensureConfigured()
|
|||||||
{
|
{
|
||||||
throw LooqsGeneralException("Database " + dbpath + " was not found");
|
throw LooqsGeneralException("Database " + dbpath + " was not found");
|
||||||
}
|
}
|
||||||
|
DatabaseFactory factory{dbpath};
|
||||||
|
auto db = factory.forCurrentThread();
|
||||||
|
DBMigrator migrator{db};
|
||||||
|
if(migrator.migrationNeeded())
|
||||||
|
{
|
||||||
|
QFile out;
|
||||||
|
out.open(stderr, QIODevice::WriteOnly);
|
||||||
|
Logger migrationLogger{&out};
|
||||||
|
migrationLogger << "Database is being upgraded, please be patient..." << Qt::endl;
|
||||||
|
QObject::connect(&migrator, &DBMigrator::migrationDone,
|
||||||
|
[&migrationLogger](uint32_t migration)
|
||||||
|
{ migrationLogger << "Progress: Successfully migrated to: " << migration << Qt::endl; });
|
||||||
|
migrator.performMigrations();
|
||||||
|
migrationLogger << "Database upgraded successfully" << Qt::endl;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,7 +96,7 @@ void Common::setupAppInfo()
|
|||||||
|
|
||||||
QString Common::databasePath()
|
QString Common::databasePath()
|
||||||
{
|
{
|
||||||
QString env = QProcessEnvironment::systemEnvironment().value("QSS_DB_OVERRIDE");
|
QString env = QProcessEnvironment::systemEnvironment().value("LOOQS_DB_OVERRIDE");
|
||||||
if(env == "")
|
if(env == "")
|
||||||
{
|
{
|
||||||
QSettings settings;
|
QSettings settings;
|
||||||
@ -104,3 +104,9 @@ QString Common::databasePath()
|
|||||||
}
|
}
|
||||||
return env;
|
return env;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Common::ipcSocketPath()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
return settings.value(SETTINGS_KEY_IPCSOCKETPATH, "/tmp/looqs-spawner").toString();
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ namespace Common
|
|||||||
{
|
{
|
||||||
void setupAppInfo();
|
void setupAppInfo();
|
||||||
QString databasePath();
|
QString databasePath();
|
||||||
|
QString ipcSocketPath();
|
||||||
bool initSqliteDatabase(QString path);
|
bool initSqliteDatabase(QString path);
|
||||||
void ensureConfigured();
|
void ensureConfigured();
|
||||||
} // namespace Common
|
} // namespace Common
|
||||||
|
1
shared/concurrentqueue.cpp
Normal file
1
shared/concurrentqueue.cpp
Normal file
@ -0,0 +1 @@
|
|||||||
|
#include "concurrentqueue.h"
|
66
shared/concurrentqueue.h
Normal file
66
shared/concurrentqueue.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef CONCURRENTQUEUE_H
|
||||||
|
#define CONCURRENTQUEUE_H
|
||||||
|
#include <QList>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QSemaphore>
|
||||||
|
#define QUEUE_SIZE 10000
|
||||||
|
template <class T> class ConcurrentQueue : protected QList<T>
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
QMutex mutex;
|
||||||
|
|
||||||
|
QSemaphore avail{QUEUE_SIZE};
|
||||||
|
|
||||||
|
public:
|
||||||
|
void enqueue(const T &t)
|
||||||
|
{
|
||||||
|
avail.acquire(1);
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
QList<T>::append(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<T> dequeue(int batchsize)
|
||||||
|
{
|
||||||
|
avail.release(batchsize);
|
||||||
|
// TODO: this sucks
|
||||||
|
QVector<T> result;
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
for(int i = 0; i < batchsize; i++)
|
||||||
|
{
|
||||||
|
result.append(QList<T>::takeFirst());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void enqueue(const QVector<T> &t)
|
||||||
|
{
|
||||||
|
QList<T> tmp(t.begin(), t.end());
|
||||||
|
avail.acquire(t.size());
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
QList<T>::append(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int remaining()
|
||||||
|
{
|
||||||
|
return QUEUE_SIZE - avail.available();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear()
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
QList<T>::clear();
|
||||||
|
avail.release(QUEUE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool dequeue(T &result)
|
||||||
|
{
|
||||||
|
QMutexLocker locker(&mutex);
|
||||||
|
if(QList<T>::isEmpty())
|
||||||
|
return false;
|
||||||
|
avail.release(1);
|
||||||
|
result = QList<T>::takeFirst();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CONCURRENTQUEUE_H
|
@ -1,5 +0,0 @@
|
|||||||
<!DOCTYPE RCC><RCC version="1.0">
|
|
||||||
<qresource>
|
|
||||||
<file>create.sql</file>
|
|
||||||
</qresource>
|
|
||||||
</RCC>
|
|
@ -1,6 +1,7 @@
|
|||||||
#include <QThread>
|
#include <QThread>
|
||||||
#include "databasefactory.h"
|
#include "databasefactory.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
|
#include "looqsgeneralexception.h"
|
||||||
DatabaseFactory::DatabaseFactory(QString connectionString)
|
DatabaseFactory::DatabaseFactory(QString connectionString)
|
||||||
{
|
{
|
||||||
this->connectionString = connectionString;
|
this->connectionString = connectionString;
|
||||||
@ -11,7 +12,7 @@ static QThreadStorage<QSqlDatabase> dbStore;
|
|||||||
QSqlDatabase DatabaseFactory::createNew()
|
QSqlDatabase DatabaseFactory::createNew()
|
||||||
{
|
{
|
||||||
static int counter = 0;
|
static int counter = 0;
|
||||||
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "QSS" + QString::number(counter++));
|
QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE", "LOOQS" + QString::number(counter++));
|
||||||
db.setDatabaseName(this->connectionString);
|
db.setDatabaseName(this->connectionString);
|
||||||
if(!db.open())
|
if(!db.open())
|
||||||
{
|
{
|
||||||
@ -28,7 +29,7 @@ QSqlDatabase DatabaseFactory::forCurrentThread()
|
|||||||
return dbStore.localData();
|
return dbStore.localData();
|
||||||
}
|
}
|
||||||
QSqlDatabase db =
|
QSqlDatabase db =
|
||||||
QSqlDatabase::addDatabase("QSQLITE", "QSS" + QString::number((quint64)QThread::currentThread(), 16));
|
QSqlDatabase::addDatabase("QSQLITE", "LOOQS" + QString::number((quint64)QThread::currentThread(), 16));
|
||||||
db.setDatabaseName(this->connectionString);
|
db.setDatabaseName(this->connectionString);
|
||||||
if(!db.open())
|
if(!db.open())
|
||||||
{
|
{
|
@ -2,7 +2,6 @@
|
|||||||
#define DATABASEFACTORY_H
|
#define DATABASEFACTORY_H
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QThreadStorage>
|
#include <QThreadStorage>
|
||||||
#include "utils.h"
|
|
||||||
class DatabaseFactory
|
class DatabaseFactory
|
||||||
{
|
{
|
||||||
private:
|
private:
|
85
shared/dbmigrator.cpp
Normal file
85
shared/dbmigrator.cpp
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
#include <QDirIterator>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QDebug>
|
||||||
|
#include "dbmigrator.h"
|
||||||
|
#include "looqsgeneralexception.h"
|
||||||
|
|
||||||
|
DBMigrator::DBMigrator(QSqlDatabase &db)
|
||||||
|
{
|
||||||
|
Q_INIT_RESOURCE(migrations);
|
||||||
|
this->db = &db;
|
||||||
|
}
|
||||||
|
|
||||||
|
DBMigrator::~DBMigrator()
|
||||||
|
{
|
||||||
|
Q_CLEANUP_RESOURCE(migrations);
|
||||||
|
}
|
||||||
|
|
||||||
|
QStringList DBMigrator::getMigrationFilenames()
|
||||||
|
{
|
||||||
|
QStringList result;
|
||||||
|
QDirIterator it(":/looqs-migrations/");
|
||||||
|
while(it.hasNext())
|
||||||
|
{
|
||||||
|
result.append(it.next());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t DBMigrator::currentRevision()
|
||||||
|
{
|
||||||
|
QSqlQuery dbquery(*db);
|
||||||
|
dbquery.exec("PRAGMA user_version;");
|
||||||
|
if(!dbquery.next())
|
||||||
|
{
|
||||||
|
throw new LooqsGeneralException("Failed to query current db revision");
|
||||||
|
}
|
||||||
|
uint32_t result = dbquery.value(0).toUInt();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DBMigrator::migrationNeeded()
|
||||||
|
{
|
||||||
|
QStringList migrations = getMigrationFilenames();
|
||||||
|
uint32_t currentRev = currentRevision();
|
||||||
|
|
||||||
|
return currentRev < static_cast<uint32_t>(migrations.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void DBMigrator::performMigrations()
|
||||||
|
{
|
||||||
|
QStringList migrations = getMigrationFilenames();
|
||||||
|
uint32_t currentRev = currentRevision();
|
||||||
|
uint32_t targetRev = (migrations.size());
|
||||||
|
|
||||||
|
for(uint32_t i = currentRev + 1; i <= targetRev; i++)
|
||||||
|
{
|
||||||
|
QString fileName = QString(":/looqs-migrations/%1.sql").arg(i);
|
||||||
|
QFile file{fileName};
|
||||||
|
if(!file.open(QIODevice::ReadOnly))
|
||||||
|
{
|
||||||
|
throw LooqsGeneralException("Migration: Failed to find required revision file");
|
||||||
|
}
|
||||||
|
QTextStream stream(&file);
|
||||||
|
db->transaction();
|
||||||
|
while(!stream.atEnd())
|
||||||
|
{
|
||||||
|
QString sql = stream.readLine();
|
||||||
|
QSqlQuery sqlQuery{*db};
|
||||||
|
if(!sqlQuery.exec(sql))
|
||||||
|
{
|
||||||
|
|
||||||
|
db->rollback();
|
||||||
|
throw LooqsGeneralException("Failed to execute sql statement while initializing database: " +
|
||||||
|
sqlQuery.lastError().text());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QSqlQuery updateVersion{*db};
|
||||||
|
updateVersion.exec(QString("PRAGMA user_version=%1;").arg(i));
|
||||||
|
db->commit();
|
||||||
|
emit migrationDone(i);
|
||||||
|
}
|
||||||
|
emit done();
|
||||||
|
}
|
24
shared/dbmigrator.h
Normal file
24
shared/dbmigrator.h
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
#ifndef DBMIGRATOR_H
|
||||||
|
#define DBMIGRATOR_H
|
||||||
|
#include <QStringList>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QObject>
|
||||||
|
class DBMigrator : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
private:
|
||||||
|
QSqlDatabase *db;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DBMigrator(QSqlDatabase &db);
|
||||||
|
~DBMigrator();
|
||||||
|
uint32_t currentRevision();
|
||||||
|
void performMigrations();
|
||||||
|
QStringList getMigrationFilenames();
|
||||||
|
bool migrationNeeded();
|
||||||
|
signals:
|
||||||
|
void migrationDone(uint32_t);
|
||||||
|
void done();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DBMIGRATOR_H
|
53
shared/dirscanworker.cpp
Normal file
53
shared/dirscanworker.cpp
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
#include <QThread>
|
||||||
|
#include "dirscanworker.h"
|
||||||
|
#include "logger.h"
|
||||||
|
DirScanWorker::DirScanWorker(ConcurrentQueue<QString> &queue, ConcurrentQueue<QString> &resultQueue,
|
||||||
|
QStringList ignorePattern, unsigned int progressReportThreshold,
|
||||||
|
std::atomic<bool> &stopToken)
|
||||||
|
{
|
||||||
|
this->queue = &queue;
|
||||||
|
this->resultQueue = &resultQueue;
|
||||||
|
this->ignorePattern = ignorePattern;
|
||||||
|
this->progressReportThreshold = progressReportThreshold;
|
||||||
|
this->stopToken = &stopToken;
|
||||||
|
setAutoDelete(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirScanWorker::run()
|
||||||
|
{
|
||||||
|
unsigned int currentProgress = 0;
|
||||||
|
QString path;
|
||||||
|
/* TODO: if we have e. g. only one path, then only one thread will scan this path.
|
||||||
|
*
|
||||||
|
* Thus, we must resubmit to the queue directories so other threads can help
|
||||||
|
the current one (requires a new logic for threads in ParallelDirScanner). Alterantively,
|
||||||
|
start new DirScanWorkers ourselves here... */
|
||||||
|
while(queue->dequeue(path))
|
||||||
|
{
|
||||||
|
QDirIterator iterator(path, ignorePattern, QDir::Files, QDirIterator::Subdirectories);
|
||||||
|
while(iterator.hasNext())
|
||||||
|
{
|
||||||
|
this->results.append(iterator.next());
|
||||||
|
++currentProgress;
|
||||||
|
if(currentProgress == progressReportThreshold)
|
||||||
|
{
|
||||||
|
if(this->stopToken->load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
Logger::info() << "Received cancel request" << Qt::endl;
|
||||||
|
this->results.clear();
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->resultQueue->enqueue(this->results);
|
||||||
|
emit progress(results.length());
|
||||||
|
currentProgress = 0;
|
||||||
|
this->results.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->resultQueue->enqueue(this->results);
|
||||||
|
emit progress(results.length());
|
||||||
|
this->results.clear();
|
||||||
|
emit finished();
|
||||||
|
}
|
31
shared/dirscanworker.h
Normal file
31
shared/dirscanworker.h
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
#ifndef DIRSCANWORKER_H
|
||||||
|
#define DIRSCANWORKER_H
|
||||||
|
#include <QObject>
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include "concurrentqueue.h"
|
||||||
|
class DirScanWorker : public QObject, public QRunnable
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
unsigned int progressReportThreshold = 1000;
|
||||||
|
ConcurrentQueue<QString> *queue = nullptr;
|
||||||
|
ConcurrentQueue<QString> *resultQueue = nullptr;
|
||||||
|
|
||||||
|
QStringList ignorePattern;
|
||||||
|
QVector<QString> results;
|
||||||
|
|
||||||
|
std::atomic<bool> *stopToken;
|
||||||
|
|
||||||
|
public:
|
||||||
|
DirScanWorker(ConcurrentQueue<QString> &queue, ConcurrentQueue<QString> &resultQueue, QStringList ignorePattern,
|
||||||
|
unsigned int progressReportThreshold, std::atomic<bool> &stopToken);
|
||||||
|
|
||||||
|
void run() override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void progress(unsigned int);
|
||||||
|
void finished();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DIRSCANWORKER_H
|
@ -1,11 +1,11 @@
|
|||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QtConcurrentMap>
|
#include <QtConcurrentMap>
|
||||||
|
#include <QProcess>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include "filesaver.h"
|
#include "filesaver.h"
|
||||||
#include "processor.h"
|
#include "processor.h"
|
||||||
#include "pdfprocessor.h"
|
#include "pdfprocessor.h"
|
||||||
#include "commandadd.h"
|
|
||||||
#include "defaulttextprocessor.h"
|
#include "defaulttextprocessor.h"
|
||||||
#include "tagstripperprocessor.h"
|
#include "tagstripperprocessor.h"
|
||||||
#include "nothingprocessor.h"
|
#include "nothingprocessor.h"
|
||||||
@ -13,18 +13,6 @@
|
|||||||
#include "odsprocessor.h"
|
#include "odsprocessor.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
static DefaultTextProcessor *defaultTextProcessor = new DefaultTextProcessor();
|
|
||||||
static TagStripperProcessor *tagStripperProcessor = new TagStripperProcessor();
|
|
||||||
static NothingProcessor *nothingProcessor = new NothingProcessor();
|
|
||||||
static OdtProcessor *odtProcessor = new OdtProcessor();
|
|
||||||
static OdsProcessor *odsProcessor = new OdsProcessor();
|
|
||||||
|
|
||||||
static QMap<QString, Processor *> processors{
|
|
||||||
{"pdf", new PdfProcessor()}, {"txt", defaultTextProcessor}, {"md", defaultTextProcessor},
|
|
||||||
{"py", defaultTextProcessor}, {"xml", nothingProcessor}, {"html", tagStripperProcessor},
|
|
||||||
{"java", defaultTextProcessor}, {"js", defaultTextProcessor}, {"cpp", defaultTextProcessor},
|
|
||||||
{"c", defaultTextProcessor}, {"sql", defaultTextProcessor}, {"odt", odtProcessor},
|
|
||||||
{"ods", odsProcessor}};
|
|
||||||
|
|
||||||
FileSaver::FileSaver(SqliteDbService &dbService)
|
FileSaver::FileSaver(SqliteDbService &dbService)
|
||||||
{
|
{
|
||||||
@ -106,32 +94,53 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
|
|||||||
|
|
||||||
SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
|
SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
|
||||||
{
|
{
|
||||||
Processor *processor = processors.value(fileInfo.suffix(), nothingProcessor);
|
|
||||||
QVector<PageData> pageData;
|
QVector<PageData> pageData;
|
||||||
QString absPath = fileInfo.absoluteFilePath();
|
QString absPath = fileInfo.absoluteFilePath();
|
||||||
|
|
||||||
|
int status = -1;
|
||||||
|
|
||||||
|
if(!fileInfo.exists())
|
||||||
|
{
|
||||||
|
return NOTFOUND;
|
||||||
|
}
|
||||||
|
|
||||||
if(fileInfo.isFile())
|
if(fileInfo.isFile())
|
||||||
{
|
{
|
||||||
try
|
QProcess process;
|
||||||
|
QStringList args;
|
||||||
|
args << "process" << absPath;
|
||||||
|
process.setProcessChannelMode(QProcess::ForwardedErrorChannel);
|
||||||
|
process.start("/proc/self/exe", args);
|
||||||
|
process.waitForStarted();
|
||||||
|
process.waitForFinished();
|
||||||
|
|
||||||
|
/* TODO: This is suboptimal as it eats lots of mem
|
||||||
|
* but avoids a weird QDataStream/QProcess behaviour
|
||||||
|
* where it thinks the process has ended when it has not...
|
||||||
|
*
|
||||||
|
* Also, there seem to be issues with reads not being blocked, so
|
||||||
|
* the only reliable way appears to be waiting until the process
|
||||||
|
* finishes.
|
||||||
|
*/
|
||||||
|
QDataStream in(process.readAllStandardOutput());
|
||||||
|
while(!in.atEnd())
|
||||||
{
|
{
|
||||||
if(processor->PREFERED_DATA_SOURCE == FILEPATH)
|
PageData pd;
|
||||||
{
|
in >> pd;
|
||||||
pageData = processor->process(absPath);
|
pageData.append(pd);
|
||||||
}
|
}
|
||||||
else
|
status = process.exitCode();
|
||||||
|
if(status != 0 && status != NOTHING_PROCESSED)
|
||||||
{
|
{
|
||||||
pageData = processor->process(Utils::readFile(absPath));
|
Logger::error() << "FileSaver::saveFile(): Error while processing" << absPath << ":"
|
||||||
}
|
<< "Exit code " << status << Qt::endl;
|
||||||
}
|
|
||||||
catch(LooqsGeneralException &e)
|
|
||||||
{
|
|
||||||
Logger::error() << "Error while processing" << absPath << ":" << e.message << Qt::endl;
|
|
||||||
return PROCESSFAIL;
|
return PROCESSFAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Could happen if a file corrupted for example
|
// Could happen if a file corrupted for example
|
||||||
if(pageData.isEmpty() && processor != nothingProcessor)
|
if(pageData.isEmpty() && status != NOTHING_PROCESSED)
|
||||||
{
|
{
|
||||||
Logger::error() << "Could not get any content for " << absPath << Qt::endl;
|
Logger::error() << "Could not get any content for " << absPath << Qt::endl;
|
||||||
}
|
}
|
@ -2,7 +2,6 @@
|
|||||||
#define FILESAVER_H
|
#define FILESAVER_H
|
||||||
#include <QSqlDatabase>
|
#include <QSqlDatabase>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include "command.h"
|
|
||||||
#include "pagedata.h"
|
#include "pagedata.h"
|
||||||
#include "filedata.h"
|
#include "filedata.h"
|
||||||
#include "sqlitedbservice.h"
|
#include "sqlitedbservice.h"
|
||||||
@ -12,12 +11,10 @@ class FileSaver
|
|||||||
private:
|
private:
|
||||||
SqliteDbService *dbService;
|
SqliteDbService *dbService;
|
||||||
|
|
||||||
protected:
|
|
||||||
SaveFileResult addFile(QString path);
|
|
||||||
SaveFileResult updateFile(QString path);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileSaver(SqliteDbService &dbService);
|
FileSaver(SqliteDbService &dbService);
|
||||||
|
SaveFileResult addFile(QString path);
|
||||||
|
SaveFileResult updateFile(QString path);
|
||||||
SaveFileResult saveFile(const QFileInfo &fileInfo);
|
SaveFileResult saveFile(const QFileInfo &fileInfo);
|
||||||
int processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc,
|
int processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc,
|
||||||
bool keepGoing, bool verbose);
|
bool keepGoing, bool verbose);
|
36
shared/filescanworker.cpp
Normal file
36
shared/filescanworker.cpp
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
#include "filescanworker.h"
|
||||||
|
#include "logger.h"
|
||||||
|
FileScanWorker::FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize,
|
||||||
|
std::atomic<bool> &stopToken)
|
||||||
|
{
|
||||||
|
this->dbService = &db;
|
||||||
|
this->queue = &queue;
|
||||||
|
this->batchsize = batchsize;
|
||||||
|
this->stopToken = &stopToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileScanWorker::run()
|
||||||
|
{
|
||||||
|
FileSaver saver{*this->dbService};
|
||||||
|
auto paths = queue->dequeue(batchsize);
|
||||||
|
for(QString &path : paths)
|
||||||
|
{
|
||||||
|
SaveFileResult sfr;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
sfr = saver.addFile(path);
|
||||||
|
}
|
||||||
|
catch(std::exception &e)
|
||||||
|
{
|
||||||
|
Logger::error() << e.what();
|
||||||
|
sfr = PROCESSFAIL; // well...
|
||||||
|
}
|
||||||
|
emit result({path, sfr});
|
||||||
|
if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck
|
||||||
|
{
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
emit finished();
|
||||||
|
}
|
29
shared/filescanworker.h
Normal file
29
shared/filescanworker.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#ifndef FILESCANWORKER_H
|
||||||
|
#define FILESCANWORKER_H
|
||||||
|
#include <QString>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QtConcurrent>
|
||||||
|
#include <utility>
|
||||||
|
#include "paralleldirscanner.h"
|
||||||
|
#include "filesaver.h"
|
||||||
|
|
||||||
|
typedef std::pair<QString, SaveFileResult> FileScanResult;
|
||||||
|
|
||||||
|
class FileScanWorker : public QObject, public QRunnable
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
SqliteDbService *dbService;
|
||||||
|
ConcurrentQueue<QString> *queue;
|
||||||
|
int batchsize;
|
||||||
|
std::atomic<bool> *stopToken;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize, std::atomic<bool> &stopToken);
|
||||||
|
void run() override;
|
||||||
|
signals:
|
||||||
|
void result(FileScanResult);
|
||||||
|
void finished();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FILESCANWORKER_H
|
132
shared/indexer.cpp
Normal file
132
shared/indexer.cpp
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#include "indexer.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
Indexer::Indexer(SqliteDbService &db)
|
||||||
|
{
|
||||||
|
dirScanner = QSharedPointer<ParallelDirScanner>(new ParallelDirScanner());
|
||||||
|
connect(dirScanner.data(), &ParallelDirScanner::scanComplete, this, &Indexer::dirScanFinished);
|
||||||
|
connect(dirScanner.data(), &ParallelDirScanner::progress, this, &Indexer::dirScanProgress);
|
||||||
|
this->db = &db;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::beginIndexing()
|
||||||
|
{
|
||||||
|
this->runningWorkers = 0;
|
||||||
|
this->currentScanProcessedCount = 0;
|
||||||
|
this->currentIndexResult = IndexResult();
|
||||||
|
this->currentIndexResult.begin = QDateTime::currentDateTime();
|
||||||
|
QVector<QString> dirs;
|
||||||
|
|
||||||
|
for(QString &path : this->pathsToScan)
|
||||||
|
{
|
||||||
|
QFileInfo info{path};
|
||||||
|
if(info.isDir())
|
||||||
|
{
|
||||||
|
dirs.append(path);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this->filePathTargetsQueue.enqueue(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this->dirScanner->setPaths(dirs);
|
||||||
|
this->dirScanner->setIgnorePatterns(this->ignorePattern);
|
||||||
|
|
||||||
|
this->dirScanner->scan();
|
||||||
|
|
||||||
|
this->workerCancellationToken.store(false, std::memory_order_seq_cst);
|
||||||
|
launchWorker(this->filePathTargetsQueue, this->filePathTargetsQueue.remaining());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::setIgnorePattern(QStringList ignorePattern)
|
||||||
|
{
|
||||||
|
this->ignorePattern = ignorePattern;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::setTargetPaths(QVector<QString> pathsToScan)
|
||||||
|
{
|
||||||
|
this->pathsToScan = pathsToScan;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::requestCancellation()
|
||||||
|
{
|
||||||
|
this->dirScanner->cancel();
|
||||||
|
this->workerCancellationToken.store(true, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
IndexResult Indexer::getResult()
|
||||||
|
{
|
||||||
|
return this->currentIndexResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::dirScanFinished()
|
||||||
|
{
|
||||||
|
Logger::info() << "Dir scan finished";
|
||||||
|
if(!isRunning())
|
||||||
|
{
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::launchWorker(ConcurrentQueue<QString> &queue, int batchsize)
|
||||||
|
{
|
||||||
|
FileScanWorker *runnable = new FileScanWorker(*this->db, queue, batchsize, this->workerCancellationToken);
|
||||||
|
connect(runnable, &FileScanWorker::result, this, &Indexer::processFileScanResult);
|
||||||
|
connect(runnable, &FileScanWorker::finished, this, &Indexer::processFinishedWorker);
|
||||||
|
++this->runningWorkers;
|
||||||
|
QThreadPool::globalInstance()->start(runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::dirScanProgress(int current, int total)
|
||||||
|
{
|
||||||
|
launchWorker(this->dirScanner->getResults(), current);
|
||||||
|
emit pathsCountChanged(total);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Indexer::processFileScanResult(FileScanResult result)
|
||||||
|
{
|
||||||
|
if(verbose)
|
||||||
|
{
|
||||||
|
this->currentIndexResult.results.append(result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(result.second == DBFAIL || result.second == PROCESSFAIL || result.second == NOTFOUND)
|
||||||
|
{
|
||||||
|
this->currentIndexResult.results.append(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(result.second == OK)
|
||||||
|
{
|
||||||
|
++this->currentIndexResult.addedPaths;
|
||||||
|
}
|
||||||
|
else if(result.second == SKIPPED)
|
||||||
|
{
|
||||||
|
++this->currentIndexResult.skippedPaths;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++this->currentIndexResult.erroredPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(currentScanProcessedCount++ == progressReportThreshold)
|
||||||
|
{
|
||||||
|
emit indexProgress(this->currentIndexResult.total(), this->currentIndexResult.addedPaths,
|
||||||
|
this->currentIndexResult.skippedPaths, this->currentIndexResult.erroredPaths,
|
||||||
|
this->dirScanner->pathCount());
|
||||||
|
currentScanProcessedCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Indexer::isRunning()
|
||||||
|
{
|
||||||
|
return this->runningWorkers > 0 || this->dirScanner->isRunning();
|
||||||
|
}
|
||||||
|
void Indexer::processFinishedWorker()
|
||||||
|
{
|
||||||
|
--this->runningWorkers;
|
||||||
|
if(!isRunning())
|
||||||
|
{
|
||||||
|
emit finished();
|
||||||
|
}
|
||||||
|
}
|
90
shared/indexer.h
Normal file
90
shared/indexer.h
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
#ifndef INDEXER_H
|
||||||
|
#define INDEXER_H
|
||||||
|
#include <QVector>
|
||||||
|
#include <QObject>
|
||||||
|
#include "sqlitedbservice.h"
|
||||||
|
#include "paralleldirscanner.h"
|
||||||
|
#include "filescanworker.h"
|
||||||
|
|
||||||
|
class IndexResult
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
QDateTime begin;
|
||||||
|
QDateTime end;
|
||||||
|
QVector<FileScanResult> results;
|
||||||
|
|
||||||
|
unsigned int addedPaths = 0;
|
||||||
|
unsigned int skippedPaths = 0;
|
||||||
|
unsigned int erroredPaths = 0;
|
||||||
|
|
||||||
|
unsigned int total()
|
||||||
|
{
|
||||||
|
return addedPaths + skippedPaths + erroredPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<QString> failedPaths() const
|
||||||
|
{
|
||||||
|
QVector<QString> result;
|
||||||
|
std::for_each(results.begin(), results.end(),
|
||||||
|
[&result](FileScanResult res)
|
||||||
|
{
|
||||||
|
if(res.second == DBFAIL || res.second == PROCESSFAIL || res.second == NOTFOUND)
|
||||||
|
{
|
||||||
|
result.append(res.first);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Indexer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
bool verbose = false;
|
||||||
|
bool keepGoing = true;
|
||||||
|
SqliteDbService *db;
|
||||||
|
|
||||||
|
int progressReportThreshold = 50;
|
||||||
|
int currentScanProcessedCount = 0;
|
||||||
|
int runningWorkers = 0;
|
||||||
|
|
||||||
|
QVector<QString> pathsToScan;
|
||||||
|
QSharedPointer<ParallelDirScanner> dirScanner;
|
||||||
|
|
||||||
|
QStringList ignorePattern;
|
||||||
|
|
||||||
|
/* Those path pointing to files not directories */
|
||||||
|
ConcurrentQueue<QString> filePathTargetsQueue;
|
||||||
|
|
||||||
|
std::atomic<bool> workerCancellationToken;
|
||||||
|
IndexResult currentIndexResult;
|
||||||
|
void launchWorker(ConcurrentQueue<QString> &queue, int batchsize);
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
void beginIndexing();
|
||||||
|
void setIgnorePattern(QStringList ignorePattern);
|
||||||
|
void setTargetPaths(QVector<QString> pathsToScan);
|
||||||
|
|
||||||
|
void requestCancellation();
|
||||||
|
|
||||||
|
Indexer(SqliteDbService &db);
|
||||||
|
IndexResult getResult();
|
||||||
|
|
||||||
|
public slots:
|
||||||
|
void dirScanFinished();
|
||||||
|
void dirScanProgress(int current, int total);
|
||||||
|
void processFileScanResult(FileScanResult result);
|
||||||
|
void processFinishedWorker();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void pathsCountChanged(int total);
|
||||||
|
void fileScanResult(FileScanResult *result);
|
||||||
|
void indexProgress(unsigned int processedFiles, unsigned int added, unsigned int skipped, unsigned int failed,
|
||||||
|
unsigned int totalPaths);
|
||||||
|
void finished();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // INDEXER_H
|
@ -23,6 +23,16 @@ QueryType LooqsQuery::getQueryType()
|
|||||||
return static_cast<QueryType>(tokensMask & COMBINED);
|
return static_cast<QueryType>(tokensMask & COMBINED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LooqsQuery::hasContentSearch()
|
||||||
|
{
|
||||||
|
return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LooqsQuery::hasPathSearch()
|
||||||
|
{
|
||||||
|
return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH;
|
||||||
|
}
|
||||||
|
|
||||||
void LooqsQuery::addSortCondition(SortCondition sc)
|
void LooqsQuery::addSortCondition(SortCondition sc)
|
||||||
{
|
{
|
||||||
this->sortConditions.append(sc);
|
this->sortConditions.append(sc);
|
||||||
@ -128,7 +138,7 @@ QVector<SortCondition> createSortConditions(QString sortExpression)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw LooqsGeneralException("Unknown order specifier: " + order);
|
throw LooqsGeneralException("Unknown order specifier: " + orderstr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -157,15 +167,15 @@ void LooqsQuery::addToken(Token t)
|
|||||||
* thus, "Downloads zip" becomes essentailly "path.contains:(Downloads) AND path.contains:(zip)"
|
* thus, "Downloads zip" becomes essentailly "path.contains:(Downloads) AND path.contains:(zip)"
|
||||||
*
|
*
|
||||||
* TODO: It's a bit ugly still*/
|
* TODO: It's a bit ugly still*/
|
||||||
LooqsQuery LooqsQuery::build(QString expression)
|
LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, bool mergeLoneWords)
|
||||||
{
|
{
|
||||||
if(!checkParanthesis(expression))
|
if(!checkParanthesis(expression))
|
||||||
{
|
{
|
||||||
throw LooqsGeneralException("Invalid paranthesis");
|
throw LooqsGeneralException("Invalid paranthesis");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QStringList loneWords;
|
||||||
LooqsQuery result;
|
LooqsQuery result;
|
||||||
// TODO: merge lonewords
|
|
||||||
QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([\\w,])+)|(?<boolean>AND|OR)"
|
QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([\\w,])+)|(?<boolean>AND|OR)"
|
||||||
"|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>\\w+))");
|
"|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>\\w+))");
|
||||||
QRegularExpressionMatchIterator i = rx.globalMatch(expression);
|
QRegularExpressionMatchIterator i = rx.globalMatch(expression);
|
||||||
@ -233,7 +243,14 @@ LooqsQuery LooqsQuery::build(QString expression)
|
|||||||
|
|
||||||
if(loneword != "")
|
if(loneword != "")
|
||||||
{
|
{
|
||||||
result.addToken(Token(FILTER_PATH_CONTAINS, loneword));
|
if(mergeLoneWords)
|
||||||
|
{
|
||||||
|
loneWords.append(loneword);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result.addToken(Token(loneWordsTokenType, loneword));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(filtername != "")
|
if(filtername != "")
|
||||||
@ -244,6 +261,10 @@ LooqsQuery LooqsQuery::build(QString expression)
|
|||||||
{
|
{
|
||||||
value = m.captured("args");
|
value = m.captured("args");
|
||||||
}
|
}
|
||||||
|
if(value == "")
|
||||||
|
{
|
||||||
|
throw LooqsGeneralException("value cannot be empty for filters");
|
||||||
|
}
|
||||||
|
|
||||||
if(filtername == "path.contains")
|
if(filtername == "path.contains")
|
||||||
{
|
{
|
||||||
@ -292,7 +313,16 @@ LooqsQuery LooqsQuery::build(QString expression)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool contentsearch = (result.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
|
if(mergeLoneWords)
|
||||||
|
{
|
||||||
|
QString mergedLoneWords = loneWords.join(' ');
|
||||||
|
if(!mergedLoneWords.isEmpty())
|
||||||
|
{
|
||||||
|
result.addToken(Token(loneWordsTokenType, mergedLoneWords));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contentsearch = result.hasContentSearch();
|
||||||
bool sortsForContent = std::any_of(result.sortConditions.begin(), result.sortConditions.end(),
|
bool sortsForContent = std::any_of(result.sortConditions.begin(), result.sortConditions.end(),
|
||||||
[](SortCondition c) { return c.field == CONTENT_TEXT; });
|
[](SortCondition c) { return c.field == CONTENT_TEXT; });
|
||||||
|
|
||||||
|
@ -52,9 +52,12 @@ class LooqsQuery
|
|||||||
{
|
{
|
||||||
return tokensMask;
|
return tokensMask;
|
||||||
}
|
}
|
||||||
|
bool hasContentSearch();
|
||||||
|
bool hasPathSearch();
|
||||||
|
|
||||||
void addSortCondition(SortCondition sc);
|
void addSortCondition(SortCondition sc);
|
||||||
static bool checkParanthesis(QString query);
|
static bool checkParanthesis(QString query);
|
||||||
static LooqsQuery build(QString query);
|
static LooqsQuery build(QString query, TokenType loneWordsTokenType, bool mergeLoneWords);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // LOOQSQUERY_H
|
#endif // LOOQSQUERY_H
|
||||||
|
5
shared/migrations/migrations.qrc
Normal file
5
shared/migrations/migrations.qrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<RCC>
|
||||||
|
<qresource prefix="/looqs-migrations">
|
||||||
|
<file>1.sql</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
13
shared/pagedata.cpp
Normal file
13
shared/pagedata.cpp
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#include "pagedata.h"
|
||||||
|
|
||||||
|
QDataStream &operator<<(QDataStream &out, const PageData &pd)
|
||||||
|
{
|
||||||
|
out << pd.pagenumber << pd.content;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream &operator>>(QDataStream &in, PageData &pd)
|
||||||
|
{
|
||||||
|
in >> pd.pagenumber >> pd.content;
|
||||||
|
return in;
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
#ifndef PAGEDATA_H
|
#ifndef PAGEDATA_H
|
||||||
#define PAGEDATA_H
|
#define PAGEDATA_H
|
||||||
#include <QString>
|
#include <QString>
|
||||||
|
#include <QMetaType>
|
||||||
|
#include <QDataStream>
|
||||||
|
|
||||||
class PageData
|
class PageData
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -10,10 +13,17 @@ class PageData
|
|||||||
PageData()
|
PageData()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
PageData(unsigned int pagenumber, QString content)
|
PageData(unsigned int pagenumber, QString content)
|
||||||
{
|
{
|
||||||
this->pagenumber = pagenumber;
|
this->pagenumber = pagenumber;
|
||||||
this->content = content;
|
this->content = content;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(PageData);
|
||||||
|
|
||||||
|
QDataStream &operator<<(QDataStream &out, const PageData &pd);
|
||||||
|
QDataStream &operator>>(QDataStream &in, PageData &pd);
|
||||||
|
|
||||||
#endif // PAGEDATA_H
|
#endif // PAGEDATA_H
|
104
shared/paralleldirscanner.cpp
Normal file
104
shared/paralleldirscanner.cpp
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
#include "paralleldirscanner.h"
|
||||||
|
|
||||||
|
#include <QRunnable>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <QDirIterator>
|
||||||
|
#include <QThread>
|
||||||
|
#include <QThreadPool>
|
||||||
|
#include <functional>
|
||||||
|
#include "dirscanworker.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
ParallelDirScanner::ParallelDirScanner()
|
||||||
|
{
|
||||||
|
this->threadpool.setMaxThreadCount(QThread::idealThreadCount() / 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConcurrentQueue<QString> &ParallelDirScanner::getResults()
|
||||||
|
{
|
||||||
|
return this->resultPathsQueue;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParallelDirScanner::setIgnorePatterns(QStringList patterns)
|
||||||
|
{
|
||||||
|
this->ignorePatterns = patterns;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParallelDirScanner::setPaths(QVector<QString> paths)
|
||||||
|
{
|
||||||
|
this->paths = paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParallelDirScanner::cancel()
|
||||||
|
{
|
||||||
|
this->stopToken.store(true, std::memory_order_seq_cst);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParallelDirScanner::handleWorkersProgress(unsigned int progress)
|
||||||
|
{
|
||||||
|
this->processedPaths += progress;
|
||||||
|
if(!this->stopToken.load(std::memory_order_seq_cst))
|
||||||
|
emit this->progress(progress, this->processedPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
{
|
||||||
|
running = false;
|
||||||
|
emit scanComplete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ParallelDirScanner::getThreadsNum() const
|
||||||
|
{
|
||||||
|
int threadsNum = this->threadpool.maxThreadCount();
|
||||||
|
if(threadsNum > this->paths.size())
|
||||||
|
{
|
||||||
|
threadsNum = this->paths.size();
|
||||||
|
}
|
||||||
|
return threadsNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ParallelDirScanner::scan()
|
||||||
|
{
|
||||||
|
Logger::info() << "I am scanning";
|
||||||
|
this->stopToken.store(false, std::memory_order_relaxed);
|
||||||
|
this->finishedWorkers = 0;
|
||||||
|
this->processedPaths = 0;
|
||||||
|
this->targetPathsQueue.clear();
|
||||||
|
this->resultPathsQueue.clear();
|
||||||
|
|
||||||
|
this->targetPathsQueue.enqueue(this->paths);
|
||||||
|
int threadsNum = getThreadsNum();
|
||||||
|
if(threadsNum == 0)
|
||||||
|
{
|
||||||
|
emit scanComplete();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
running = true;
|
||||||
|
for(int i = 0; i < threadsNum; i++)
|
||||||
|
{
|
||||||
|
DirScanWorker *runnable = new DirScanWorker(this->targetPathsQueue, this->resultPathsQueue,
|
||||||
|
this->ignorePatterns, 1000, this->stopToken);
|
||||||
|
runnable->setAutoDelete(false);
|
||||||
|
connect(runnable, &DirScanWorker::progress, this, &ParallelDirScanner::handleWorkersProgress,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
connect(runnable, &DirScanWorker::finished, this, &ParallelDirScanner::handleWorkersFinish,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
threadpool.start(runnable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ParallelDirScanner::isRunning()
|
||||||
|
{
|
||||||
|
return this->running;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int ParallelDirScanner::pathCount()
|
||||||
|
{
|
||||||
|
return this->processedPaths;
|
||||||
|
}
|
48
shared/paralleldirscanner.h
Normal file
48
shared/paralleldirscanner.h
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#ifndef PARALLELDIRSCANNER_H
|
||||||
|
#define PARALLELDIRSCANNER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QMutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <QThreadPool>
|
||||||
|
#include "concurrentqueue.h"
|
||||||
|
class ParallelDirScanner : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
protected:
|
||||||
|
QStringList ignorePatterns;
|
||||||
|
QThreadPool threadpool;
|
||||||
|
|
||||||
|
unsigned int finishedWorkers = 0;
|
||||||
|
unsigned int processedPaths = 0;
|
||||||
|
|
||||||
|
std::atomic<bool> stopToken;
|
||||||
|
|
||||||
|
bool running = false;
|
||||||
|
|
||||||
|
QVector<QString> paths;
|
||||||
|
ConcurrentQueue<QString> targetPathsQueue;
|
||||||
|
ConcurrentQueue<QString> resultPathsQueue;
|
||||||
|
unsigned int getThreadsNum() const;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ParallelDirScanner();
|
||||||
|
|
||||||
|
ConcurrentQueue<QString> &getResults();
|
||||||
|
void setIgnorePatterns(QStringList patterns);
|
||||||
|
void setPaths(QVector<QString> paths);
|
||||||
|
void scan();
|
||||||
|
bool isRunning();
|
||||||
|
|
||||||
|
unsigned int pathCount();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void scanComplete();
|
||||||
|
void progress(int, int);
|
||||||
|
public slots:
|
||||||
|
void cancel();
|
||||||
|
void handleWorkersProgress(unsigned int progress);
|
||||||
|
void handleWorkersFinish();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // PARALLELDIRSCANNER_H
|
@ -10,6 +10,8 @@ enum DataSource
|
|||||||
ARRAY
|
ARRAY
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define NOTHING_PROCESSED 4
|
||||||
|
|
||||||
class Processor
|
class Processor
|
||||||
{
|
{
|
||||||
public:
|
public:
|
111
shared/sandboxedprocessor.cpp
Normal file
111
shared/sandboxedprocessor.cpp
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#include <QFile>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDataStream>
|
||||||
|
#include "sandboxedprocessor.h"
|
||||||
|
#include "pdfprocessor.h"
|
||||||
|
#include "defaulttextprocessor.h"
|
||||||
|
#include "tagstripperprocessor.h"
|
||||||
|
#include "nothingprocessor.h"
|
||||||
|
#include "odtprocessor.h"
|
||||||
|
#include "odsprocessor.h"
|
||||||
|
#include "../submodules/exile.h/exile.h"
|
||||||
|
#include "logger.h"
|
||||||
|
|
||||||
|
static DefaultTextProcessor *defaultTextProcessor = new DefaultTextProcessor();
|
||||||
|
static TagStripperProcessor *tagStripperProcessor = new TagStripperProcessor();
|
||||||
|
static NothingProcessor *nothingProcessor = new NothingProcessor();
|
||||||
|
static OdtProcessor *odtProcessor = new OdtProcessor();
|
||||||
|
static OdsProcessor *odsProcessor = new OdsProcessor();
|
||||||
|
|
||||||
|
static QMap<QString, Processor *> processors{
|
||||||
|
{"pdf", new PdfProcessor()}, {"txt", defaultTextProcessor}, {"md", defaultTextProcessor},
|
||||||
|
{"py", defaultTextProcessor}, {"xml", nothingProcessor}, {"html", tagStripperProcessor},
|
||||||
|
{"java", defaultTextProcessor}, {"js", defaultTextProcessor}, {"cpp", defaultTextProcessor},
|
||||||
|
{"c", defaultTextProcessor}, {"sql", defaultTextProcessor}, {"odt", odtProcessor},
|
||||||
|
{"ods", odsProcessor}};
|
||||||
|
|
||||||
|
void SandboxedProcessor::enableSandbox(QString readablePath)
|
||||||
|
{
|
||||||
|
struct exile_policy *policy = exile_init_policy();
|
||||||
|
if(policy == NULL)
|
||||||
|
{
|
||||||
|
qCritical() << "Could not init exile";
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
policy->namespace_options = EXILE_UNSHARE_NETWORK | EXILE_UNSHARE_USER;
|
||||||
|
|
||||||
|
if(!readablePath.isEmpty())
|
||||||
|
{
|
||||||
|
std::string readablePathLocation = readablePath.toStdString();
|
||||||
|
if(exile_append_path_policies(policy, EXILE_FS_ALLOW_ALL_READ, readablePathLocation.c_str()) != 0)
|
||||||
|
{
|
||||||
|
qCritical() << "Failed to add path policies";
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
policy->no_fs = 1;
|
||||||
|
}
|
||||||
|
int ret = exile_enable_policy(policy);
|
||||||
|
if(ret != 0)
|
||||||
|
{
|
||||||
|
qDebug() << "Failed to establish sandbox: " << ret;
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
exile_free_policy(policy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SandboxedProcessor::printResults(const QVector<PageData> &pageData)
|
||||||
|
{
|
||||||
|
QFile fsstdout;
|
||||||
|
fsstdout.open(stdout, QIODevice::WriteOnly);
|
||||||
|
QDataStream stream(&fsstdout);
|
||||||
|
|
||||||
|
for(const PageData &data : pageData)
|
||||||
|
{
|
||||||
|
stream << data;
|
||||||
|
// fsstdout.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
fsstdout.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
int SandboxedProcessor::process()
|
||||||
|
{
|
||||||
|
QFileInfo fileInfo(this->filePath);
|
||||||
|
Processor *processor = processors.value(fileInfo.suffix(), nothingProcessor);
|
||||||
|
|
||||||
|
if(processor == nothingProcessor)
|
||||||
|
{
|
||||||
|
/* Nothing to do */
|
||||||
|
return NOTHING_PROCESSED;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVector<PageData> pageData;
|
||||||
|
QString absPath = fileInfo.absoluteFilePath();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if(processor->PREFERED_DATA_SOURCE == FILEPATH)
|
||||||
|
{
|
||||||
|
/* Read access to FS needed... doh..*/
|
||||||
|
enableSandbox(absPath);
|
||||||
|
pageData = processor->process(absPath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QByteArray data = Utils::readFile(absPath);
|
||||||
|
enableSandbox();
|
||||||
|
pageData = processor->process(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(LooqsGeneralException &e)
|
||||||
|
{
|
||||||
|
Logger::error() << "SandboxedProcessor: Error while processing" << absPath << ":" << e.message << Qt::endl;
|
||||||
|
return 3 /* PROCESSFAIL */;
|
||||||
|
}
|
||||||
|
|
||||||
|
printResults(pageData);
|
||||||
|
return 0;
|
||||||
|
}
|
23
shared/sandboxedprocessor.h
Normal file
23
shared/sandboxedprocessor.h
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#ifndef SANDBOXEDPROCESSOR_H
|
||||||
|
#define SANDBOXEDPROCESSOR_H
|
||||||
|
#include <QString>
|
||||||
|
#include "pagedata.h"
|
||||||
|
|
||||||
|
class SandboxedProcessor
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
QString filePath;
|
||||||
|
|
||||||
|
void enableSandbox(QString readablePath = "");
|
||||||
|
void printResults(const QVector<PageData> &pageData);
|
||||||
|
|
||||||
|
public:
|
||||||
|
SandboxedProcessor(QString filepath)
|
||||||
|
{
|
||||||
|
this->filePath = filepath;
|
||||||
|
}
|
||||||
|
|
||||||
|
int process();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // SANDBOXEDPROCESSOR_H
|
@ -4,15 +4,18 @@
|
|||||||
#
|
#
|
||||||
#-------------------------------------------------
|
#-------------------------------------------------
|
||||||
|
|
||||||
QT += sql
|
|
||||||
|
|
||||||
QT -= gui
|
QT -= gui
|
||||||
|
QT += sql concurrent
|
||||||
|
|
||||||
TARGET = shared
|
TARGET = shared
|
||||||
TEMPLATE = lib
|
TEMPLATE = lib
|
||||||
CONFIG += staticlib
|
CONFIG += staticlib
|
||||||
CONFIG += c++17
|
CONFIG += c++17
|
||||||
|
|
||||||
|
INCLUDEPATH += $$PWD/../sandbox/exile.h/
|
||||||
|
INCLUDEPATH += /usr/include/poppler/qt5/ /usr/include/quazip5
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
# any feature of Qt which has been marked as deprecated (the exact warnings
|
# any feature of Qt which has been marked as deprecated (the exact warnings
|
||||||
# depend on your compiler). Please consult the documentation of the
|
# depend on your compiler). Please consult the documentation of the
|
||||||
@ -25,19 +28,62 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
|||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
SOURCES += sqlitesearch.cpp \
|
SOURCES += sqlitesearch.cpp \
|
||||||
|
concurrentqueue.cpp \
|
||||||
|
databasefactory.cpp \
|
||||||
|
dbmigrator.cpp \
|
||||||
|
defaulttextprocessor.cpp \
|
||||||
|
dirscanworker.cpp \
|
||||||
|
encodingdetector.cpp \
|
||||||
|
filesaver.cpp \
|
||||||
|
filescanworker.cpp \
|
||||||
|
indexer.cpp \
|
||||||
|
logger.cpp \
|
||||||
looqsgeneralexception.cpp \
|
looqsgeneralexception.cpp \
|
||||||
common.cpp \
|
common.cpp \
|
||||||
looqsquery.cpp
|
looqsquery.cpp \
|
||||||
|
nothingprocessor.cpp \
|
||||||
|
odsprocessor.cpp \
|
||||||
|
odtprocessor.cpp \
|
||||||
|
pagedata.cpp \
|
||||||
|
paralleldirscanner.cpp \
|
||||||
|
pdfprocessor.cpp \
|
||||||
|
processor.cpp \
|
||||||
|
sandboxedprocessor.cpp \
|
||||||
|
sqlitedbservice.cpp \
|
||||||
|
tagstripperprocessor.cpp \
|
||||||
|
utils.cpp \
|
||||||
|
../submodules/exile.h/exile.c
|
||||||
|
|
||||||
HEADERS += sqlitesearch.h \
|
HEADERS += sqlitesearch.h \
|
||||||
|
concurrentqueue.h \
|
||||||
|
databasefactory.h \
|
||||||
|
dbmigrator.h \
|
||||||
|
defaulttextprocessor.h \
|
||||||
|
dirscanworker.h \
|
||||||
|
encodingdetector.h \
|
||||||
filedata.h \
|
filedata.h \
|
||||||
|
filesaver.h \
|
||||||
|
filescanworker.h \
|
||||||
|
indexer.h \
|
||||||
|
logger.h \
|
||||||
looqsgeneralexception.h \
|
looqsgeneralexception.h \
|
||||||
looqsquery.h \
|
looqsquery.h \
|
||||||
|
nothingprocessor.h \
|
||||||
|
odsprocessor.h \
|
||||||
|
odtprocessor.h \
|
||||||
|
pagedata.h \
|
||||||
|
paralleldirscanner.h \
|
||||||
|
pdfprocessor.h \
|
||||||
|
processor.h \
|
||||||
|
sandboxedprocessor.h \
|
||||||
searchresult.h \
|
searchresult.h \
|
||||||
|
sqlitedbservice.h \
|
||||||
|
tagstripperprocessor.h \
|
||||||
token.h \
|
token.h \
|
||||||
common.h
|
common.h \
|
||||||
|
utils.h
|
||||||
unix {
|
unix {
|
||||||
target.path = /usr/lib
|
target.path = /usr/lib
|
||||||
INSTALLS += target
|
INSTALLS += target
|
||||||
}
|
}
|
||||||
RESOURCES = create.qrc
|
RESOURCES = migrations/migrations.qrc
|
||||||
|
@ -7,7 +7,8 @@
|
|||||||
#include "logger.h"
|
#include "logger.h"
|
||||||
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime)
|
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime)
|
||||||
{
|
{
|
||||||
auto query = QSqlQuery("SELECT 1 FROM file WHERE path = ? and mtime = ?", dbFactory->forCurrentThread());
|
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
||||||
|
query.prepare("SELECT 1 FROM file WHERE path = ? and mtime = ?");
|
||||||
query.addBindValue(path);
|
query.addBindValue(path);
|
||||||
query.addBindValue(mtime);
|
query.addBindValue(mtime);
|
||||||
if(!query.exec())
|
if(!query.exec())
|
@ -12,7 +12,8 @@ enum SaveFileResult
|
|||||||
OK,
|
OK,
|
||||||
SKIPPED,
|
SKIPPED,
|
||||||
DBFAIL,
|
DBFAIL,
|
||||||
PROCESSFAIL
|
PROCESSFAIL,
|
||||||
|
NOTFOUND
|
||||||
};
|
};
|
||||||
|
|
||||||
class SqliteDbService
|
class SqliteDbService
|
@ -133,11 +133,17 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
|
|||||||
throw LooqsGeneralException("Nothing to search for supplied");
|
throw LooqsGeneralException("Nothing to search for supplied");
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const Token &token : query.getTokens())
|
bool ftsAlreadyJoined = false;
|
||||||
|
auto tokens = query.getTokens();
|
||||||
|
for(const Token &token : tokens)
|
||||||
{
|
{
|
||||||
if(token.type == FILTER_CONTENT_CONTAINS)
|
if(token.type == FILTER_CONTENT_CONTAINS)
|
||||||
|
{
|
||||||
|
if(!ftsAlreadyJoined)
|
||||||
{
|
{
|
||||||
joinSql += " INNER JOIN content_fts ON content.id = content_fts.ROWID ";
|
joinSql += " INNER JOIN content_fts ON content.id = content_fts.ROWID ";
|
||||||
|
ftsAlreadyJoined = true;
|
||||||
|
}
|
||||||
whereSql += " content_fts.content MATCH ? ";
|
whereSql += " content_fts.content MATCH ? ";
|
||||||
bindValues.append(token.value);
|
bindValues.append(token.value);
|
||||||
}
|
}
|
||||||
@ -154,9 +160,13 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
|
|||||||
if(isContentSearch)
|
if(isContentSearch)
|
||||||
{
|
{
|
||||||
if(sortSql.isEmpty())
|
if(sortSql.isEmpty())
|
||||||
|
{
|
||||||
|
if(std::find_if(tokens.begin(), tokens.end(),
|
||||||
|
[](const Token &t) -> bool { return t.type == FILTER_CONTENT_CONTAINS; }) != tokens.end())
|
||||||
{
|
{
|
||||||
sortSql = "ORDER BY rank";
|
sortSql = "ORDER BY rank";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
prepSql =
|
prepSql =
|
||||||
"SELECT file.path AS path, group_concat(content.page) AS pages, file.mtime AS mtime, file.size AS size, "
|
"SELECT file.path AS path, group_concat(content.page) AS pages, file.mtime AS mtime, file.size AS size, "
|
||||||
"file.filetype AS filetype FROM file INNER JOIN content ON file.id = content.fileid " +
|
"file.filetype AS filetype FROM file INNER JOIN content ON file.id = content.fileid " +
|
||||||
|
1
submodules/exile.h
Submodule
1
submodules/exile.h
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit ea66ef76ebb88a43ac25c9a86f8fcab8efa130b2
|
Loading…
Reference in New Issue
Block a user