diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..3ed67ad --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/exile.h"] + path = submodules/exile.h + url = https://gitea.quitesimple.org/crtxcr/exile.h diff --git a/README.md b/README.md index bb76fb0..2822eb2 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,12 @@ Currently, this allows you search all indexed pdfs and take a look at the pages Coming soon™ -## Goals +## 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. * **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. - * **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 - + * **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. ## Build ### Ubuntu 21.04 diff --git a/cli/cli.pro b/cli/cli.pro index 9c985c7..2430ed3 100644 --- a/cli/cli.pro +++ b/cli/cli.pro @@ -18,10 +18,12 @@ LIBS += -luchardet -lpoppler-qt5 -lquazip5 SOURCES += \ main.cpp \ encodingdetector.cpp \ + pagedata.cpp \ processor.cpp \ pdfprocessor.cpp \ defaulttextprocessor.cpp \ commandadd.cpp \ + sandboxedprocessor.cpp \ tagstripperprocessor.cpp \ nothingprocessor.cpp \ odtprocessor.cpp \ @@ -44,6 +46,7 @@ HEADERS += \ defaulttextprocessor.h \ command.h \ commandadd.h \ + sandboxedprocessor.h \ tagstripperprocessor.h \ nothingprocessor.h \ odtprocessor.h \ diff --git a/cli/commandlist.cpp b/cli/commandlist.cpp index 31e0ff6..fa1fbd2 100644 --- a/cli/commandlist.cpp +++ b/cli/commandlist.cpp @@ -24,7 +24,7 @@ int CommandList::handle(QStringList arguments) QStringList files = parser.positionalArguments(); 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) { diff --git a/cli/commandsearch.cpp b/cli/commandsearch.cpp index 4350171..e7ed114 100644 --- a/cli/commandsearch.cpp +++ b/cli/commandsearch.cpp @@ -16,7 +16,7 @@ int CommandSearch::handle(QStringList arguments) QStringList files = parser.positionalArguments(); QString queryStrings = files.join(' '); - LooqsQuery query = LooqsQuery::build(queryStrings); + LooqsQuery query = LooqsQuery::build(queryStrings, TokenType::FILTER_PATH_CONTAINS, false); bool reverse = parser.isSet("reverse"); if(reverse) { diff --git a/cli/filesaver.cpp b/cli/filesaver.cpp index 2ae1c14..a0b2d94 100644 --- a/cli/filesaver.cpp +++ b/cli/filesaver.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "filesaver.h" #include "processor.h" @@ -13,18 +14,6 @@ #include "odsprocessor.h" #include "utils.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 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) { @@ -106,32 +95,47 @@ int FileSaver::processFiles(const QVector paths, std::function pageData; QString absPath = fileInfo.absoluteFilePath(); + int status = -1; 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 = processor->process(absPath); - } - else - { - pageData = processor->process(Utils::readFile(absPath)); - } + PageData pd; + in >> pd; + pageData.append(pd); } - catch(LooqsGeneralException &e) + status = process.exitCode(); + if(status != 0) { - Logger::error() << "Error while processing" << absPath << ":" << e.message << Qt::endl; + Logger::error() << "Error while processing" << absPath << ":" + << "Exit code " << status << Qt::endl; + return PROCESSFAIL; } } // 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; } diff --git a/cli/main.cpp b/cli/main.cpp index a490269..9439b1d 100644 --- a/cli/main.cpp +++ b/cli/main.cpp @@ -24,6 +24,7 @@ #include "commandsearch.h" #include "databasefactory.h" #include "logger.h" +#include "sandboxedprocessor.h" #include "../shared/common.h" void printUsage(QString argv0) @@ -59,6 +60,7 @@ int main(int argc, char *argv[]) QCoreApplication app(argc, argv); QStringList args = app.arguments(); QString argv0 = args.takeFirst(); + if(args.length() < 1) { printUsage(argv0); @@ -74,11 +76,24 @@ int main(int argc, char *argv[]) Logger::error() << "Error: " << e.message; return 1; } + qRegisterMetaType(); QString connectionString = Common::databasePath(); DatabaseFactory dbFactory(connectionString); SqliteDbService dbService(dbFactory); 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); if(cmd != nullptr) { diff --git a/cli/pagedata.cpp b/cli/pagedata.cpp new file mode 100644 index 0000000..6d7a19e --- /dev/null +++ b/cli/pagedata.cpp @@ -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; +} diff --git a/cli/pagedata.h b/cli/pagedata.h index d550116..8697d56 100644 --- a/cli/pagedata.h +++ b/cli/pagedata.h @@ -1,6 +1,9 @@ #ifndef PAGEDATA_H #define PAGEDATA_H #include +#include +#include + class PageData { public: @@ -10,10 +13,17 @@ class PageData PageData() { } + PageData(unsigned int pagenumber, QString content) { this->pagenumber = pagenumber; this->content = content; } }; + +Q_DECLARE_METATYPE(PageData); + +QDataStream &operator<<(QDataStream &out, const PageData &pd); +QDataStream &operator>>(QDataStream &in, PageData &pd); + #endif // PAGEDATA_H diff --git a/cli/processor.h b/cli/processor.h index 04bd04a..82f1c27 100644 --- a/cli/processor.h +++ b/cli/processor.h @@ -10,6 +10,8 @@ enum DataSource ARRAY }; +#define NOTHING_PROCESSED 4 + class Processor { public: diff --git a/cli/sandboxedprocessor.cpp b/cli/sandboxedprocessor.cpp new file mode 100644 index 0000000..3f722d9 --- /dev/null +++ b/cli/sandboxedprocessor.cpp @@ -0,0 +1,103 @@ +#include +#include +#include +#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 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(); + + policy->namespace_options = EXILE_UNSHARE_NETWORK | EXILE_UNSHARE_USER; + + if(!readablePath.isEmpty()) + { + std::string readablePathLocation = readablePath.toStdString(); + exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ, readablePathLocation.c_str()); + } + 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) +{ + 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; + 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() << "Error while processing" << absPath << ":" << e.message << Qt::endl; + return 3 /* PROCESSFAIL */; + } + + printResults(pageData); + return 0; +} diff --git a/cli/sandboxedprocessor.h b/cli/sandboxedprocessor.h new file mode 100644 index 0000000..416d2e0 --- /dev/null +++ b/cli/sandboxedprocessor.h @@ -0,0 +1,23 @@ +#ifndef SANDBOXEDPROCESSOR_H +#define SANDBOXEDPROCESSOR_H +#include +#include "pagedata.h" + +class SandboxedProcessor +{ + private: + QString filePath; + + void enableSandbox(QString readablePath = ""); + void printResults(const QVector &pageData); + + public: + SandboxedProcessor(QString filepath) + { + this->filePath = filepath; + } + + int process(); +}; + +#endif // SANDBOXEDPROCESSOR_H diff --git a/gui/gui.pro b/gui/gui.pro index 5417c1f..e97e05c 100644 --- a/gui/gui.pro +++ b/gui/gui.pro @@ -4,7 +4,7 @@ # #------------------------------------------------- -QT += core concurrent gui +QT += core concurrent gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++17 @@ -23,6 +23,8 @@ DEFINES += QT_DEPRECATED_WARNINGS #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ + ipcclient.cpp \ + ipcserver.cpp \ main.cpp \ mainwindow.cpp \ pdfworker.cpp \ @@ -30,6 +32,9 @@ SOURCES += \ clicklabel.cpp HEADERS += \ + ipc.h \ + ipcclient.h \ + ipcserver.h \ mainwindow.h \ pdfworker.h \ pdfpreview.h \ diff --git a/gui/ipc.h b/gui/ipc.h new file mode 100644 index 0000000..f1ef8a3 --- /dev/null +++ b/gui/ipc.h @@ -0,0 +1,9 @@ +#ifndef IPC_H +#define IPC_H + +enum IPCCommand +{ + DocOpen, + FileOpen +}; +#endif // IPC_H diff --git a/gui/ipcclient.cpp b/gui/ipcclient.cpp new file mode 100644 index 0000000..9f29f6b --- /dev/null +++ b/gui/ipcclient.cpp @@ -0,0 +1,27 @@ +#include +#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; +} diff --git a/gui/ipcclient.h b/gui/ipcclient.h new file mode 100644 index 0000000..cd3eb2c --- /dev/null +++ b/gui/ipcclient.h @@ -0,0 +1,18 @@ +#ifndef IPCCLIENT_H +#define IPCCLIENT_H +#include +#include +#include +#include "ipc.h" + +class IPCClient +{ + private: + QString socketPath; + + public: + IPCClient(QString socketPath); + bool sendCommand(IPCCommand command, QStringList args); +}; + +#endif // IPCCLIENT_H diff --git a/gui/ipcserver.cpp b/gui/ipcserver.cpp new file mode 100644 index 0000000..d7d8dc5 --- /dev/null +++ b/gui/ipcserver.cpp @@ -0,0 +1,88 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ipcserver.h" + +IpcServer::IpcServer() +{ + 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(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)); +} + +void IpcServer::spawnerNewConnection() +{ + QScopedPointer 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]); + } + } +} diff --git a/gui/ipcserver.h b/gui/ipcserver.h new file mode 100644 index 0000000..7f86a8b --- /dev/null +++ b/gui/ipcserver.h @@ -0,0 +1,21 @@ +#ifndef IPCSERVER_H +#define IPCSERVER_H +#include +#include +#include "ipc.h" +class IpcServer : public QObject +{ + Q_OBJECT + private: + QLocalServer spawningServer; + bool docOpen(QString path, int pagenum); + bool fileOpen(QString path); + private slots: + void spawnerNewConnection(); + + public: + IpcServer(); + bool startSpawner(QString socketPath); +}; + +#endif // IPCSERVER_H diff --git a/gui/main.cpp b/gui/main.cpp index df3ee28..9449d18 100644 --- a/gui/main.cpp +++ b/gui/main.cpp @@ -1,13 +1,88 @@ #include #include #include +#include +#include + #include "mainwindow.h" #include "searchresult.h" #include "pdfpreview.h" #include "../shared/common.h" +#include "../submodules/exile.h/exile.h" +#include "ipcserver.h" int main(int argc, char *argv[]) { + QString socketPath = "/tmp/looqs-spawner"; + if(argc > 1) + { + Common::setupAppInfo(); + QApplication a(argc, argv); + QString arg = argv[1]; + if(arg == "ipc") + { + IpcServer *ipcserver = new IpcServer(); + qDebug() << "Launching ipc"; + if(!ipcserver->startSpawner(socketPath)) + { + qCritical() << "Error failed to spawn"; + return 1; + } + qDebug() << "Launched"; + } + return a.exec(); + } + 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); + } + + struct exile_policy *policy = exile_init_policy(); + if(policy == NULL) + { + qCritical() << "Failed to init policy for sandbox"; + return 1; + } + std::string appDataLocation = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation).toStdString(); + std::string cacheDataLocation = QStandardPaths::writableLocation(QStandardPaths::CacheLocation).toStdString(); + std::string sockPath = socketPath.toStdString(); + policy->namespace_options = EXILE_UNSHARE_NETWORK | EXILE_UNSHARE_USER; + policy->vow_promises = EXILE_SYSCALL_VOW_THREAD | EXILE_SYSCALL_VOW_CPATH | EXILE_SYSCALL_VOW_WPATH | + EXILE_SYSCALL_VOW_RPATH | EXILE_SYSCALL_VOW_UNIX | EXILE_SYSCALL_VOW_STDIO | + EXILE_SYSCALL_VOW_PROT_EXEC | EXILE_SYSCALL_VOW_PROC | EXILE_SYSCALL_VOW_SHM | + EXILE_SYSCALL_VOW_FSNOTIFY | EXILE_SYSCALL_VOW_IOCTL; + + if(exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_REMOVE_FILE, "/") != 0) + { + qCritical() << "Failed to append a path to the path policy"; + return 1; + } + + if(exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, appDataLocation.c_str()) != + 0) + { + qCritical() << "Failed to append a path to the path policy"; + return 1; + } + if(exile_append_path_policy(policy, EXILE_FS_ALLOW_ALL_READ | EXILE_FS_ALLOW_ALL_WRITE, + cacheDataLocation.c_str()) != 0) + { + qCritical() << "Failed to append a path to the path policy"; + return 1; + } + int ret = exile_enable_policy(policy); + if(ret != 0) + { + qDebug() << "Failed to establish sandbox"; + return 1; + } + exile_free_policy(policy); + Common::setupAppInfo(); QApplication a(argc, argv); try @@ -23,7 +98,9 @@ int main(int argc, char *argv[]) qRegisterMetaType>("QVector"); qRegisterMetaType>("QVector"); qRegisterMetaType("PdfPreview"); - MainWindow w; + + IPCClient client{socketPath}; + MainWindow w{0, client}; w.showMaximized(); return a.exec(); diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 9f1487f..c55cbd4 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -18,10 +18,11 @@ #include "../shared/looqsgeneralexception.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); setWindowTitle(QCoreApplication::applicationName()); + this->ipcClient = &client; QSettings settings; db = QSqlDatabase::addDatabase("QSQLITE"); @@ -136,29 +137,7 @@ void MainWindow::pdfPreviewReceived(PdfPreview preview) 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); - } - } - else - { - QDesktopServices::openUrl(QUrl::fromLocalFile(docPath)); - } - }); + connect(label, &ClickLabel::leftClick, [this, docPath, previewPage]() { ipcDocOpen(docPath, previewPage); }); connect(label, &ClickLabel::rightClick, [this, docPath, previewPage]() { @@ -187,8 +166,13 @@ void MainWindow::lineEditReturnPressed() [&, q]() { SqliteSearch searcher(db); - this->currentQuery = LooqsQuery::build(q); - return searcher.search(this->currentQuery); + this->contentSearchQuery = LooqsQuery::build(q, TokenType::FILTER_CONTENT_CONTAINS, true); + + LooqsQuery filesQuery = LooqsQuery::build(q, TokenType::FILTER_PATH_CONTAINS, false); + QVector results; + results.append(searcher.search(filesQuery)); + results.append(searcher.search(this->contentSearchQuery)); + return results; }); searchWatcher.setFuture(searchFuture); } @@ -264,7 +248,7 @@ void MainWindow::makePdfPreview(int page) QVector wordsToHighlight; QRegularExpression extractor(R"#("([^"]*)"|(\w+))#"); - for(const Token &token : this->currentQuery.getTokens()) + for(const Token &token : this->contentSearchQuery.getTokens()) { if(token.type == FILTER_CONTENT_CONTAINS) { @@ -301,17 +285,27 @@ void MainWindow::createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo) [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); }); menu.addAction("Copy full path to clipboard", [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); }); - menu.addAction("Open containing folder", - [&fileInfo] - { - QString dir = fileInfo.absolutePath(); - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); - }); + menu.addAction("Open containing folder", [this, &fileInfo] { this->ipcFileOpen(fileInfo.absolutePath()); }); +} + +void MainWindow::ipcDocOpen(QString path, int num) +{ + 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) { - QDesktopServices::openUrl(QUrl::fromLocalFile(item->text(1))); + ipcFileOpen(item->text(1)); } void MainWindow::showSearchResultsContextMenu(const QPoint &point) diff --git a/gui/mainwindow.h b/gui/mainwindow.h index f6a3dd0..053274d 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -8,8 +8,10 @@ #include #include #include +#include #include "pdfworker.h" #include "../shared/looqsquery.h" +#include "ipcclient.h" namespace Ui { class MainWindow; @@ -20,7 +22,7 @@ class MainWindow : public QMainWindow Q_OBJECT public: - explicit MainWindow(QWidget *parent = 0); + explicit MainWindow(QWidget *parent, IPCClient &client); ~MainWindow(); signals: void beginSearch(const QString &query); @@ -28,6 +30,7 @@ class MainWindow : public QMainWindow private: Ui::MainWindow *ui; + IPCClient *ipcClient; QFileIconProvider iconProvider; bool pdfDirty; QSqlDatabase db; @@ -42,9 +45,12 @@ class MainWindow : public QMainWindow unsigned int processedPdfPreviews; void handleSearchResults(const QVector &results); void handleSearchError(QString error); - LooqsQuery currentQuery; + LooqsQuery contentSearchQuery; int pdfPreviewsPerPage; void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo); + void ipcDocOpen(QString path, int num); + void ipcFileOpen(QString path); + private slots: void lineEditReturnPressed(); void treeSearchItemActivated(QTreeWidgetItem *item, int i); diff --git a/gui/pdfworker.cpp b/gui/pdfworker.cpp index d4a683f..b300f21 100644 --- a/gui/pdfworker.cpp +++ b/gui/pdfworker.cpp @@ -103,6 +103,5 @@ QFuture PdfWorker::generatePreviews(const QVector path double scaleX = QGuiApplication::primaryScreen()->physicalDotsPerInchX() * scalefactor; double scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * scalefactor; - QSettings setting; return QtConcurrent::mapped(previews, Renderer(scaleX, scaleY, wordsToHighlight)); } diff --git a/shared/looqsquery.cpp b/shared/looqsquery.cpp index fdcded7..6cb4567 100644 --- a/shared/looqsquery.cpp +++ b/shared/looqsquery.cpp @@ -128,7 +128,7 @@ QVector createSortConditions(QString sortExpression) } else { - throw LooqsGeneralException("Unknown order specifier: " + order); + throw LooqsGeneralException("Unknown order specifier: " + orderstr); } } else @@ -157,15 +157,15 @@ void LooqsQuery::addToken(Token t) * thus, "Downloads zip" becomes essentailly "path.contains:(Downloads) AND path.contains:(zip)" * * TODO: It's a bit ugly still*/ -LooqsQuery LooqsQuery::build(QString expression) +LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, bool mergeLoneWords) { if(!checkParanthesis(expression)) { throw LooqsGeneralException("Invalid paranthesis"); } + QStringList loneWords; LooqsQuery result; - // TODO: merge lonewords QRegularExpression rx("((?(\\.|\\w)+):(?\\((?[^\\)]+)\\)|([\\w,])+)|(?AND|OR)" "|(?!)|(?\\(|\\))|(?\\w+))"); QRegularExpressionMatchIterator i = rx.globalMatch(expression); @@ -233,7 +233,14 @@ LooqsQuery LooqsQuery::build(QString expression) if(loneword != "") { - result.addToken(Token(FILTER_PATH_CONTAINS, loneword)); + if(mergeLoneWords) + { + loneWords.append(loneword); + } + else + { + result.addToken(Token(loneWordsTokenType, loneword)); + } } if(filtername != "") @@ -292,6 +299,11 @@ LooqsQuery LooqsQuery::build(QString expression) } } + if(mergeLoneWords) + { + result.addToken(Token(loneWordsTokenType, loneWords.join(' '))); + } + bool contentsearch = (result.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; bool sortsForContent = std::any_of(result.sortConditions.begin(), result.sortConditions.end(), [](SortCondition c) { return c.field == CONTENT_TEXT; }); diff --git a/shared/looqsquery.h b/shared/looqsquery.h index da6c9c7..c0a0e76 100644 --- a/shared/looqsquery.h +++ b/shared/looqsquery.h @@ -54,7 +54,7 @@ class LooqsQuery } void addSortCondition(SortCondition sc); static bool checkParanthesis(QString query); - static LooqsQuery build(QString query); + static LooqsQuery build(QString query, TokenType loneWordsTokenType, bool mergeLoneWords); }; #endif // LOOQSQUERY_H diff --git a/shared/shared.pro b/shared/shared.pro index 88072ea..fbbdd8b 100644 --- a/shared/shared.pro +++ b/shared/shared.pro @@ -13,6 +13,8 @@ TEMPLATE = lib CONFIG += staticlib CONFIG += c++17 +INCLUDEPATH += $$PWD/../sandbox/exile.h/ + # The following define makes your compiler emit warnings if you use # any feature of Qt which has been marked as deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the diff --git a/submodules/exile.h b/submodules/exile.h new file mode 160000 index 0000000..4824c6e --- /dev/null +++ b/submodules/exile.h @@ -0,0 +1 @@ +Subproject commit 4824c6eaa9043878daaba7b3778338f5bf913f06