4 커밋

작성자 SHA1 메시지 날짜
dbe4061c58 gui sandbox: Allow wpath to improve poppler text rendering
Apparently poppler or something needs open() with write
flags to render pdfs with proper fonts.

Landlock guards file system write access, so this is fine.
2022-12-06 11:59:26 +01:00
87e3cc3380 gui: PreviewGeneratorPdf: Enable Text hinting 2022-12-06 11:58:10 +01:00
ffdaa4c0c6 gui: mainwindow: Refactor to use new PreviewCoordinator 2022-12-06 11:57:34 +01:00
fe8da94951 gui: Introduce PreviewCoordinator
Move some preview generation logic to PreviewCoordinator
2022-12-06 11:55:16 +01:00
60개의 변경된 파일330개의 추가작업 그리고 1366개의 파일을 삭제

파일 보기

@ -1,24 +1,5 @@
# looqs: Release notes # looqs: Release notes
## 2023-05-07 - v0.9
Highlights: Tag support. Also begin new index mode to only index metadata (currently only path + file size, more to come).
Note: Upgrading can take some time as new column indexes will be added
CHANGES:
- gui: Improve font rendering in previews
- gui: Allow indexing only metadata
- gui: Allow adding content for files which only had metadata indexed before
- gui: Allow assigning tags by right clicking on paths
- cli: "add" command: Implement --verbose (-v)
- cli: "add" command: Implement --no-content and --fill-content
- cli: Add "tag" command which allows managing tags for paths.
- search: Add "tag:()", "t:()" filters
- Minor improvements and refactorings under the hood
- Add packages: Ubuntu 23.04.
## 2022-11-19 - v0.8.1 ## 2022-11-19 - v0.8.1
CHANGES: CHANGES:

파일 보기

@ -1,4 +1,4 @@
Copyright (c) 2018-2023: Albert Schwarzkopf <looqs at quitesimple period org> Copyright (c) 2018-2022: Albert Schwarzkopf <looqs at quitesimple period org>
looqs is made available under the following license: looqs is made available under the following license:

파일 보기

@ -28,7 +28,7 @@ There is no need to write the long form of filters. There are also booleans avai
The screenshots in this section may occasionally be slightly outdated, but they are usually recent enough to get an overall impression of the current state of the GUI. The screenshots in this section may occasionally be slightly outdated, but they are usually recent enough to get an overall impression of the current state of the GUI.
## Current status ## Current status
Latest version: 2023-05-07, v0.9 Latest version: 2022-11-19, v0.8.1
Please keep in mind: looqs is still at an early stage and may exhibit some weirdness and contain bugs. Please keep in mind: looqs is still at an early stage and may exhibit some weirdness and contain bugs.
@ -76,7 +76,7 @@ To build on Ubuntu and Debian, clone the repo and then run:
``` ```
git submodule init git submodule init
git submodule update git submodule update
sudo apt install build-essential qtbase5-dev libqt5sql5-sqlite 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
``` ```
@ -97,9 +97,7 @@ The GUI is located in `gui/looqs-gui`, the binary for the CLI is in `cli/looqs`
## Packages ## Packages
At this point, looqs is not in any official distro package repo, but I maintain some packages. At this point, looqs is not in any official distro package repo, but I maintain some packages.
### Ubuntu 22.04, 22.10
### Ubuntu 23.04, 22.10, 22.04
Latest release can be installed using apt from the repo. Latest release can be installed using apt from the repo.
``` ```
# First, obtain key, assume it's trusted. # First, obtain key, assume it's trusted.
@ -110,8 +108,6 @@ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] ht
sudo apt-get update sudo apt-get update
sudo apt-get install looqs sudo apt-get install looqs
``` ```
### Gentoo (EXPERIMENTAL)
Available in this overlay: https://github.com/quitesimpleorg/quitesimple-overlay
### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL) ### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL)
looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is
@ -138,7 +134,7 @@ An AppImage may accompany the tarball in the future.
### Other distros ### Other distros
I appreciate help for others distros. If you create a package, let me know! I'll probably add a package for voidlinux at some point and maybe will provide a Gentoo ebuild. However, I would appreciate help for others distros. If you create a package, let me know!
### Signature verification ### Signature verification

파일 보기

@ -165,8 +165,6 @@ A number of search filters are available.
| path.begins:(term) | pb:(term) | Filters path beginning with the specified term | | path.begins:(term) | pb:(term) | Filters path beginning with the specified term |
| contains:(terms) | c:(terms) | Full-text search, also understands quotes | | contains:(terms) | c:(terms) | Full-text search, also understands quotes |
| limit:(integer) | - | Limits the number of results. The default is 1000. Say "limit:0" to see all results | | limit:(integer) | - | Limits the number of results. The default is 1000. Say "limit:0" to see all results |
| tag:(tagname) | t:(tagname) | Filter for files that have been tagged with the corresponding tag |
Filters can be combined. The booleans AND and OR are supported. Negations can be applied too, except for c:(). Negations are specified with "!". Filters can be combined. The booleans AND and OR are supported. Negations can be applied too, except for c:(). Negations are specified with "!".
The AND boolean is implicit and thus entering it strictly optional. The AND boolean is implicit and thus entering it strictly optional.
@ -179,5 +177,11 @@ Examples:
|p:(notes) (pe:(odt) OR pe:(docx)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|Finds files such as notes.docx, notes.odt but also any .docs and .odt when the path contains the string 'notes'| |p:(notes) (pe:(odt) OR pe:(docx)) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|Finds files such as notes.docx, notes.odt but also any .docs and .odt when the path contains the string 'notes'|
|memcpy !(pe:(.c) OR pe:(.cpp))| Performs a FTS search for 'memcpy' but excludes .cpp and .c files.| |memcpy !(pe:(.c) OR pe:(.cpp))| Performs a FTS search for 'memcpy' but excludes .cpp and .c files.|
|c:("I think, therefore")|Performs a FTS search for the phrase "I think, therefore".| |c:("I think, therefore")|Performs a FTS search for the phrase "I think, therefore".|
|c:("invoice") Downloads|Equivalent to c:("invoice") p:("Downloads")| |c:("invoice") Downloads|This query is equivalent to c:("invoice") p:("Downloads")|
|p:(Downloads) invoice|Equivalent to c:("invoice") p:("Downloads")|

파일 보기

@ -15,7 +15,6 @@ DEFINES += QT_DEPRECATED_WARNINGS
# 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
SOURCES += \ SOURCES += \
commandtag.cpp \
main.cpp \ main.cpp \
commandadd.cpp \ commandadd.cpp \
commanddelete.cpp \ commanddelete.cpp \
@ -28,7 +27,6 @@ HEADERS += \
command.h \ command.h \
commandadd.h \ commandadd.h \
commanddelete.h \ commanddelete.h \
commandtag.h \
commandupdate.h \ commandupdate.h \
commandsearch.h \ commandsearch.h \
commandlist.h commandlist.h
@ -46,8 +44,6 @@ packagesExist(quazip1-qt5) {
} }
INCLUDEPATH += $$PWD/../shared INCLUDEPATH += $$PWD/../shared
INCLUDEPATH += /usr/include/poppler/qt5/
DEPENDPATH += $$PWD/../shared DEPENDPATH += $$PWD/../shared
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a

파일 보기

@ -2,6 +2,7 @@
#include <QThread> #include <QThread>
#include <QDebug> #include <QDebug>
#include "command.h" #include "command.h"
#include "looqsgeneralexception.h"
void Command::execute() void Command::execute()
{ {

파일 보기

@ -23,7 +23,7 @@ void CommandAdd::indexerFinished()
if(failedPathsCount > 0) if(failedPathsCount > 0)
{ {
Logger::info() << "Failed paths: " << Qt::endl; Logger::info() << "Failed paths: " << Qt::endl;
for(const QString &paths : result.failedPaths()) for(QString paths : result.failedPaths())
{ {
Logger::info() << paths << Qt::endl; Logger::info() << paths << Qt::endl;
} }
@ -41,36 +41,23 @@ int CommandAdd::handle(QStringList arguments)
{ {
QCommandLineParser parser; QCommandLineParser parser;
parser.addOptions({{{"c", "continue"}, parser.addOptions({{{"c", "continue"},
"Continue adding files, don't exit on first error. Exit code will be 0. If this option is not " "Continue adding files, don't exit on first error. If this option is not given, looqs will "
"given, looqs will " "exit asap, but it's possible that a few files will still be processed. "
"exit asap, but it's possible that a few files will still be processed."
"Set -t 1 to avoid this behavior, but processing will be slower. "}, "Set -t 1 to avoid this behavior, but processing will be slower. "},
{{"n", "no-content"}, "Only add paths to database. Do not index content"},
{{"v", "verbose"}, "Print paths of files being processed"},
{{"f", "fill-content"}, "Index content for files previously indexed with -n"},
{{"t", "threads"}, "Number of threads to use.", "threads"}}); {{"t", "threads"}, "Number of threads to use.", "threads"}});
parser.addHelpOption(); parser.addHelpOption();
parser.addPositionalArgument("add", "Add paths to the index", parser.addPositionalArgument("add", "Add paths to the index",
"add [paths...]. If no path is given, read from stdin, one path per line."); "add [paths...]. If no path is given, read from stdin, one path per line.");
parser.process(arguments); parser.process(arguments);
this->keepGoing = parser.isSet("continue"); this->keepGoing = parser.isSet("continue");
bool pathsOnly = parser.isSet("no-content");
bool fillContent = parser.isSet("fill-content");
bool verbose = parser.isSet("verbose");
if(parser.isSet("threads")) if(parser.isSet("threads"))
{ {
QString threadsCount = parser.value("threads"); QString threadsCount = parser.value("threads");
QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt()); QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt());
} }
if(pathsOnly && fillContent)
{
Logger::error() << "Invalid options: -n and -f cannot both be set";
return EXIT_FAILURE;
}
QStringList files = parser.positionalArguments(); QStringList files = parser.positionalArguments();
if(files.length() == 0) if(files.length() == 0)
@ -84,47 +71,15 @@ int CommandAdd::handle(QStringList arguments)
} }
} }
FileSaverOptions fileSaverOptions;
fileSaverOptions.keepGoing = keepGoing;
fileSaverOptions.fillExistingContentless = fillContent;
fileSaverOptions.metadataOnly = pathsOnly;
fileSaverOptions.verbose = verbose;
indexer = new Indexer(*this->dbService); indexer = new Indexer(*this->dbService);
indexer->setFileSaverOptions(fileSaverOptions);
indexer->setTargetPaths(files.toVector()); indexer->setTargetPaths(files.toVector());
indexer->setKeepGoing(keepGoing);
if(verbose)
{
indexer->setProgressReportThreshold(1);
}
connect(indexer, &Indexer::pathsCountChanged, this, connect(indexer, &Indexer::pathsCountChanged, this,
[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; }); [](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; });
connect(indexer, &Indexer::indexProgress, this, connect(indexer, &Indexer::indexProgress, this,
[verbose, this](int pathsCount, unsigned int /*added*/, unsigned int /*skipped*/, unsigned int /*failed*/, [](int pathsCount, unsigned int added, unsigned int skipped, unsigned int failed, unsigned int totalCount)
unsigned int /*totalCount*/) { Logger::info() << "Processed files: " << pathsCount << Qt::endl; });
{
Logger::info() << "Processed files: " << pathsCount << Qt::endl;
if(verbose)
{
IndexResult indexResult = indexer->getResult();
int newlyAdded = indexResult.results.count() - currentResult.results.count();
if(newlyAdded > 0)
{
int newOffset = indexResult.results.count() - newlyAdded;
for(int i = newOffset; i < indexResult.results.count(); i++)
{
auto result = indexResult.results.at(i);
Logger::info() << SaveFileResultToString(result.second) << result.first << Qt::endl;
}
}
this->currentResult = indexResult;
}
}
);
connect(indexer, &Indexer::finished, this, &CommandAdd::indexerFinished); connect(indexer, &Indexer::finished, this, &CommandAdd::indexerFinished);
this->autoFinish = false; this->autoFinish = false;

파일 보기

@ -13,8 +13,6 @@ class CommandAdd : public Command
bool keepGoing = true; bool keepGoing = true;
protected: protected:
IndexResult currentResult;
public: public:
using Command::Command; using Command::Command;

파일 보기

@ -1,5 +1,6 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include "commandlist.h" #include "commandlist.h"
#include "databasefactory.h"
#include "logger.h" #include "logger.h"
int CommandList::handle(QStringList arguments) int CommandList::handle(QStringList arguments)

파일 보기

@ -1,5 +1,6 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include "commandsearch.h" #include "commandsearch.h"
#include "databasefactory.h"
#include "logger.h" #include "logger.h"
int CommandSearch::handle(QStringList arguments) int CommandSearch::handle(QStringList arguments)

파일 보기

@ -1,153 +0,0 @@
#include <QCommandLineParser>
#include "commandtag.h"
#include "logger.h"
#include "tagmanager.h"
bool CommandTag::ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths)
{
for(const QString &path : paths)
{
QFileInfo info{path};
if(!info.exists())
{
Logger::error() << "Can't add tag for file " + info.absoluteFilePath() + " because it does not exist"
<< Qt::endl;
return false;
}
QString absolutePath = info.absoluteFilePath();
if(!this->dbService->fileExistsInDatabase(absolutePath))
{
Logger::error() << "Only files that have been indexed can be tagged. File not in index: " + absolutePath
<< Qt::endl;
return false;
}
absolutePaths.append(absolutePath);
}
return true;
}
int CommandTag::handle(QStringList arguments)
{
QCommandLineParser parser;
parser.addPositionalArgument("add", "Adds a tag to a file",
"add [tag] [paths...]. Adds the tag to the specified paths");
parser.addPositionalArgument("remove", "Removes a path associated to a tag", "remove [tag] [path]");
parser.addPositionalArgument("delete", "Deletes a tag", "delete [tag]");
parser.addPositionalArgument("list", "Lists paths associated with a tag, or all tags", "list [tag]");
parser.addPositionalArgument("show", "Lists tags associated with a path", "show [path]");
parser.addHelpOption();
parser.parse(arguments);
QStringList args = parser.positionalArguments();
if(args.length() == 0)
{
parser.showHelp(EXIT_FAILURE);
return EXIT_FAILURE;
}
TagManager tagManager{*this->dbService};
QString cmd = args[0];
if(cmd == "add")
{
if(args.length() < 3)
{
Logger::error() << "Not enough arguments provided. 'add' requires a tag followed by at least one path"
<< Qt::endl;
return EXIT_FAILURE;
}
QString tag = args[1];
QVector<QString> paths = args.mid(2).toVector();
QVector<QString> absolutePaths;
if(!ensureAbsolutePaths(paths, absolutePaths))
{
return EXIT_FAILURE;
}
bool result = tagManager.addPathsToTag(tag, absolutePaths);
if(!result)
{
Logger::error() << "Failed to assign tags" << Qt::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
if(cmd == "list")
{
QString tag;
if(args.length() >= 2)
{
tag = args[1];
}
QVector<QString> entries;
if(tag.isEmpty())
{
entries = tagManager.getTags();
}
else
{
entries = tagManager.getPaths(tag);
}
for(const QString &entry : entries)
{
Logger::info() << entry << Qt::endl;
}
}
if(cmd == "remove")
{
if(args.length() < 3)
{
Logger::error() << "Not enough arguments provided. 'remove' requires a tag followed by at least one path"
<< Qt::endl;
return EXIT_FAILURE;
}
QString tag = args[1];
QVector<QString> paths = args.mid(2).toVector();
QVector<QString> absolutePaths;
if(!ensureAbsolutePaths(paths, absolutePaths))
{
return EXIT_FAILURE;
}
if(!tagManager.removePathsForTag(tag, absolutePaths))
{
Logger::error() << "Failed to remove path assignments" << Qt::endl;
return EXIT_FAILURE;
}
}
if(cmd == "delete")
{
if(args.length() != 2)
{
Logger::error() << "The 'delete' command requires the tag to delete" << Qt::endl;
return EXIT_FAILURE;
}
if(!tagManager.deleteTag(args[1]))
{
Logger::error() << "Failed to delete tag" << Qt::endl;
return EXIT_FAILURE;
}
}
if(cmd == "show")
{
if(args.length() != 2)
{
Logger::error() << "The 'show' command requires a path to show the assigned tags" << Qt::endl;
return EXIT_FAILURE;
}
QString path = args[1];
QVector<QString> absolutePaths;
if(!ensureAbsolutePaths({path}, absolutePaths))
{
return EXIT_FAILURE;
}
QVector<QString> tags = tagManager.getTags(absolutePaths.at(0));
for(const QString &entry : tags)
{
Logger::info() << entry << Qt::endl;
}
}
return EXIT_SUCCESS;
}

파일 보기

@ -1,16 +0,0 @@
#ifndef COMMANDTAG_H
#define COMMANDTAG_H
#include "command.h"
class CommandTag : public Command
{
protected:
bool ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths);
public:
using Command::Command;
int handle(QStringList arguments) override;
};
#endif // COMMANDTAG_H

파일 보기

@ -38,13 +38,10 @@ int CommandUpdate::handle(QStringList arguments)
QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt()); QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt());
} }
bool hasErrors = false;
IndexSyncer *syncer = new IndexSyncer(*this->dbService); IndexSyncer *syncer = new IndexSyncer(*this->dbService);
syncer->setKeepGoing(keepGoing);
FileSaverOptions fileOptions; syncer->setVerbose(verbose);
fileOptions.keepGoing = keepGoing;
fileOptions.verbose = verbose;
syncer->setFileSaverOptions(fileOptions);
syncer->setPattern(pattern); syncer->setPattern(pattern);
syncer->setDryRun(dryRun); syncer->setDryRun(dryRun);
syncer->setRemoveDeletedFromIndex(deleteMissing); syncer->setRemoveDeletedFromIndex(deleteMissing);
@ -63,7 +60,7 @@ int CommandUpdate::handle(QStringList arguments)
/* TODO: updated not printed, handled be verbose in FileSaver, but this can be improved */ /* TODO: updated not printed, handled be verbose in FileSaver, but this can be improved */
} }
connect(syncer, &IndexSyncer::finished, this, connect(syncer, &IndexSyncer::finished, this,
[this, dryRun, keepGoing](unsigned int totalUpdated, unsigned int totalRemoved, unsigned int totalErrors) [&](unsigned int totalUpdated, unsigned int totalRemoved, unsigned int totalErrors)
{ {
Logger::info() << "Syncing finished" << Qt::endl; Logger::info() << "Syncing finished" << Qt::endl;
@ -75,7 +72,7 @@ int CommandUpdate::handle(QStringList arguments)
} }
int retval = 0; int retval = 0;
if(this->hasErrors && !keepGoing) if(hasErrors && !keepGoing)
{ {
retval = 1; retval = 1;
} }
@ -85,7 +82,7 @@ int CommandUpdate::handle(QStringList arguments)
[&](QString error) [&](QString error)
{ {
Logger::error() << error << Qt::endl; Logger::error() << error << Qt::endl;
this->hasErrors = true; hasErrors = true;
}); });
this->autoFinish = false; this->autoFinish = false;

파일 보기

@ -4,9 +4,6 @@
#include "filesaver.h" #include "filesaver.h"
class CommandUpdate : public Command class CommandUpdate : public Command
{ {
protected:
bool hasErrors = false;
public: public:
using Command::Command; using Command::Command;
int handle(QStringList arguments) override; int handle(QStringList arguments) override;

파일 보기

@ -21,7 +21,6 @@
#include "commandupdate.h" #include "commandupdate.h"
#include "commandsearch.h" #include "commandsearch.h"
#include "commandlist.h" #include "commandlist.h"
#include "commandtag.h"
#include "databasefactory.h" #include "databasefactory.h"
#include "logger.h" #include "logger.h"
#include "sandboxedprocessor.h" #include "sandboxedprocessor.h"
@ -32,7 +31,7 @@
void printUsage(QString argv0) void printUsage(QString argv0)
{ {
qInfo() << "Usage:" << argv0 << "command"; qInfo() << "Usage:" << argv0 << "command";
qInfo() << "Valid commands: add, update, search, delete, tag, list. Each command has a --help option."; qInfo() << "Valid commands: add, update, delete, search, list. Each command has a --help option.";
} }
Command *commandFromName(QString name, SqliteDbService &dbService) Command *commandFromName(QString name, SqliteDbService &dbService)
@ -57,10 +56,6 @@ Command *commandFromName(QString name, SqliteDbService &dbService)
{ {
return new CommandList(dbService); return new CommandList(dbService);
} }
if(name == "tag")
{
return new CommandTag(dbService);
}
return nullptr; return nullptr;
} }

파일 보기

@ -193,7 +193,7 @@ int main(int argc, char *argv[])
Logger::error() << error << Qt::endl; Logger::error() << error << Qt::endl;
QMessageBox::critical(nullptr, "Error during upgrade", QMessageBox::critical(nullptr, "Error during upgrade",
error); error);
exit(EXIT_FAILURE); qApp->quit();
} }
); );

파일 보기

@ -16,15 +16,13 @@
#include <QScreen> #include <QScreen>
#include <QProgressDialog> #include <QProgressDialog>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QWidgetAction>
#include <QInputDialog>
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include "clicklabel.h" #include "clicklabel.h"
#include "../shared/sqlitesearch.h" #include "../shared/sqlitesearch.h"
#include "../shared/looqsgeneralexception.h" #include "../shared/looqsgeneralexception.h"
#include "../shared/common.h" #include "../shared/common.h"
#include "previewgenerator.h"
#include "aboutdialog.h" #include "aboutdialog.h"
MainWindow::MainWindow(QWidget *parent, QString socketPath) MainWindow::MainWindow(QWidget *parent, QString socketPath)
@ -45,9 +43,6 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
indexer = new Indexer(*(this->dbService)); indexer = new Indexer(*(this->dbService));
indexer->setParent(this); indexer->setParent(this);
tagManager = new TagManager(*(this->dbService));
connectSignals(); connectSignals();
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
ui->tabWidget->setCurrentIndex(0); ui->tabWidget->setCurrentIndex(0);
@ -153,7 +148,7 @@ void MainWindow::connectSignals()
connect(this->indexer, &Indexer::finished, this, &MainWindow::finishIndexing); connect(this->indexer, &Indexer::finished, this, &MainWindow::finishIndexing);
connect(ui->lstPaths->selectionModel(), &QItemSelectionModel::selectionChanged, this, connect(ui->lstPaths->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[&](const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) [&](const QItemSelection &selected, const QItemSelection &deselected)
{ ui->btnDeletePath->setEnabled(this->ui->lstPaths->selectedItems().count() > 0); }); { ui->btnDeletePath->setEnabled(this->ui->lstPaths->selectedItems().count() > 0); });
connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); }); connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); });
@ -173,20 +168,19 @@ void MainWindow::connectSignals()
} }
}); });
connect(ui->menuAboutAction, &QAction::triggered, this, connect(ui->menuAboutAction, &QAction::triggered, this,
[this](bool /*checked*/) [this](bool checked)
{ {
AboutDialog aboutDialog(this); AboutDialog aboutDialog(this);
aboutDialog.exec(); aboutDialog.exec();
}); });
connect(ui->menuAboutQtAction, &QAction::triggered, this, connect(ui->menuAboutQtAction, &QAction::triggered, this,
[this](bool /*checked*/) { QMessageBox::aboutQt(this, "About Qt"); }); [this](bool checked) { QMessageBox::aboutQt(this, "About Qt"); });
connect(ui->menuSyncIndexAction, &QAction::triggered, this, &MainWindow::startIndexSync); connect(ui->menuSyncIndexAction, &QAction::triggered, this, &MainWindow::startIndexSync);
connect(ui->menuOpenUserManualAction, &QAction::triggered, this, connect(ui->menuOpenUserManualAction, &QAction::triggered, this,
[]() { QDesktopServices::openUrl(Common::userManualUrl()); }); [this]() { QDesktopServices::openUrl(Common::userManualUrl()); });
connect( connect(indexSyncer, &IndexSyncer::finished, this,
indexSyncer, &IndexSyncer::finished, this,
[&](unsigned int totalUpdated, unsigned int totalDeleted, unsigned int totalErrored) [&](unsigned int totalUpdated, unsigned int totalDeleted, unsigned int totalErrored)
{ {
this->progressDialog.cancel(); this->progressDialog.cancel();
@ -194,7 +188,9 @@ void MainWindow::connectSignals()
QMessageBox::information( QMessageBox::information(
this, "Syncing finished", this, "Syncing finished",
QString("Syncing finished\n\nTotal updated: %1\nTotal deleted: %2\nTotal errors: %3\n") QString("Syncing finished\n\nTotal updated: %1\nTotal deleted: %2\nTotal errors: %3\n")
.arg(QString::number(totalUpdated), QString::number(totalDeleted), QString::number(totalErrored))); .arg(QString::number(totalUpdated))
.arg(QString::number(totalDeleted))
.arg(QString::number(totalErrored)));
}); });
connect(this, &MainWindow::beginIndexSync, indexSyncer, &IndexSyncer::sync); connect(this, &MainWindow::beginIndexSync, indexSyncer, &IndexSyncer::sync);
connect(&this->progressDialog, &QProgressDialog::canceled, indexSyncer, &IndexSyncer::cancel); connect(&this->progressDialog, &QProgressDialog::canceled, indexSyncer, &IndexSyncer::cancel);
@ -226,18 +222,16 @@ void MainWindow::connectSignals()
qCritical() << msg << Qt::endl; qCritical() << msg << Qt::endl;
QMessageBox::critical(this, "IPC error", msg); QMessageBox::critical(this, "IPC error", msg);
}); });
connect(ui->radioMetadataOnly, &QRadioButton::toggled, this,
[this](bool toggled) /*connect(this, &MainWindow::startIpcPreviews, &previewCoordinator, &IPCPreviewClient::startGeneration,
{ Qt::QueuedConnection);
if(toggled) connect(this, &MainWindow::stopIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::stopGeneration,
{ Qt::QueuedConnection); */
this->ui->chkFillContentForContentless->setChecked(false);
};
});
} }
void MainWindow::exportFailedPaths() void MainWindow::exportFailedPaths()
{ {
QString filename = QString filename =
QString("/tmp/looqs_indexresult_failed_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss")); QString("/tmp/looqs_indexresult_failed_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss"));
QFile outFile(filename); QFile outFile(filename);
@ -270,11 +264,8 @@ void MainWindow::startIndexSync()
progressDialog.setValue(0); progressDialog.setValue(0);
progressDialog.open(); progressDialog.open();
FileSaverOptions options; indexSyncer->setKeepGoing(true);
options.keepGoing = true; indexSyncer->setVerbose(false);
options.verbose = false;
indexSyncer->setFileSaverOptions(options);
indexSyncer->setDryRun(false); indexSyncer->setDryRun(false);
indexSyncer->setRemoveDeletedFromIndex(true); indexSyncer->setRemoveDeletedFromIndex(true);
@ -293,7 +284,6 @@ void MainWindow::startIndexing()
if(this->indexer->isRunning()) if(this->indexer->isRunning())
{ {
ui->btnStartIndexing->setEnabled(false); ui->btnStartIndexing->setEnabled(false);
ui->btnStartIndexing->setText("Start indexing"); ui->btnStartIndexing->setText("Start indexing");
this->indexer->requestCancellation(); this->indexer->requestCancellation();
return; return;
@ -303,8 +293,6 @@ void MainWindow::startIndexing()
ui->resultsTab->setEnabled(false); ui->resultsTab->setEnabled(false);
ui->settingsTab->setEnabled(false); ui->settingsTab->setEnabled(false);
ui->txtPathScanAdd->setEnabled(false); ui->txtPathScanAdd->setEnabled(false);
ui->btnAddPath->setEnabled(false);
ui->btnChoosePath->setEnabled(false);
ui->txtSearch->setEnabled(false); ui->txtSearch->setEnabled(false);
ui->previewProcessBar->setValue(0); ui->previewProcessBar->setValue(0);
ui->previewProcessBar->setVisible(true); ui->previewProcessBar->setVisible(true);
@ -321,15 +309,6 @@ void MainWindow::startIndexing()
this->indexer->setTargetPaths(paths); this->indexer->setTargetPaths(paths);
QString ignorePatterns = ui->txtIgnorePatterns->text(); QString ignorePatterns = ui->txtIgnorePatterns->text();
this->indexer->setIgnorePattern(ignorePatterns.split(";")); this->indexer->setIgnorePattern(ignorePatterns.split(";"));
FileSaverOptions options;
options.fillExistingContentless =
ui->chkFillContentForContentless->isEnabled() && ui->chkFillContentForContentless->isChecked();
options.metadataOnly = ui->radioMetadataOnly->isChecked();
options.verbose = false;
options.keepGoing = true;
this->indexer->setFileSaverOptions(options);
this->indexer->beginIndexing(); this->indexer->beginIndexing();
QSettings settings; QSettings settings;
settings.setValue("indexPaths", pathSettingsValue); settings.setValue("indexPaths", pathSettingsValue);
@ -352,8 +331,6 @@ void MainWindow::finishIndexing()
ui->resultsTab->setEnabled(true); ui->resultsTab->setEnabled(true);
ui->settingsTab->setEnabled(true); ui->settingsTab->setEnabled(true);
ui->txtPathScanAdd->setEnabled(true); ui->txtPathScanAdd->setEnabled(true);
ui->btnAddPath->setEnabled(true);
ui->btnChoosePath->setEnabled(true);
ui->txtSearch->setEnabled(true); ui->txtSearch->setEnabled(true);
if(result.erroredPaths > 0) if(result.erroredPaths > 0)
{ {
@ -361,7 +338,7 @@ void MainWindow::finishIndexing()
} }
} }
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());
@ -407,8 +384,7 @@ void MainWindow::processShortcut(int key)
{ {
ui->txtSearch->setFocus(); ui->txtSearch->setFocus();
QString currentText = ui->txtSearch->text().trimmed(); QString currentText = ui->txtSearch->text().trimmed();
static QRegularExpression separatorRegex("[\\s\\)]"); int index = currentText.lastIndexOf(QRegularExpression("[\\s\\)]"));
int index = currentText.lastIndexOf(separatorRegex);
if(index != -1) if(index != -1)
{ {
bool isFilter = (index == currentText.length() - 1); bool isFilter = (index == currentText.length() - 1);
@ -688,7 +664,7 @@ void MainWindow::previewReceived()
QFileInfo fileInfo{docPath}; QFileInfo fileInfo{docPath};
QMenu menu("labeRightClick", this); QMenu menu("labeRightClick", this);
createSearchResultMenu(menu, fileInfo); createSearchResultMenu(menu, fileInfo);
menu.addAction("Copy page number", this, menu.addAction("Copy page number",
[previewPage] { QGuiApplication::clipboard()->setText(QString::number(previewPage)); }); [previewPage] { QGuiApplication::clipboard()->setText(QString::number(previewPage)); });
menu.exec(QCursor::pos()); menu.exec(QCursor::pos());
}; };
@ -888,8 +864,6 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
makePreviews(1); makePreviews(1);
} }
ui->tabWidget->setTabEnabled(1, previewDirty);
QString statusText = "Results: " + QString::number(results.size()) + " files"; QString statusText = "Results: " + QString::number(results.size()) + " files";
statusText += ", previewable: " + QString::number(this->previewCoordinator.previewableCount()); statusText += ", previewable: " + QString::number(this->previewCoordinator.previewableCount());
if(hasDeleted) if(hasDeleted)
@ -928,7 +902,7 @@ void MainWindow::makePreviews(int page)
ui->previewProcessBar->setMaximum(this->previewCoordinator.previewableCount()); ui->previewProcessBar->setMaximum(this->previewCoordinator.previewableCount());
QVector<QString> wordsToHighlight; QVector<QString> wordsToHighlight;
static QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#"); QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#");
for(const Token &token : this->contentSearchQuery.getTokens()) for(const Token &token : this->contentSearchQuery.getTokens())
{ {
if(token.type == FILTER_CONTENT_CONTAINS) if(token.type == FILTER_CONTENT_CONTAINS)
@ -960,6 +934,7 @@ void MainWindow::makePreviews(int page)
renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * (currentScale / 100.); renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * (currentScale / 100.);
renderConfig.wordsToHighlight = wordsToHighlight; renderConfig.wordsToHighlight = wordsToHighlight;
int previewPos = 0;
QVector<RenderTarget> targets; QVector<RenderTarget> targets;
for(const SearchResult &sr : this->previewCoordinator.getPreviewableSearchResults()) for(const SearchResult &sr : this->previewCoordinator.getPreviewableSearchResults())
{ {
@ -999,85 +974,26 @@ void MainWindow::handleSearchError(QString error)
void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo) void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
{ {
menu.addAction("Copy filename to clipboard", this, menu.addAction("Copy filename to clipboard",
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); }); [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); });
menu.addAction("Copy full path to clipboard", this, menu.addAction("Copy full path to clipboard",
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); }); [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); });
menu.addAction("Open containing folder", this, [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); }); menu.addAction("Open containing folder", [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); });
auto previewables = this->previewCoordinator.getPreviewableSearchResults(); auto previewables = this->previewCoordinator.getPreviewableSearchResults();
auto result = auto result =
std::find_if(previewables.begin(), previewables.end(), std::find_if(previewables.begin(), previewables.end(),
[&fileInfo](SearchResult &a) { return fileInfo.absoluteFilePath() == a.fileData.absPath; }); [this, &fileInfo](SearchResult &a) { return fileInfo.absoluteFilePath() == a.fileData.absPath; });
if(result != previewables.end()) if(result != previewables.end())
{ {
menu.addAction("Show previews for this file", this, menu.addAction("Show previews for this file",
[this, &fileInfo] [this, &fileInfo]
{ {
ui->tabWidget->setCurrentIndex(1); ui->tabWidget->setCurrentIndex(1);
this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath()); this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
}); });
} }
QMenu *tagMenu = menu.addMenu("Tag file with: ");
QVector<QString> allTags = this->dbService->getTags();
QHash<QString, bool> fileTags;
QString path = fileInfo.absoluteFilePath();
for(const QString &fileTag : this->dbService->getTagsForPath(path))
{
fileTags[fileTag] = true;
}
for(const QString &tag : allTags)
{
QCheckBox *checkBox = new QCheckBox(tagMenu);
QWidgetAction *checkableAction = new QWidgetAction(tagMenu);
checkableAction->setDefaultWidget(checkBox);
checkBox->setText(tag);
checkBox->setChecked(fileTags.contains(tag));
tagMenu->addAction(checkableAction);
connect(checkBox, &QCheckBox::stateChanged, this,
[this, checkBox, path]
{
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
QString checkBoxText = checkBox->text();
if(checkBox->isChecked())
{
if(!this->tagManager->addTagsToPath(path, {checkBoxText}))
{
QMessageBox::critical(this, "Error while adding tag",
"An error occured while trying to add the tag");
}
}
else
{
if(!this->tagManager->removeTagsForPath(path, {checkBoxText}))
{
QMessageBox::critical(this, "Error while removing tag",
"An error occured while trying to remove the tag");
}
}
});
}
tagMenu->addAction("Add new tags", this,
[this, path]
{
bool ok;
QString text =
QInputDialog::getText(this, tr("Enter new tags"), tr("New tags (comma separated):"),
QLineEdit::Normal, "", &ok);
if(ok && !this->tagManager->addTagsToPath(path, text, ','))
{
QMessageBox::critical(this, "Error while trying to add tags",
"An error occured while trying to add tags");
}
});
} }
void MainWindow::openDocument(QString path, int num) void MainWindow::openDocument(QString path, int num)
@ -1107,7 +1023,7 @@ void MainWindow::openFile(QString path)
QDesktopServices::openUrl(QUrl::fromLocalFile(path)); QDesktopServices::openUrl(QUrl::fromLocalFile(path));
} }
void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int /*i*/) void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i)
{ {
openFile(item->text(1)); openFile(item->text(1));
} }
@ -1132,11 +1048,10 @@ MainWindow::~MainWindow()
delete this->dbService; delete this->dbService;
delete this->dbFactory; delete this->dbFactory;
delete this->indexer; delete this->indexer;
delete this->tagManager;
delete ui; delete ui;
} }
void MainWindow::closeEvent(QCloseEvent * /*event*/) void MainWindow::closeEvent(QCloseEvent *event)
{ {
QStringList list = this->searchHistory.toList(); QStringList list = this->searchHistory.toList();
QSettings settings; QSettings settings;

파일 보기

@ -14,7 +14,6 @@
#include "../shared/indexsyncer.h" #include "../shared/indexsyncer.h"
#include "previewcoordinator.h" #include "previewcoordinator.h"
#include "indexer.h" #include "indexer.h"
#include "tagmanager.h"
namespace Ui namespace Ui
{ {
class MainWindow; class MainWindow;
@ -40,9 +39,6 @@ class MainWindow : public QMainWindow
QFutureWatcher<QVector<SearchResult>> searchWatcher; QFutureWatcher<QVector<SearchResult>> searchWatcher;
LooqsQuery contentSearchQuery; LooqsQuery contentSearchQuery;
QVector<QString> searchHistory; QVector<QString> searchHistory;
TagManager *tagManager;
int currentSearchHistoryIndex = 0; int currentSearchHistoryIndex = 0;
QString currentSavedSearchText; QString currentSavedSearchText;
bool previewDirty = false; bool previewDirty = false;
@ -61,7 +57,7 @@ class MainWindow : public QMainWindow
void initSettingsTabs(); void initSettingsTabs();
int currentSelectedScale(); int currentSelectedScale();
void processShortcut(int key); void processShortcut(int key);
bool eventFilter(QObject *object, QEvent *event) override; bool eventFilter(QObject *object, QEvent *event);
private slots: private slots:
void lineEditReturnPressed(); void lineEditReturnPressed();

파일 보기

@ -18,13 +18,16 @@
<item> <item>
<widget class="QLineEdit" name="txtSearch"/> <widget class="QLineEdit" name="txtSearch"/>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3"/>
</item>
<item> <item>
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="tabPosition"> <property name="tabPosition">
<enum>QTabWidget::South</enum> <enum>QTabWidget::South</enum>
</property> </property>
<property name="currentIndex"> <property name="currentIndex">
<number>2</number> <number>1</number>
</property> </property>
<widget class="QWidget" name="resultsTab"> <widget class="QWidget" name="resultsTab">
<attribute name="title"> <attribute name="title">
@ -79,7 +82,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1244</width> <width>1244</width>
<height>641</height> <height>633</height>
</rect> </rect>
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout"/> <layout class="QHBoxLayout" name="horizontalLayout"/>
@ -192,6 +195,62 @@
</attribute> </attribute>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="6" column="0"> <item row="6" column="0">
<widget class="QLineEdit" name="txtIgnorePatterns"/>
</item>
<item row="11" column="0">
<widget class="QPushButton" name="btnStartIndexing">
<property name="text">
<string>Start indexing</string>
</property>
</widget>
</item>
<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"> <widget class="QGroupBox" name="groupBoxIndexProgress">
<property name="contextMenuPolicy"> <property name="contextMenuPolicy">
<enum>Qt::PreventContextMenu</enum> <enum>Qt::PreventContextMenu</enum>
@ -393,108 +452,6 @@
</layout> </layout>
</widget> </widget>
</item> </item>
<item row="2" column="0">
<widget class="QGroupBox" name="groupBoxIndexOptions">
<property name="title">
<string>Index options</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_11">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Ignore patterns, separated by ';'. Example: *.js;*Downloads*:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txtIgnorePatterns"/>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioIndexEverything">
<property name="text">
<string>Index everything (metadata + file content)</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chkFillContentForContentless">
<property name="enabled">
<bool>true</bool>
</property>
<property name="text">
<string>Index content for files previously indexed without content</string>
</property>
<property name="checked">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radioMetadataOnly">
<property name="text">
<string>Index metadata only, don't process content of files</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="8" column="0">
<widget class="QPushButton" name="btnStartIndexing">
<property name="text">
<string>Start indexing</string>
</property>
</widget>
</item>
<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>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="settingsTab"> <widget class="QWidget" name="settingsTab">
@ -744,22 +701,5 @@
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<resources/> <resources/>
<connections> <connections/>
<connection>
<sender>radioIndexEverything</sender>
<signal>toggled(bool)</signal>
<receiver>chkFillContentForContentless</receiver>
<slot>setEnabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>639</x>
<y>464</y>
</hint>
<hint type="destinationlabel">
<x>639</x>
<y>497</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

파일 보기

@ -24,7 +24,7 @@ QSharedPointer<PreviewResult> PreviewGeneratorOdt::generate(RenderConfig config,
throw LooqsGeneralException("Error while reading content.xml of " + documentPath); throw LooqsGeneralException("Error while reading content.xml of " + documentPath);
} }
TagStripperProcessor tsp; TagStripperProcessor tsp;
QString content = tsp.process(entireContent).pages.constFirst().content; QString content = tsp.process(entireContent).first().content;
PreviewGeneratorPlainText plainTextGenerator; PreviewGeneratorPlainText plainTextGenerator;
result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName())); result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName()));

파일 보기

@ -195,7 +195,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
int totalWordsA = 0; int totalWordsA = 0;
int differentWordsB = 0; int differentWordsB = 0;
int totalWordsB = 0; int totalWordsB = 0;
for(int count : qAsConst(a.wordCountMap)) for(int count : a.wordCountMap.values())
{ {
if(count > 0) if(count > 0)
{ {
@ -203,7 +203,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
} }
totalWordsA += count; totalWordsA += count;
} }
for(int count : qAsConst(b.wordCountMap)) for(int count : b.wordCountMap.values())
{ {
if(count > 0) if(count > 0)
{ {
@ -246,8 +246,6 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second; totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
} }
} }
if(!resultText.isEmpty())
{
if(isTruncated) if(isTruncated)
{ {
header += "(truncated) "; header += "(truncated) ";
@ -258,9 +256,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
} }
header += "<hr>"; header += "<hr>";
resultText = header + resultText; return header + resultText;
}
return resultText;
} }
QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath, QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath,

파일 보기

@ -24,9 +24,7 @@ QString DefaultTextProcessor::processText(const QByteArray &data) const
return {}; return {};
} }
DocumentProcessResult DefaultTextProcessor::process(const QByteArray &data) const QVector<PageData> DefaultTextProcessor::process(const QByteArray &data) const
{ {
DocumentProcessResult result; return {{0, processText(data)}};
result.pages.append({0, processText(data)});
return result;
} }

파일 보기

@ -11,7 +11,7 @@ class DefaultTextProcessor : public Processor
public: public:
DefaultTextProcessor(); DefaultTextProcessor();
QString processText(const QByteArray &data) const; QString processText(const QByteArray &data) const;
DocumentProcessResult process(const QByteArray &data) const override; QVector<PageData> process(const QByteArray &data) const override;
}; };
#endif // DEFAULTTEXTPROCESSOR_H #endif // DEFAULTTEXTPROCESSOR_H

파일 보기

@ -1,31 +0,0 @@
#include "documentoutlineentry.h"
DocumentOutlineEntry::DocumentOutlineEntry()
{
}
QDataStream &operator<<(QDataStream &out, const DocumentOutlineEntry &pd)
{
out << pd.text << pd.type << pd.destinationPage;
out << pd.children.size();
for(const DocumentOutlineEntry &entry : pd.children)
{
out << entry;
}
return out;
}
QDataStream &operator>>(QDataStream &in, DocumentOutlineEntry &pd)
{
in >> pd.text >> pd.type >> pd.destinationPage;
int numChildren;
in >> numChildren;
for(int i = 0; i < numChildren; i++)
{
DocumentOutlineEntry entry;
in >> entry;
pd.children.append(entry);
}
return in;
}

파일 보기

@ -1,29 +0,0 @@
#ifndef DOCUMENTOUTLINEENTRY_H
#define DOCUMENTOUTLINEENTRY_H
#include <QMetaType>
#include <QDataStream>
#include <QString>
enum OutlineDestinationType
{
OUTLINE_DESTINATION_TYPE_NONE,
OUTLINE_DESTINATION_TYPE_PAGE
/* In the future, links, or #anchors are possible */
};
class DocumentOutlineEntry
{
public:
DocumentOutlineEntry();
QVector<DocumentOutlineEntry> children;
OutlineDestinationType type;
QString text;
unsigned int destinationPage;
};
Q_DECLARE_METATYPE(DocumentOutlineEntry);
QDataStream &operator<<(QDataStream &out, const DocumentOutlineEntry &pd);
QDataStream &operator>>(QDataStream &in, DocumentOutlineEntry &pd);
#endif // DOCUMENTOUTLINEENTRY_H

파일 보기

@ -1,39 +0,0 @@
#include "documentprocessresult.h"
QDataStream &operator<<(QDataStream &out, const DocumentProcessResult &pd)
{
out << pd.pages.size();
out << pd.outlines.size();
for(const PageData &pd : pd.pages)
{
out << pd;
}
for(const DocumentOutlineEntry &outline : pd.outlines)
{
out << outline;
}
return out;
}
QDataStream &operator>>(QDataStream &in, DocumentProcessResult &pd)
{
int numPages, numOutlines;
in >> numPages;
in >> numOutlines;
for(int i = 0; i < numPages; i++)
{
PageData data;
in >> data;
pd.pages.append(data);
}
for(int i = 0; i < numOutlines; i++)
{
DocumentOutlineEntry outline;
in >> outline;
pd.outlines.append(outline);
}
return in;
}

파일 보기

@ -1,17 +0,0 @@
#ifndef DOCUMENTPROCESSRESULT_H
#define DOCUMENTPROCESSRESULT_H
#include <pagedata.h>
#include <documentoutlineentry.h>
class DocumentProcessResult
{
public:
QVector<PageData> pages;
QVector<DocumentOutlineEntry> outlines;
};
Q_DECLARE_METATYPE(DocumentProcessResult);
QDataStream &operator<<(QDataStream &out, const DocumentProcessResult &pd);
QDataStream &operator>>(QDataStream &in, DocumentProcessResult &pd);
#endif // DOCUMENTPROCESSRESULT_H

파일 보기

@ -25,22 +25,10 @@ SaveFileResult FileSaver::addFile(QString path)
QString absPath = info.absoluteFilePath(); QString absPath = info.absoluteFilePath();
auto mtime = info.lastModified().toSecsSinceEpoch(); auto mtime = info.lastModified().toSecsSinceEpoch();
if(this->dbService->fileExistsInDatabase(absPath, mtime))
bool exists = false;
if(this->fileSaverOptions.fillExistingContentless)
{
exists = this->dbService->fileExistsInDatabase(absPath, mtime, 'c');
}
else
{
exists = this->dbService->fileExistsInDatabase(absPath, mtime);
}
if(exists)
{ {
return SKIPPED; return SKIPPED;
} }
return saveFile(info); return saveFile(info);
} }
@ -50,17 +38,18 @@ SaveFileResult FileSaver::updateFile(QString path)
return saveFile(info); return saveFile(info);
} }
int FileSaver::addFiles(const QVector<QString> paths) int FileSaver::addFiles(const QVector<QString> paths, bool keepGoing, bool verbose)
{ {
return processFiles(paths, std::bind(&FileSaver::addFile, this, std::placeholders::_1)); return processFiles(paths, std::bind(&FileSaver::addFile, this, std::placeholders::_1), keepGoing, verbose);
} }
int FileSaver::updateFiles(const QVector<QString> paths) int FileSaver::updateFiles(const QVector<QString> paths, bool keepGoing, bool verbose)
{ {
return processFiles(paths, std::bind(&FileSaver::updateFile, this, std::placeholders::_1)); return processFiles(paths, std::bind(&FileSaver::updateFile, this, std::placeholders::_1), keepGoing, verbose);
} }
int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc) int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc,
bool keepGoing, bool verbose)
{ {
std::atomic<bool> terminate{false}; std::atomic<bool> terminate{false};
std::atomic<int> processedCount{0}; std::atomic<int> processedCount{0};
@ -71,7 +60,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
{ {
return; return;
} }
if(this->fileSaverOptions.verbose) if(verbose)
{ {
Logger::info() << "Processing " << path << Qt::endl; Logger::info() << "Processing " << path << Qt::endl;
} }
@ -79,7 +68,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
if(result == DBFAIL || result == PROCESSFAIL) if(result == DBFAIL || result == PROCESSFAIL)
{ {
Logger::error() << "Failed to process " << path << Qt::endl; Logger::error() << "Failed to process " << path << Qt::endl;
if(!this->fileSaverOptions.keepGoing) if(!keepGoing)
{ {
terminate = true; terminate = true;
} }
@ -87,7 +76,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
else else
{ {
++processedCount; ++processedCount;
if(this->fileSaverOptions.verbose) if(verbose)
{ {
if(result == SKIPPED) if(result == SKIPPED)
{ {
@ -110,7 +99,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo) SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
{ {
DocumentProcessResult processResult; QVector<PageData> pageData;
QString canonicalPath = fileInfo.canonicalFilePath(); QString canonicalPath = fileInfo.canonicalFilePath();
int processorReturnCode = -1; int processorReturnCode = -1;
@ -131,26 +120,11 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
{ {
if(canonicalPath.startsWith(excludedPath)) if(canonicalPath.startsWith(excludedPath))
{ {
if(this->fileSaverOptions.verbose)
{
Logger::info() << "Skipped due to excluded path";
}
return SKIPPED; return SKIPPED;
} }
} }
bool mustFillContent = this->fileSaverOptions.fillExistingContentless; if(fileInfo.size() > 0)
if(!mustFillContent)
{
mustFillContent = !this->fileSaverOptions.metadataOnly;
if(mustFillContent)
{
auto filetype = this->dbService->queryFileType(fileInfo.absolutePath());
mustFillContent = !filetype.has_value() || filetype.value() == 'c';
}
}
if(fileInfo.size() > 0 && mustFillContent)
{ {
QProcess process; QProcess process;
QStringList args; QStringList args;
@ -169,10 +143,11 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
* finishes. * finishes.
*/ */
QDataStream in(process.readAllStandardOutput()); QDataStream in(process.readAllStandardOutput());
while(!in.atEnd())
if(!in.atEnd())
{ {
in >> processResult; PageData pd;
in >> pd;
pageData.append(pd);
} }
processorReturnCode = process.exitCode(); processorReturnCode = process.exitCode();
if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY) if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY)
@ -184,7 +159,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
} }
} }
} }
SaveFileResult result = this->dbService->saveFile(fileInfo, processResult, this->fileSaverOptions.metadataOnly); SaveFileResult result = this->dbService->saveFile(fileInfo, pageData);
if(result == OK && processorReturnCode == OK_WASEMPTY) if(result == OK && processorReturnCode == OK_WASEMPTY)
{ {
return OK_WASEMPTY; return OK_WASEMPTY;

파일 보기

@ -2,7 +2,6 @@
#define FILESAVER_H #define FILESAVER_H
#include <QSqlDatabase> #include <QSqlDatabase>
#include <QFileInfo> #include <QFileInfo>
#include "filesaveroptions.h"
#include "pagedata.h" #include "pagedata.h"
#include "filedata.h" #include "filedata.h"
#include "sqlitedbservice.h" #include "sqlitedbservice.h"
@ -12,21 +11,16 @@ class FileSaver
private: private:
SqliteDbService *dbService; SqliteDbService *dbService;
QStringList excludedPaths = Common::excludedPaths(); QStringList excludedPaths = Common::excludedPaths();
FileSaverOptions fileSaverOptions;
public: public:
FileSaver(SqliteDbService &dbService); FileSaver(SqliteDbService &dbService);
SaveFileResult addFile(QString path); SaveFileResult addFile(QString path);
SaveFileResult updateFile(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,
int addFiles(const QVector<QString> paths); bool keepGoing, bool verbose);
int updateFiles(const QVector<QString> paths); int addFiles(const QVector<QString> paths, bool keepGoing, bool verbose);
int updateFiles(const QVector<QString> paths, bool keepGoing, bool verbose);
void setFileSaverOptions(FileSaverOptions options)
{
this->fileSaverOptions = options;
}
}; };
#endif // FILESAVER_H #endif // FILESAVER_H

파일 보기

@ -1,14 +0,0 @@
#ifndef FILESAVEROPTIONS_H
#define FILESAVEROPTIONS_H
class FileSaverOptions
{
public:
bool verbose = false;
bool keepGoing = false;
bool metadataOnly = false;
/* Whether those previously explicitly without content should be filled */
bool fillExistingContentless = false;
};
#endif // FILESAVEROPTIONS_H

파일 보기

@ -12,7 +12,6 @@ FileScanWorker::FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &qu
void FileScanWorker::run() void FileScanWorker::run()
{ {
FileSaver saver{*this->dbService}; FileSaver saver{*this->dbService};
saver.setFileSaverOptions(this->fileSaverOptions);
auto paths = queue->dequeue(batchsize); auto paths = queue->dequeue(batchsize);
for(QString &path : paths) for(QString &path : paths)
{ {
@ -21,18 +20,11 @@ void FileScanWorker::run()
{ {
sfr = saver.addFile(path); sfr = saver.addFile(path);
} }
catch(LooqsGeneralException &e)
{
Logger::error() << e.message << Qt::endl;
sfr = PROCESSFAIL;
}
catch(std::exception &e) catch(std::exception &e)
{ {
Logger::error() << e.what() << Qt::endl; Logger::error() << e.what();
sfr = PROCESSFAIL; // well... sfr = PROCESSFAIL; // well...
} }
emit result({path, sfr}); emit result({path, sfr});
if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck
{ {
@ -42,8 +34,3 @@ void FileScanWorker::run()
} }
emit finished(); emit finished();
} }
void FileScanWorker::setFileSaverOptions(FileSaverOptions options)
{
this->fileSaverOptions = options;
}

파일 보기

@ -15,14 +15,12 @@ class FileScanWorker : public QObject, public QRunnable
protected: protected:
SqliteDbService *dbService; SqliteDbService *dbService;
ConcurrentQueue<QString> *queue; ConcurrentQueue<QString> *queue;
FileSaverOptions fileSaverOptions;
int batchsize; int batchsize;
std::atomic<bool> *stopToken; std::atomic<bool> *stopToken;
public: public:
FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize, std::atomic<bool> &stopToken); FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize, std::atomic<bool> &stopToken);
void run() override; void run() override;
void setFileSaverOptions(FileSaverOptions options);
signals: signals:
void result(FileScanResult); void result(FileScanResult);
void finished(); void finished();

파일 보기

@ -73,6 +73,16 @@ void Indexer::setTargetPaths(QVector<QString> pathsToScan)
this->pathsToScan = pathsToScan; this->pathsToScan = pathsToScan;
} }
void Indexer::setVerbose(bool verbose)
{
this->verbose = verbose;
}
void Indexer::setKeepGoing(bool keepGoing)
{
this->keepGoing = keepGoing;
}
void Indexer::requestCancellation() void Indexer::requestCancellation()
{ {
this->dirScanner->cancel(); this->dirScanner->cancel();
@ -98,7 +108,6 @@ void Indexer::launchWorker(ConcurrentQueue<QString> &queue, int batchsize)
FileScanWorker *runnable = new FileScanWorker(*this->db, queue, batchsize, this->workerCancellationToken); FileScanWorker *runnable = new FileScanWorker(*this->db, queue, batchsize, this->workerCancellationToken);
connect(runnable, &FileScanWorker::result, this, &Indexer::processFileScanResult); connect(runnable, &FileScanWorker::result, this, &Indexer::processFileScanResult);
connect(runnable, &FileScanWorker::finished, this, &Indexer::processFinishedWorker); connect(runnable, &FileScanWorker::finished, this, &Indexer::processFinishedWorker);
runnable->setFileSaverOptions(this->fileSaverOptions);
++this->runningWorkers; ++this->runningWorkers;
QThreadPool::globalInstance()->start(runnable); QThreadPool::globalInstance()->start(runnable);
} }
@ -111,6 +120,24 @@ void Indexer::dirScanProgress(int current, int total)
void Indexer::processFileScanResult(FileScanResult result) void Indexer::processFileScanResult(FileScanResult result)
{ {
if(isErrorSaveFileResult(result.second))
{
this->currentIndexResult.results.append(result);
if(!keepGoing)
{
this->requestCancellation();
emit finished();
return;
}
}
else
{
if(verbose)
{
this->currentIndexResult.results.append(result);
}
}
/* TODO: OK_WASEMPTY might need a special list */ /* TODO: OK_WASEMPTY might need a special list */
if(result.second == OK || result.second == OK_WASEMPTY) if(result.second == OK || result.second == OK_WASEMPTY)
{ {
@ -125,24 +152,6 @@ void Indexer::processFileScanResult(FileScanResult result)
++this->currentIndexResult.erroredPaths; ++this->currentIndexResult.erroredPaths;
} }
if(isErrorSaveFileResult(result.second))
{
this->currentIndexResult.results.append(result);
if(!this->fileSaverOptions.keepGoing)
{
this->requestCancellation();
emit finished();
return;
}
}
else
{
if(this->fileSaverOptions.verbose)
{
this->currentIndexResult.results.append(result);
}
}
QTime currentTime = QTime::currentTime(); QTime currentTime = QTime::currentTime();
if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10) if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10)
{ {
@ -166,13 +175,3 @@ void Indexer::processFinishedWorker()
emit finished(); emit finished();
} }
} }
void Indexer::setFileSaverOptions(FileSaverOptions options)
{
this->fileSaverOptions = options;
}
void Indexer::setProgressReportThreshold(int threshold)
{
this->progressReportThreshold = threshold;
}

파일 보기

@ -52,7 +52,8 @@ class Indexer : public QObject
{ {
Q_OBJECT Q_OBJECT
protected: protected:
FileSaverOptions fileSaverOptions; bool verbose = false;
bool keepGoing = true;
SqliteDbService *db; SqliteDbService *db;
int progressReportThreshold = 50; int progressReportThreshold = 50;
@ -79,10 +80,8 @@ class Indexer : public QObject
void beginIndexing(); void beginIndexing();
void setIgnorePattern(QStringList ignorePattern); void setIgnorePattern(QStringList ignorePattern);
void setTargetPaths(QVector<QString> pathsToScan); void setTargetPaths(QVector<QString> pathsToScan);
void setVerbose(bool verbose);
void setFileSaverOptions(FileSaverOptions options); void setKeepGoing(bool keepGoing);
void setProgressReportThreshold(int threshold);
void requestCancellation(); void requestCancellation();

파일 보기

@ -7,16 +7,21 @@ IndexSyncer::IndexSyncer(SqliteDbService &dbService)
this->dbService = &dbService; this->dbService = &dbService;
} }
void IndexSyncer::setFileSaverOptions(FileSaverOptions options)
{
fileSaverOptions = options;
}
void IndexSyncer::setDryRun(bool dryRun) void IndexSyncer::setDryRun(bool dryRun)
{ {
this->dryRun = dryRun; this->dryRun = dryRun;
} }
void IndexSyncer::setVerbose(bool verbose)
{
this->verbose = verbose;
}
void IndexSyncer::setKeepGoing(bool keepGoing)
{
this->keepGoing = keepGoing;
}
void IndexSyncer::setRemoveDeletedFromIndex(bool removeDeletedFromIndex) void IndexSyncer::setRemoveDeletedFromIndex(bool removeDeletedFromIndex)
{ {
this->removeDeletedFromIndex = removeDeletedFromIndex; this->removeDeletedFromIndex = removeDeletedFromIndex;
@ -30,7 +35,7 @@ void IndexSyncer::setPattern(QString pattern)
void IndexSyncer::sync() void IndexSyncer::sync()
{ {
this->stopToken.store(false, std::memory_order_relaxed); this->stopToken.store(false, std::memory_order_relaxed);
FileSaver saver(*this->dbService);
QVector<FileData> files; QVector<FileData> files;
int offset = 0; int offset = 0;
int limit = 10000; int limit = 10000;
@ -82,7 +87,7 @@ void IndexSyncer::sync()
if(!this->dbService->deleteFile(fileData.absPath)) if(!this->dbService->deleteFile(fileData.absPath))
{ {
emit error("Error: Failed to delete " + fileData.absPath + " from the index"); emit error("Error: Failed to delete " + fileData.absPath + " from the index");
if(!this->fileSaverOptions.keepGoing) if(!this->keepGoing)
{ {
emit finished(totalUpdatesFilesCount, totalDeletedFilesCount, totalErroredFilesCount); emit finished(totalUpdatesFilesCount, totalDeletedFilesCount, totalErroredFilesCount);
return; return;
@ -99,15 +104,13 @@ void IndexSyncer::sync()
} }
} }
FileSaver saver(*this->dbService); unsigned int updatedFilesCount = saver.updateFiles(filePathsToUpdate, keepGoing, verbose);
saver.setFileSaverOptions(this->fileSaverOptions);
unsigned int updatedFilesCount = saver.updateFiles(filePathsToUpdate);
unsigned int shouldHaveUpdatedCount = static_cast<unsigned int>(filePathsToUpdate.size()); unsigned int shouldHaveUpdatedCount = static_cast<unsigned int>(filePathsToUpdate.size());
if(updatedFilesCount != shouldHaveUpdatedCount) if(updatedFilesCount != shouldHaveUpdatedCount)
{ {
totalErroredFilesCount += (shouldHaveUpdatedCount - updatedFilesCount); totalErroredFilesCount += (shouldHaveUpdatedCount - updatedFilesCount);
if(!this->fileSaverOptions.keepGoing) if(!keepGoing)
{ {
QString errorMsg = QString("Failed to update all files selected for updating in this batch. Updated") + QString errorMsg = QString("Failed to update all files selected for updating in this batch. Updated") +
updatedFilesCount + "out of" + shouldHaveUpdatedCount + "selected for updating"; updatedFilesCount + "out of" + shouldHaveUpdatedCount + "selected for updating";

파일 보기

@ -1,15 +1,16 @@
#ifndef INDEXSYNCER_H #ifndef INDEXSYNCER_H
#define INDEXSYNCER_H #define INDEXSYNCER_H
#include "sqlitedbservice.h" #include "sqlitedbservice.h"
#include "filesaveroptions.h"
class IndexSyncer : public QObject class IndexSyncer : public QObject
{ {
Q_OBJECT Q_OBJECT
private: private:
SqliteDbService *dbService = nullptr; SqliteDbService *dbService = nullptr;
FileSaverOptions fileSaverOptions; bool keepGoing = true;
bool removeDeletedFromIndex = true; bool removeDeletedFromIndex = true;
bool dryRun = false; bool dryRun = false;
bool verbose = false;
QString pattern; QString pattern;
std::atomic<bool> stopToken{false}; std::atomic<bool> stopToken{false};
@ -17,12 +18,12 @@ class IndexSyncer : public QObject
public: public:
IndexSyncer(SqliteDbService &dbService); IndexSyncer(SqliteDbService &dbService);
void setFileSaverOptions(FileSaverOptions options);
public slots: public slots:
void sync(); void sync();
void cancel(); void cancel();
void setDryRun(bool dryRun); void setDryRun(bool dryRun);
void setVerbose(bool verbose);
void setKeepGoing(bool keepGoing);
void setRemoveDeletedFromIndex(bool removeDeletedFromIndex); void setRemoveDeletedFromIndex(bool removeDeletedFromIndex);
void setPattern(QString pattern); void setPattern(QString pattern);

파일 보기

@ -7,7 +7,6 @@
#include <optional> #include <optional>
#include <algorithm> #include <algorithm>
#include "looqsquery.h" #include "looqsquery.h"
#include "looqsgeneralexception.h"
const QVector<Token> &LooqsQuery::getTokens() const const QVector<Token> &LooqsQuery::getTokens() const
{ {
@ -29,11 +28,6 @@ bool LooqsQuery::hasContentSearch() const
return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
} }
bool LooqsQuery::hasOutlineSearch() const
{
return (this->getTokensMask() & FILTER_OUTLINE_CONTAINS) == FILTER_OUTLINE_CONTAINS;
}
bool LooqsQuery::hasPathSearch() const bool LooqsQuery::hasPathSearch() const
{ {
return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH; return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH;
@ -186,8 +180,7 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
QStringList loneWords; QStringList loneWords;
LooqsQuery result; LooqsQuery result;
static QRegularExpression rx( QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([^\\s])+)|(?<boolean>AND|OR)"
"((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([^\\s])+)|(?<boolean>AND|OR)"
"|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>[^\\s]+))"); "|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>[^\\s]+))");
QRegularExpressionMatchIterator i = rx.globalMatch(expression); QRegularExpressionMatchIterator i = rx.globalMatch(expression);
auto previousWasBool = [&result] { return !result.tokens.empty() && ((result.tokens.last().type & BOOL) == BOOL); }; auto previousWasBool = [&result] { return !result.tokens.empty() && ((result.tokens.last().type & BOOL) == BOOL); };
@ -290,14 +283,6 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
{ {
tokenType = FILTER_CONTENT_PAGE; tokenType = FILTER_CONTENT_PAGE;
} }
else if(filtername == "t" || filtername == "tag")
{
tokenType = FILTER_TAG_ASSIGNED;
}
else if(filtername == "toc" || filtername == "outline")
{
tokenType = FILTER_OUTLINE_CONTAINS;
}
// TODO: given this is not really a "filter", this feels slightly misplaced here // TODO: given this is not really a "filter", this feels slightly misplaced here
else if(filtername == "sort") else if(filtername == "sort")
{ {

파일 보기

@ -2,6 +2,7 @@
#define LOOQSQUERY_H #define LOOQSQUERY_H
#include <QString> #include <QString>
#include <QVector> #include <QVector>
#include "looqsgeneralexception.h"
#include "token.h" #include "token.h"
/* Fields that can be queried or sorted */ /* Fields that can be queried or sorted */
enum QueryField enum QueryField
@ -45,7 +46,7 @@ class LooqsQuery
void addToken(Token t); void addToken(Token t);
void updateTokensMask() void updateTokensMask()
{ {
for(const Token &t : qAsConst(tokens)) for(const Token &t : tokens)
{ {
this->tokensMask |= t.type; this->tokensMask |= t.type;
} }
@ -68,7 +69,6 @@ class LooqsQuery
this->limit = limit; this->limit = limit;
} }
bool hasContentSearch() const; bool hasContentSearch() const;
bool hasOutlineSearch() const;
bool hasPathSearch() const; bool hasPathSearch() const;
void addSortCondition(SortCondition sc); void addSortCondition(SortCondition sc);
@ -92,6 +92,14 @@ class LooqsQuery
this->sortConditions = sortConditions; this->sortConditions = sortConditions;
updateTokensMask(); updateTokensMask();
} }
LooqsQuery(const LooqsQuery &o)
{
this->tokens = o.tokens;
this->sortConditions = o.sortConditions;
this->tokensMask = o.tokensMask;
this->limit = o.limit;
}
}; };
#endif // LOOQSQUERY_H #endif // LOOQSQUERY_H

파일 보기

@ -1,6 +0,0 @@
CREATE TABLE tag(id integer PRIMARY KEY, name varchar(128) UNIQUE);
CREATE TABLE filetag(fileid integer, tagid integer);
CREATE INDEX filetag_fileid ON filetag(fileid);
CREATE INDEX tag_id ON tag(id);
CREATE INDEX file_path ON file ( path );
UPDATE file SET filetype='c' WHERE filetype='f';

파일 보기

@ -1,2 +0,0 @@
CREATE TABLE outline(id INTEGER PRIMARY KEY, fileid INTEGER REFERENCES file (id) ON DELETE CASCADE, text varchar(1024), page integer);
CREATE INDEX outline_fileid ON outline (fileid);

파일 보기

@ -4,7 +4,5 @@
<file>2.sql</file> <file>2.sql</file>
<file>3.sql</file> <file>3.sql</file>
<file>4.sql</file> <file>4.sql</file>
<file>5.sql</file>
<file>6.sql</file>
</qresource> </qresource>
</RCC> </RCC>

파일 보기

@ -10,7 +10,7 @@ class NothingProcessor : public Processor
NothingProcessor(); NothingProcessor();
public: public:
DocumentProcessResult process(const QByteArray & /*data*/) const override QVector<PageData> process(const QByteArray &data) const override
{ {
return {}; return {};
} }

파일 보기

@ -3,12 +3,12 @@
#include "odtprocessor.h" #include "odtprocessor.h"
#include "tagstripperprocessor.h" #include "tagstripperprocessor.h"
DocumentProcessResult OdtProcessor::process(const QByteArray & /*data*/) const QVector<PageData> OdtProcessor::process(const QByteArray &data) const
{ {
throw LooqsGeneralException("Not implemented yet"); throw LooqsGeneralException("Not implemented yet");
} }
DocumentProcessResult OdtProcessor::process(QString path) const QVector<PageData> OdtProcessor::process(QString path) const
{ {
QuaZipFile zipFile(path); QuaZipFile zipFile(path);
zipFile.setFileName("content.xml"); zipFile.setFileName("content.xml");

파일 보기

@ -8,9 +8,9 @@ class OdtProcessor : public Processor
{ {
this->PREFERED_DATA_SOURCE = FILEPATH; this->PREFERED_DATA_SOURCE = FILEPATH;
} }
DocumentProcessResult process(const QByteArray &data) const override; QVector<PageData> process(const QByteArray &data) const override;
DocumentProcessResult process(QString path) const override; QVector<PageData> process(QString path) const override;
}; };
#endif // ODTPROCESSOR_H #endif // ODTPROCESSOR_H

파일 보기

@ -1,3 +1,5 @@
#include "paralleldirscanner.h"
#include <QRunnable> #include <QRunnable>
#include <QMutex> #include <QMutex>
#include <QDirIterator> #include <QDirIterator>
@ -5,7 +7,7 @@
#include <QThreadPool> #include <QThreadPool>
#include <functional> #include <functional>
#include "dirscanworker.h" #include "dirscanworker.h"
#include "paralleldirscanner.h" #include "logger.h"
ParallelDirScanner::ParallelDirScanner() ParallelDirScanner::ParallelDirScanner()
{ {

파일 보기

@ -5,30 +5,9 @@ PdfProcessor::PdfProcessor()
{ {
} }
QVector<DocumentOutlineEntry> PdfProcessor::createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const QVector<PageData> PdfProcessor::process(const QByteArray &data) const
{ {
QVector<DocumentOutlineEntry> result; QVector<PageData> result;
for(const Poppler::OutlineItem &outlineItem : outlineItems)
{
DocumentOutlineEntry documentOutlineEntry;
documentOutlineEntry.text = outlineItem.name();
documentOutlineEntry.type = OUTLINE_DESTINATION_TYPE_PAGE;
if(!outlineItem.destination().isNull())
{
documentOutlineEntry.destinationPage = outlineItem.destination()->pageNumber();
}
if(outlineItem.hasChildren())
{
documentOutlineEntry.children = createOutline(outlineItem.children());
}
result.append(documentOutlineEntry);
}
return result;
}
DocumentProcessResult PdfProcessor::process(const QByteArray &data) const
{
DocumentProcessResult result;
QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data)); QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data));
if(doc.isNull()) if(doc.isNull())
{ {
@ -47,13 +26,12 @@ DocumentProcessResult PdfProcessor::process(const QByteArray &data) const
for(auto i = 0; i < pagecount; i++) for(auto i = 0; i < pagecount; i++)
{ {
QString text = doc->page(i)->text(entirePage); QString text = doc->page(i)->text(entirePage);
result.pages.append({static_cast<unsigned int>(i + 1), text}); result.append({static_cast<unsigned int>(i + 1), text});
/*TODO: hack, so we can fts search several words over the whole document, not just pages. /*TODO: hack, so we can fts search several words over the whole document, not just pages.
* this of course uses more space and should be solved differently. * this of course uses more space and should be solved differently.
*/ */
entire += text; entire += text;
} }
result.pages.append({0, entire}); result.append({0, entire});
result.outlines = createOutline(doc->outline());
return result; return result;
} }

파일 보기

@ -1,6 +1,5 @@
#ifndef PDFPROCESSOR_H #ifndef PDFPROCESSOR_H
#define PDFPROCESSOR_H #define PDFPROCESSOR_H
#include <poppler-qt5.h>
#include "processor.h" #include "processor.h"
class PdfProcessor : public Processor class PdfProcessor : public Processor
{ {
@ -8,8 +7,7 @@ class PdfProcessor : public Processor
PdfProcessor(); PdfProcessor();
public: public:
QVector<DocumentOutlineEntry> createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const; QVector<PageData> process(const QByteArray &data) const override;
DocumentProcessResult process(const QByteArray &data) const override;
}; };
#endif // PDFPROCESSOR_H #endif // PDFPROCESSOR_H

파일 보기

@ -2,8 +2,8 @@
#define PROCESSOR_H #define PROCESSOR_H
#include <QVector> #include <QVector>
#include <QFile> #include <QFile>
#include "pagedata.h"
#include "utils.h" #include "utils.h"
#include "documentprocessresult.h"
enum DataSource enum DataSource
{ {
FILEPATH, FILEPATH,
@ -18,8 +18,8 @@ class Processor
* a single file */ * a single file */
DataSource PREFERED_DATA_SOURCE = ARRAY; DataSource PREFERED_DATA_SOURCE = ARRAY;
Processor(); Processor();
virtual DocumentProcessResult process(const QByteArray &data) const = 0; virtual QVector<PageData> process(const QByteArray &data) const = 0;
virtual DocumentProcessResult process(QString path) const virtual QVector<PageData> process(QString path) const
{ {
return process(Utils::readFile(path)); return process(Utils::readFile(path));
} }

파일 보기

@ -65,12 +65,18 @@ void SandboxedProcessor::enableSandbox(QString readablePath)
exile_free_policy(policy); exile_free_policy(policy);
} }
void SandboxedProcessor::printResults(const DocumentProcessResult &result) void SandboxedProcessor::printResults(const QVector<PageData> &pageData)
{ {
QFile fsstdout; QFile fsstdout;
fsstdout.open(stdout, QIODevice::WriteOnly); fsstdout.open(stdout, QIODevice::WriteOnly);
QDataStream stream(&fsstdout); QDataStream stream(&fsstdout);
stream << result;
for(const PageData &data : pageData)
{
stream << data;
// fsstdout.flush();
}
fsstdout.close(); fsstdout.close();
} }
@ -96,7 +102,7 @@ SaveFileResult SandboxedProcessor::process()
return OK; return OK;
} }
DocumentProcessResult processResult; QVector<PageData> pageData;
QString absPath = fileInfo.absoluteFilePath(); QString absPath = fileInfo.absoluteFilePath();
try try
@ -105,13 +111,13 @@ SaveFileResult SandboxedProcessor::process()
{ {
/* Read access to FS needed... doh..*/ /* Read access to FS needed... doh..*/
enableSandbox(absPath); enableSandbox(absPath);
processResult = processor->process(absPath); pageData = processor->process(absPath);
} }
else else
{ {
QByteArray data = Utils::readFile(absPath); QByteArray data = Utils::readFile(absPath);
enableSandbox(); enableSandbox();
processResult = processor->process(data); pageData = processor->process(data);
} }
} }
catch(LooqsGeneralException &e) catch(LooqsGeneralException &e)
@ -120,6 +126,6 @@ SaveFileResult SandboxedProcessor::process()
return PROCESSFAIL; return PROCESSFAIL;
} }
printResults(processResult); printResults(pageData);
return processResult.pages.isEmpty() ? OK_WASEMPTY : OK; return pageData.isEmpty() ? OK_WASEMPTY : OK;
} }

파일 보기

@ -2,7 +2,7 @@
#define SANDBOXEDPROCESSOR_H #define SANDBOXEDPROCESSOR_H
#include <QString> #include <QString>
#include <QMimeDatabase> #include <QMimeDatabase>
#include "documentprocessresult.h" #include "pagedata.h"
#include "savefileresult.h" #include "savefileresult.h"
class SandboxedProcessor class SandboxedProcessor
@ -12,7 +12,7 @@ class SandboxedProcessor
QMimeDatabase mimeDatabase; QMimeDatabase mimeDatabase;
void enableSandbox(QString readablePath = ""); void enableSandbox(QString readablePath = "");
void printResults(const DocumentProcessResult &pageData); void printResults(const QVector<PageData> &pageData);
public: public:
SandboxedProcessor(QString filepath) SandboxedProcessor(QString filepath)

파일 보기

@ -42,8 +42,6 @@ SOURCES += sqlitesearch.cpp \
dbmigrator.cpp \ dbmigrator.cpp \
defaulttextprocessor.cpp \ defaulttextprocessor.cpp \
dirscanworker.cpp \ dirscanworker.cpp \
documentoutlineentry.cpp \
documentprocessresult.cpp \
encodingdetector.cpp \ encodingdetector.cpp \
filesaver.cpp \ filesaver.cpp \
filescanworker.cpp \ filescanworker.cpp \
@ -62,7 +60,6 @@ SOURCES += sqlitesearch.cpp \
processor.cpp \ processor.cpp \
sandboxedprocessor.cpp \ sandboxedprocessor.cpp \
sqlitedbservice.cpp \ sqlitedbservice.cpp \
tagmanager.cpp \
tagstripperprocessor.cpp \ tagstripperprocessor.cpp \
utils.cpp \ utils.cpp \
../submodules/exile.h/exile.c \ ../submodules/exile.h/exile.c \
@ -74,12 +71,9 @@ HEADERS += sqlitesearch.h \
dbmigrator.h \ dbmigrator.h \
defaulttextprocessor.h \ defaulttextprocessor.h \
dirscanworker.h \ dirscanworker.h \
documentoutlineentry.h \
documentprocessresult.h \
encodingdetector.h \ encodingdetector.h \
filedata.h \ filedata.h \
filesaver.h \ filesaver.h \
filesaveroptions.h \
filescanworker.h \ filescanworker.h \
indexer.h \ indexer.h \
indexsyncer.h \ indexsyncer.h \
@ -98,7 +92,6 @@ HEADERS += sqlitesearch.h \
savefileresult.h \ savefileresult.h \
searchresult.h \ searchresult.h \
sqlitedbservice.h \ sqlitedbservice.h \
tagmanager.h \
tagstripperprocessor.h \ tagstripperprocessor.h \
token.h \ token.h \
common.h \ common.h \

파일 보기

@ -2,10 +2,25 @@
#include <QFileInfo> #include <QFileInfo>
#include <QDateTime> #include <QDateTime>
#include <QSqlError> #include <QSqlError>
#include "looqsgeneralexception.h"
#include "sqlitedbservice.h" #include "sqlitedbservice.h"
#include "filedata.h" #include "filedata.h"
#include "logger.h" #include "logger.h"
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime)
{
auto query = QSqlQuery(dbFactory->forCurrentThread());
query.prepare("SELECT 1 FROM file WHERE path = ? and mtime = ?");
query.addBindValue(path);
query.addBindValue(mtime);
if(!query.exec())
{
throw LooqsGeneralException("Error while trying to query for file existance: " + query.lastError().text());
}
if(!query.next())
{
return false;
}
return query.value(0).toBool();
}
QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query) QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
{ {
@ -14,29 +29,20 @@ QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
return searcher.search(query); return searcher.search(query);
} }
std::optional<QChar> SqliteDbService::queryFileType(QString absPath)
{
auto query = exec("SELECT filetype FROM file WHERE path = ?", {absPath});
if(!query.next())
{
return {};
}
return query.value(0).toChar();
}
bool SqliteDbService::fileExistsInDatabase(QString path) bool SqliteDbService::fileExistsInDatabase(QString path)
{ {
return execBool("SELECT 1 FROM file WHERE path = ?", {path}); auto query = QSqlQuery(dbFactory->forCurrentThread());
} query.prepare("SELECT 1 FROM file WHERE path = ?");
query.addBindValue(path);
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime) if(!query.exec())
{ {
return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ?", {path, mtime}); throw LooqsGeneralException("Error while trying to query for file existance: " + query.lastError().text());
} }
if(!query.next())
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime, QChar fileType) {
{ return false;
return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ? AND filetype = ?", {path, mtime, fileType}); }
return query.value(0).toBool();
} }
SqliteDbService::SqliteDbService(DatabaseFactory &dbFactory) SqliteDbService::SqliteDbService(DatabaseFactory &dbFactory)
@ -104,117 +110,6 @@ unsigned int SqliteDbService::getFiles(QVector<FileData> &results, QString wildC
return processedRows; return processedRows;
} }
QVector<QString> SqliteDbService::getTags()
{
QVector<QString> result;
auto query = QSqlQuery(dbFactory->forCurrentThread());
query.prepare("SELECT name FROM tag ORDER by name ASC");
query.setForwardOnly(true);
if(!query.exec())
{
throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
}
while(query.next())
{
QString tagname = query.value(0).toString();
result.append(tagname);
}
return result;
}
QVector<QString> SqliteDbService::getTagsForPath(QString path)
{
QVector<QString> result;
auto query = QSqlQuery(dbFactory->forCurrentThread());
query.prepare("SELECT name FROM tag INNER JOIN filetag ON tag.id = filetag.tagid INNER JOIN file ON filetag.fileid "
"= file.id WHERE file.path = ? ORDER BY name ASC");
query.addBindValue(path);
query.setForwardOnly(true);
if(!query.exec())
{
throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
}
while(query.next())
{
QString tagname = query.value(0).toString();
result.append(tagname);
}
return result;
}
QVector<QString> SqliteDbService::getPathsForTag(QString tag)
{
QVector<QString> result;
auto query = QSqlQuery(dbFactory->forCurrentThread());
query.prepare(
"SELECT file.path FROM tag INNER JOIN filetag ON tag.id = filetag.tagid INNER JOIN file ON filetag.fileid "
"= file.id WHERE tag.name = ?");
query.addBindValue(tag.toLower());
query.setForwardOnly(true);
if(!query.exec())
{
throw LooqsGeneralException("Error while trying to retrieve paths from database: " + query.lastError().text());
}
while(query.next())
{
QString path = query.value(0).toString();
result.append(path);
}
return result;
}
bool SqliteDbService::setTags(QString path, const QSet<QString> &tags)
{
QSqlDatabase db = dbFactory->forCurrentThread();
if(!db.transaction())
{
Logger::error() << "Failed to open transaction for " << path << " : " << db.lastError() << Qt::endl;
return false;
}
QSqlQuery deletionQuery = QSqlQuery(db);
deletionQuery.prepare("DELETE FROM filetag WHERE fileid = (SELECT id FROM file WHERE path = ?)");
deletionQuery.addBindValue(path);
if(!deletionQuery.exec())
{
db.rollback();
Logger::error() << "Failed to delete existing tags " << deletionQuery.lastError() << Qt::endl;
return false;
}
for(const QString &tag : tags)
{
QSqlQuery tagQuery = QSqlQuery(db);
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
tagQuery.addBindValue(tag.toLower());
if(!tagQuery.exec())
{
db.rollback();
Logger::error() << "Failed to insert tag " << tagQuery.lastError() << Qt::endl;
return false;
}
QSqlQuery fileTagQuery(db);
fileTagQuery.prepare(
"INSERT INTO filetag(fileid, tagid) VALUES((SELECT id FROM file WHERE path = ?), (SELECT id "
"FROM tag WHERE name = ?))");
fileTagQuery.bindValue(0, path);
fileTagQuery.bindValue(1, tag);
if(!fileTagQuery.exec())
{
db.rollback();
Logger::error() << "Failed to assign tag to file" << Qt::endl;
return false;
}
}
if(!db.commit())
{
db.rollback();
Logger::error() << "Failed to commit transaction when saving tags" << Qt::endl;
return false;
}
return true;
}
bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData) bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData)
{ {
QString ftsInsertStatement; QString ftsInsertStatement;
@ -253,63 +148,11 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid
return true; return true;
} }
bool SqliteDbService::insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines) SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData)
{
QSqlQuery outlineQuery(db);
outlineQuery.prepare("INSERT INTO outline(fileid, text, page) VALUES(?,?,?)");
outlineQuery.addBindValue(fileid);
for(const DocumentOutlineEntry &outline : outlines)
{
outlineQuery.bindValue(1, outline.text.toLower());
outlineQuery.bindValue(2, outline.destinationPage);
if(!outlineQuery.exec())
{
Logger::error() << "Failed outline insertion " << outlineQuery.lastError() << Qt::endl;
return false;
}
if(!insertOutline(db, fileid, outline.children))
{
Logger::error() << "Failed outline insertion (children)) " << outlineQuery.lastError() << Qt::endl;
return false;
}
}
return true;
}
QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args)
{
auto query = QSqlQuery(dbFactory->forCurrentThread());
query.prepare(querystr);
for(const QVariant &v : args)
{
query.addBindValue(v);
}
if(!query.exec())
{
throw LooqsGeneralException("Error while exec(): " + query.lastError().text() + " for query: " + querystr);
}
return query;
}
bool SqliteDbService::execBool(QString querystr, std::initializer_list<QVariant> args)
{
auto query = exec(querystr, args);
if(!query.next())
{
return false;
}
return query.value(0).toBool();
}
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResult &processResult, bool pathsOnly)
{ {
QString absPath = fileInfo.absoluteFilePath(); QString absPath = fileInfo.absoluteFilePath();
auto mtime = fileInfo.lastModified().toSecsSinceEpoch(); auto mtime = fileInfo.lastModified().toSecsSinceEpoch();
QChar fileType = fileInfo.isDir() ? 'd' : 'c'; QChar fileType = fileInfo.isDir() ? 'd' : 'f';
if(pathsOnly)
{
fileType = 'f';
}
QSqlDatabase db = dbFactory->forCurrentThread(); QSqlDatabase db = dbFactory->forCurrentThread();
QSqlQuery delQuery(db); QSqlQuery delQuery(db);
@ -343,29 +186,19 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResu
return DBFAIL; return DBFAIL;
} }
if(!pathsOnly)
{
int lastid = inserterQuery.lastInsertId().toInt(); int lastid = inserterQuery.lastInsertId().toInt();
if(!insertToFTS(false, db, lastid, processResult.pages)) if(!insertToFTS(false, db, lastid, pageData))
{ {
db.rollback(); db.rollback();
Logger::error() << "Failed to insert data to FTS index " << Qt::endl; Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
return DBFAIL; return DBFAIL;
} }
if(!insertToFTS(true, db, lastid, processResult.pages)) if(!insertToFTS(true, db, lastid, pageData))
{ {
db.rollback(); db.rollback();
Logger::error() << "Failed to insert data to FTS index " << Qt::endl; Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
return DBFAIL; return DBFAIL;
} }
if(!insertOutline(db, lastid, processResult.outlines))
{
db.rollback();
Logger::error() << "Failed to insert outline data " << Qt::endl;
return DBFAIL;
}
}
if(!db.commit()) if(!db.commit())
{ {
db.rollback(); db.rollback();
@ -374,123 +207,3 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResu
} }
return OK; return OK;
} }
bool SqliteDbService::addTag(QString tag, QString path)
{
QVector<QString> paths;
paths.append(path);
return addTag(tag, paths);
}
bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
{
QSqlDatabase db = dbFactory->forCurrentThread();
QSqlQuery tagQuery(db);
QSqlQuery fileTagQuery(db);
tag = tag.toLower();
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
tagQuery.addBindValue(tag);
fileTagQuery.prepare("INSERT INTO filetag(fileid, tagid) VALUES((SELECT id FROM file WHERE path = ?), (SELECT id "
"FROM tag WHERE name = ?))");
fileTagQuery.bindValue(1, tag);
if(!db.transaction())
{
Logger::error() << "Failed to open transaction to add paths for tag " << tag << " : " << db.lastError()
<< Qt::endl;
return false;
}
if(!tagQuery.exec())
{
db.rollback();
Logger::error() << "Failed INSERT query" << tagQuery.lastError() << Qt::endl;
return false;
}
for(const QString &path : paths)
{
fileTagQuery.bindValue(0, path);
if(!fileTagQuery.exec())
{
db.rollback();
Logger::error() << "Failed to add paths to tag" << Qt::endl;
return false;
}
}
if(!db.commit())
{
db.rollback();
Logger::error() << "Failed to commit tag insertion transaction" << db.lastError() << Qt::endl;
return false;
}
return true;
}
bool SqliteDbService::removePathsForTag(QString tag, const QVector<QString> &paths)
{
QSqlDatabase db = dbFactory->forCurrentThread();
QSqlQuery tagQuery(db);
QSqlQuery fileTagQuery(db);
tag = tag.toLower();
fileTagQuery.prepare(
"DELETE FROM filetag WHERE fileid = (SELECT id FROM file WHERE path = ?) AND tagid = (SELECT id "
"FROM tag WHERE name = ?)");
fileTagQuery.bindValue(1, tag);
for(const QString &path : paths)
{
fileTagQuery.bindValue(0, path);
if(!fileTagQuery.exec())
{
Logger::error() << "An error occured while trying to remove paths from tag assignment" << Qt::endl;
return false;
}
}
return true;
}
bool SqliteDbService::deleteTag(QString tag)
{
QSqlDatabase db = dbFactory->forCurrentThread();
if(!db.transaction())
{
Logger::error() << "Failed to open transaction while trying to delete tag " << tag << " : " << db.lastError()
<< Qt::endl;
return false;
}
tag = tag.toLower();
QSqlQuery assignmentDeleteQuery(db);
assignmentDeleteQuery.prepare("DELETE FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)");
assignmentDeleteQuery.addBindValue(tag);
if(!assignmentDeleteQuery.exec())
{
db.rollback();
Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
return false;
}
QSqlQuery deleteTagQuery(db);
deleteTagQuery.prepare("DELETE FROM tag WHERE name = ?");
deleteTagQuery.addBindValue(tag);
if(!deleteTagQuery.exec())
{
db.rollback();
Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
return false;
}
if(!db.commit())
{
db.rollback();
Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
return false;
}
return true;
}

파일 보기

@ -1,11 +1,9 @@
#ifndef SQLITEDBSERVICE_H #ifndef SQLITEDBSERVICE_H
#define SQLITEDBSERVICE_H #define SQLITEDBSERVICE_H
#include <QFileInfo> #include <QFileInfo>
#include <optional>
#include "databasefactory.h" #include "databasefactory.h"
#include "utils.h" #include "utils.h"
#include "documentprocessresult.h" #include "pagedata.h"
#include "filedata.h" #include "filedata.h"
#include "../shared/sqlitesearch.h" #include "../shared/sqlitesearch.h"
#include "../shared/token.h" #include "../shared/token.h"
@ -17,32 +15,14 @@ class SqliteDbService
DatabaseFactory *dbFactory = nullptr; DatabaseFactory *dbFactory = nullptr;
bool insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData); bool insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData);
QSqlQuery exec(QString query, std::initializer_list<QVariant> args);
bool execBool(QString querystr, std::initializer_list<QVariant> args);
public: public:
SqliteDbService(DatabaseFactory &dbFactory); SqliteDbService(DatabaseFactory &dbFactory);
SaveFileResult saveFile(QFileInfo fileInfo, DocumentProcessResult &pageData, bool pathsOnly); SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData);
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
bool deleteFile(QString path); bool deleteFile(QString path);
bool fileExistsInDatabase(QString path); bool fileExistsInDatabase(QString path);
bool fileExistsInDatabase(QString path, qint64 mtime); bool fileExistsInDatabase(QString path, qint64 mtime);
bool fileExistsInDatabase(QString path, qint64 mtime, QChar filetype);
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
bool addTag(QString tag, QString path);
bool addTag(QString tag, const QVector<QString> &paths);
QVector<QString> getTags();
QVector<QString> getTagsForPath(QString path);
QVector<QString> getPathsForTag(QString path);
bool setTags(QString path, const QSet<QString> &tags);
bool removePathsForTag(QString tag, const QVector<QString> &paths);
bool deleteTag(QString tag);
QVector<SearchResult> search(const LooqsQuery &query); QVector<SearchResult> search(const LooqsQuery &query);
std::optional<QChar> queryFileType(QString absPath);
bool insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines);
}; };
#endif // SQLITEDBSERVICE_H #endif // SQLITEDBSERVICE_H

파일 보기

@ -69,7 +69,7 @@ QString SqliteSearch::createSortSql(const QVector<SortCondition> sortConditions)
QString SqliteSearch::escapeFtsArgument(QString ftsArg) QString SqliteSearch::escapeFtsArgument(QString ftsArg)
{ {
QString result; QString result;
static QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#"); QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#");
QRegularExpressionMatchIterator i = extractor.globalMatch(ftsArg); QRegularExpressionMatchIterator i = extractor.globalMatch(ftsArg);
while(i.hasNext()) while(i.hasNext())
{ {
@ -143,25 +143,15 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
{ {
return {" fts MATCH ? ", {escapeFtsArgument(value)}}; return {" fts MATCH ? ", {escapeFtsArgument(value)}};
} }
if(token.type == FILTER_TAG_ASSIGNED)
{
return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ",
{value.toLower()}};
}
if(token.type == FILTER_OUTLINE_CONTAINS)
{
return {" outline.text LIKE '%' || ? || '%' ", {value.toLower()}};
}
throw LooqsGeneralException("Unknown token passed (should not happen)"); throw LooqsGeneralException("Unknown token passed (should not happen)");
} }
QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query) QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
{ {
QString whereSql; QString whereSql;
QString joinSql;
QVector<QString> bindValues; QVector<QString> bindValues;
bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
bool isOutlineSearch = query.hasOutlineSearch();
if(query.getTokens().isEmpty()) if(query.getTokens().isEmpty())
{ {
throw LooqsGeneralException("Nothing to search for supplied"); throw LooqsGeneralException("Nothing to search for supplied");
@ -190,38 +180,31 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
} }
QString whereSqlTrigram = whereSql; QString whereSqlTrigram = whereSql;
whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty... whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
prepSql = "SELECT DISTINCT path, page, mtime, size, filetype FROM (" prepSql =
"SELECT DISTINCT path, page, mtime, size, filetype FROM ("
"SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, " "SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = " "file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
"content.fileid " "content.fileid "
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " + "INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
whereSql + whereSql +
"UNION ALL SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, " "UNION ALL SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
"file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON " "file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON file.id = "
"file.id = "
"content.fileid " + "content.fileid " +
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + "INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + whereSqlTrigram +
whereSqlTrigram + " ) " + sortSql; " ) " + sortSql;
++bindIterations; ++bindIterations;
} }
else else
{ {
QString pageColumn = "'0' as page";
QString joiners = "";
if(isOutlineSearch)
{
pageColumn = "outline.page as page";
joiners = " INNER JOIN outline ON outline.fileid = file.id ";
}
if(sortSql.isEmpty()) if(sortSql.isEmpty())
{ {
sortSql = "ORDER BY file.mtime DESC"; sortSql = "ORDER BY file.mtime DESC";
} }
prepSql = "SELECT DISTINCT file.path AS path, " + pageColumn + prepSql = "SELECT file.path AS path, '0' as page, file.mtime AS mtime, file.size AS size, file.filetype AS "
",file.mtime AS mtime, file.size AS size, " "filetype FROM file WHERE 1=1 AND " +
"file.filetype AS filetype FROM file" + whereSql + " " + sortSql;
joiners + " WHERE 1=1 AND " + whereSql + " " + sortSql;
} }
if(query.getLimit() > 0) if(query.getLimit() > 0)
{ {
prepSql += " LIMIT " + QString::number(query.getLimit()); prepSql += " LIMIT " + QString::number(query.getLimit());
@ -255,7 +238,7 @@ QVector<SearchResult> SqliteSearch::search(const LooqsQuery &query)
throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text()); throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text());
} }
bool contentSearch = query.hasContentSearch() || query.hasOutlineSearch(); bool contentSearch = query.hasContentSearch();
while(dbQuery.next()) while(dbQuery.next())
{ {
SearchResult result; SearchResult result;

파일 보기

@ -1,66 +0,0 @@
#include "tagmanager.h"
TagManager::TagManager(SqliteDbService &dbService)
{
this->dbService = &dbService;
}
bool TagManager::addTagsToPath(QString path, const QSet<QString> &tags)
{
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
for(const QString &tag : tags)
{
currentTags.append(tag.toLower());
}
QSet<QString> newTags{currentTags.begin(), currentTags.end()};
return this->dbService->setTags(path, newTags);
}
bool TagManager::removeTagsForPath(QString path, const QSet<QString> &tags)
{
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
for(const QString &tag : tags)
{
currentTags.removeAll(tag);
}
QSet<QString> newTags{currentTags.begin(), currentTags.end()};
return this->dbService->setTags(path, newTags);
}
bool TagManager::removePathsForTag(QString tag, const QVector<QString> &paths)
{
return this->dbService->removePathsForTag(tag, paths);
}
bool TagManager::deleteTag(QString tag)
{
return this->dbService->deleteTag(tag);
}
QVector<QString> TagManager::getTags(QString path)
{
return this->dbService->getTagsForPath(path);
}
QVector<QString> TagManager::getTags()
{
return this->dbService->getTags();
}
QVector<QString> TagManager::getPaths(QString tag)
{
return this->dbService->getPathsForTag(tag);
}
bool TagManager::addTagsToPath(QString path, QString tagstring, QChar delim)
{
auto splitted = tagstring.split(delim);
return addTagsToPath(path, QSet<QString>{splitted.begin(), splitted.end()});
}
bool TagManager::addPathsToTag(QString tag, const QVector<QString> &paths)
{
return this->dbService->addTag(tag, paths);
}

파일 보기

@ -1,28 +0,0 @@
#ifndef TAGMANAGER_H
#define TAGMANAGER_H
#include "sqlitedbservice.h"
class TagManager
{
private:
SqliteDbService *dbService = nullptr;
bool ensurePathOkay(QString inpath);
public:
TagManager(SqliteDbService &dbService);
bool addTagsToPath(QString path, const QSet<QString> &tags);
bool addTagsToPath(QString path, QString tagstring, QChar delim);
bool addPathsToTag(QString tag, const QVector<QString> &paths);
bool removeTagsForPath(QString path, const QSet<QString> &tags);
bool removePathsForTag(QString tag, const QVector<QString> &paths);
bool deleteTag(QString tag);
QVector<QString> getTags(QString path);
QVector<QString> getTags();
QVector<QString> getPaths(QString tag);
};
#endif // TAGMANAGER_H

파일 보기

@ -4,11 +4,11 @@ TagStripperProcessor::TagStripperProcessor()
{ {
} }
DocumentProcessResult TagStripperProcessor::process(const QByteArray &data) const QVector<PageData> TagStripperProcessor::process(const QByteArray &data) const
{ {
auto result = DefaultTextProcessor::process(data); auto result = DefaultTextProcessor::process(data);
// TODO: does not work properly with <br> and does not deal with entities... // TODO: does not work properly with <br> and does not deal with entities...
Q_ASSERT(result.pages.size() > 0);
result.pages[0].content.remove(QRegExp("<[^>]*>")); result[0].content.remove(QRegExp("<[^>]*>"));
return result; return result;
} }

파일 보기

@ -8,7 +8,7 @@ class TagStripperProcessor : public DefaultTextProcessor
TagStripperProcessor(); TagStripperProcessor();
public: public:
DocumentProcessResult process(const QByteArray &data) const override; QVector<PageData> process(const QByteArray &data) const override;
}; };
#endif // XMLSTRIPPERPROCESSOR_H #endif // XMLSTRIPPERPROCESSOR_H

파일 보기

@ -19,9 +19,7 @@ enum TokenType
FILTER_PATH_SIZE, FILTER_PATH_SIZE,
FILTER_PATH_ENDS, FILTER_PATH_ENDS,
FILTER_PATH_STARTS, FILTER_PATH_STARTS,
FILTER_TAG_ASSIGNED, FILTER_CONTENT = 512,
FILTER_OUTLINE_CONTAINS,
FILTER_CONTENT = 512, /* Everything below here is content search (except LIMIT) */
FILTER_CONTENT_CONTAINS, FILTER_CONTENT_CONTAINS,
FILTER_CONTENT_PAGE, FILTER_CONTENT_PAGE,
LIMIT = 1024 LIMIT = 1024