Compare commits

..

No commits in common. "595684c36457065b3786ff7a52a8e3e8a1abb389" and "20a1f8b2cd2687df95732ff32ebd6a497b48bdae" have entirely different histories.

49 changed files with 367 additions and 1323 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -34,7 +34,6 @@ SOURCES += \
main.cpp \ main.cpp \
mainwindow.cpp \ mainwindow.cpp \
clicklabel.cpp \ clicklabel.cpp \
previewcoordinator.cpp \
previewgenerator.cpp \ previewgenerator.cpp \
previewgeneratormapfunctor.cpp \ previewgeneratormapfunctor.cpp \
previewgeneratorodt.cpp \ previewgeneratorodt.cpp \
@ -55,7 +54,6 @@ HEADERS += \
ipcserver.h \ ipcserver.h \
mainwindow.h \ mainwindow.h \
clicklabel.h \ clicklabel.h \
previewcoordinator.h \
previewgenerator.h \ previewgenerator.h \
previewgeneratormapfunctor.h \ previewgeneratormapfunctor.h \
previewgeneratorodt.h \ previewgeneratorodt.h \

View File

@ -28,7 +28,7 @@ void enableIpcSandbox()
policy->namespace_options = EXILE_UNSHARE_USER | EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_NETWORK; policy->namespace_options = EXILE_UNSHARE_USER | EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_NETWORK;
policy->no_new_privs = 1; policy->no_new_privs = 1;
policy->drop_caps = 1; policy->drop_caps = 1;
policy->vow_promises = exile_vows_from_str("thread cpath rpath wpath unix stdio proc error"); policy->vow_promises = exile_vows_from_str("thread cpath rpath unix stdio proc error");
policy->mount_path_policies_to_chroot = 1; policy->mount_path_policies_to_chroot = 1;
QString ipcSocketPath = Common::ipcSocketPath(); QString ipcSocketPath = Common::ipcSocketPath();
@ -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();
} }
); );

View File

@ -16,15 +16,14 @@
#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 "ipcpreviewclient.h"
#include "previewgenerator.h"
#include "aboutdialog.h" #include "aboutdialog.h"
MainWindow::MainWindow(QWidget *parent, QString socketPath) MainWindow::MainWindow(QWidget *parent, QString socketPath)
@ -33,7 +32,8 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
this->progressDialog.cancel(); // because constructing it shows it, quite weird this->progressDialog.cancel(); // because constructing it shows it, quite weird
ui->setupUi(this); ui->setupUi(this);
setWindowTitle(QCoreApplication::applicationName()); setWindowTitle(QCoreApplication::applicationName());
this->ipcPreviewClient.moveToThread(&this->ipcClientThread);
this->ipcPreviewClient.setSocketPath(socketPath);
QSettings settings; QSettings settings;
this->dbFactory = new DatabaseFactory(Common::databasePath()); this->dbFactory = new DatabaseFactory(Common::databasePath());
@ -45,9 +45,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);
@ -81,7 +78,7 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
ui->txtSearch->installEventFilter(this); ui->txtSearch->installEventFilter(this);
ui->scrollArea->viewport()->installEventFilter(this); ui->scrollArea->viewport()->installEventFilter(this);
this->previewCoordinator.setSocketPath(socketPath); this->ipcClientThread.start();
} }
void MainWindow::addPathToIndex() void MainWindow::addPathToIndex()
@ -153,7 +150,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 +170,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 +190,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);
@ -210,9 +208,9 @@ void MainWindow::connectSignals()
} }
}, },
Qt::QueuedConnection); Qt::QueuedConnection);
connect(&previewCoordinator, &PreviewCoordinator::previewReady, this, &MainWindow::previewReceived, connect(&ipcPreviewClient, &IPCPreviewClient::previewReceived, this, &MainWindow::previewReceived,
Qt::QueuedConnection); Qt::QueuedConnection);
connect(&previewCoordinator, &PreviewCoordinator::completedGeneration, this, connect(&ipcPreviewClient, &IPCPreviewClient::finished, this,
[&] [&]
{ {
this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->maximum()); this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->maximum());
@ -220,24 +218,22 @@ void MainWindow::connectSignals()
this->ui->comboPreviewFiles->setEnabled(true); this->ui->comboPreviewFiles->setEnabled(true);
ui->txtSearch->setEnabled(true); ui->txtSearch->setEnabled(true);
}); });
connect(&previewCoordinator, &PreviewCoordinator::error, this, connect(&ipcPreviewClient, &IPCPreviewClient::error, this,
[this](QString msg) [this](QString msg)
{ {
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, &ipcPreviewClient, &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 +266,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 +286,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 +295,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 +311,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 +333,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 +340,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 +386,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);
@ -654,17 +632,13 @@ void MainWindow::saveSettings()
qApp->quit(); qApp->quit();
} }
void MainWindow::previewReceived() void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration)
{ {
this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->value() + 1); if(previewGeneration < this->currentPreviewGeneration)
QBoxLayout *layout = static_cast<QBoxLayout *>(ui->scrollAreaWidgetContents->layout());
int index = layout->count();
if(index > 0)
{ {
--index; return;
} }
QSharedPointer<PreviewResult> preview = this->previewCoordinator.resultAt(index); this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->value() + 1);
if(!preview.isNull() && preview->hasPreview()) if(!preview.isNull() && preview->hasPreview())
{ {
QString docPath = preview->getDocumentPath(); QString docPath = preview->getDocumentPath();
@ -687,8 +661,8 @@ void MainWindow::previewReceived()
{ {
QFileInfo fileInfo{docPath}; QFileInfo fileInfo{docPath};
QMenu menu("labeRightClick", this); QMenu menu("labeRightClick", this);
createSearchResultMenu(menu, fileInfo); createSearchResutlMenu(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());
}; };
@ -710,7 +684,24 @@ void MainWindow::previewReceived()
previewWidget->setLayout(previewLayout); previewWidget->setLayout(previewLayout);
layout->insertWidget(index, previewWidget); QBoxLayout *layout = static_cast<QBoxLayout *>(ui->scrollAreaWidgetContents->layout());
int pos = previewOrder[docPath + QString::number(previewPage)];
if(pos <= layout->count())
{
layout->insertWidget(pos, previewWidget);
for(auto it = previewWidgetOrderCache.constKeyValueBegin();
it != previewWidgetOrderCache.constKeyValueEnd(); it++)
{
if(it->first <= layout->count())
{
layout->insertWidget(it->first, it->second);
}
}
}
else
{
previewWidgetOrderCache[pos] = previewWidget;
}
} }
} }
@ -827,6 +818,7 @@ void MainWindow::lineEditReturnPressed()
void MainWindow::handleSearchResults(const QVector<SearchResult> &results) void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
{ {
this->previewableSearchResults.clear();
qDeleteAll(ui->scrollAreaWidgetContents->children()); qDeleteAll(ui->scrollAreaWidgetContents->children());
ui->treeResultsList->clear(); ui->treeResultsList->clear();
@ -835,8 +827,6 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
ui->comboPreviewFiles->setVisible(true); ui->comboPreviewFiles->setVisible(true);
ui->lblTotalPreviewPagesCount->setText(""); ui->lblTotalPreviewPagesCount->setText("");
this->previewCoordinator.init(results);
bool hasDeleted = false; bool hasDeleted = false;
QHash<QString, bool> seenMap; QHash<QString, bool> seenMap;
for(const SearchResult &result : results) for(const SearchResult &result : results)
@ -857,29 +847,34 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
item->setText(3, this->locale().formattedDataSize(result.fileData.size)); item->setText(3, this->locale().formattedDataSize(result.fileData.size));
} }
bool exists = pathInfo.exists(); bool exists = pathInfo.exists();
if(!exists) if(exists)
{
if(result.wasContentSearch)
{
if(!pathInfo.suffix().contains("htm")) // hack until we can preview them properly...
{
if(PreviewGenerator::get(pathInfo) != nullptr)
{
this->previewableSearchResults.append(result);
if(!seenMap.contains(result.fileData.absPath))
{
ui->comboPreviewFiles->addItem(result.fileData.absPath);
}
}
}
}
}
else
{ {
hasDeleted = true; hasDeleted = true;
} }
seenMap[absPath] = true; seenMap[absPath] = true;
} }
seenMap.clear();
for(const SearchResult &result : this->previewCoordinator.getPreviewableSearchResults())
{
const QString &absPath = result.fileData.absPath;
if(!seenMap.contains(absPath))
{
ui->comboPreviewFiles->addItem(absPath);
}
seenMap[absPath] = true;
}
ui->treeResultsList->resizeColumnToContents(0); ui->treeResultsList->resizeColumnToContents(0);
ui->treeResultsList->resizeColumnToContents(1); ui->treeResultsList->resizeColumnToContents(1);
ui->treeResultsList->resizeColumnToContents(2); ui->treeResultsList->resizeColumnToContents(2);
previewDirty = !this->previewableSearchResults.empty();
previewDirty = this->previewCoordinator.previewableCount() > 0;
ui->spinPreviewPage->setValue(1); ui->spinPreviewPage->setValue(1);
@ -888,10 +883,8 @@ 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->previewableSearchResults.count());
if(hasDeleted) if(hasDeleted)
{ {
statusText += " WARNING: Some files are inaccessible. No preview available for those. Index may be out of sync"; statusText += " WARNING: Some files are inaccessible. No preview available for those. Index may be out of sync";
@ -908,7 +901,7 @@ int MainWindow::currentSelectedScale()
void MainWindow::makePreviews(int page) void MainWindow::makePreviews(int page)
{ {
if(this->previewCoordinator.previewableCount() == 0) if(this->previewableSearchResults.empty())
{ {
return; return;
} }
@ -925,10 +918,11 @@ void MainWindow::makePreviews(int page)
ui->scrollAreaWidgetContents->setLayout(new QVBoxLayout()); ui->scrollAreaWidgetContents->setLayout(new QVBoxLayout());
ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignCenter); ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignCenter);
} }
ui->previewProcessBar->setMaximum(this->previewCoordinator.previewableCount()); ui->previewProcessBar->setMaximum(this->previewableSearchResults.size());
processedPdfPreviews = 0;
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,8 +954,12 @@ 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;
this->previewOrder.clear();
this->previewWidgetOrderCache.clear();
int previewPos = 0;
QVector<RenderTarget> targets; QVector<RenderTarget> targets;
for(const SearchResult &sr : this->previewCoordinator.getPreviewableSearchResults()) for(SearchResult &sr : this->previewableSearchResults)
{ {
if(ui->comboPreviewFiles->currentIndex() != 0) if(ui->comboPreviewFiles->currentIndex() != 0)
{ {
@ -973,8 +971,11 @@ void MainWindow::makePreviews(int page)
RenderTarget renderTarget; RenderTarget renderTarget;
renderTarget.path = sr.fileData.absPath; renderTarget.path = sr.fileData.absPath;
renderTarget.page = (int)sr.page; renderTarget.page = (int)sr.page;
targets.append(renderTarget); targets.append(renderTarget);
int pos = previewPos - beginOffset;
this->previewOrder[renderTarget.path + QString::number(renderTarget.page)] = pos;
++previewPos;
} }
int numpages = ceil(static_cast<double>(targets.size()) / previewsPerPage); int numpages = ceil(static_cast<double>(targets.size()) / previewsPerPage);
ui->spinPreviewPage->setMaximum(numpages); ui->spinPreviewPage->setMaximum(numpages);
@ -984,12 +985,12 @@ void MainWindow::makePreviews(int page)
ui->previewProcessBar->setMaximum(targets.count()); ui->previewProcessBar->setMaximum(targets.count());
ui->previewProcessBar->setMinimum(0); ui->previewProcessBar->setMinimum(0);
ui->previewProcessBar->setValue(0); ui->previewProcessBar->setValue(0);
ui->previewProcessBar->setVisible(this->previewCoordinator.previewableCount() > 0); ui->previewProcessBar->setVisible(this->previewableSearchResults.size() > 0);
++this->currentPreviewGeneration;
this->ui->spinPreviewPage->setEnabled(false); this->ui->spinPreviewPage->setEnabled(false);
this->ui->comboPreviewFiles->setEnabled(false); this->ui->comboPreviewFiles->setEnabled(false);
this->ui->txtSearch->setEnabled(false); this->ui->txtSearch->setEnabled(false);
emit startIpcPreviews(renderConfig, targets);
this->previewCoordinator.startGeneration(renderConfig, targets);
} }
void MainWindow::handleSearchError(QString error) void MainWindow::handleSearchError(QString error)
@ -997,87 +998,27 @@ void MainWindow::handleSearchError(QString error)
ui->lblSearchResults->setText("Error:" + error); ui->lblSearchResults->setText("Error:" + error);
} }
void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo) void MainWindow::createSearchResutlMenu(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 result = auto result =
std::find_if(previewables.begin(), previewables.end(), std::find_if(this->previewableSearchResults.begin(), this->previewableSearchResults.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 != this->previewableSearchResults.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 +1048,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));
} }
@ -1121,22 +1062,22 @@ void MainWindow::showSearchResultsContextMenu(const QPoint &point)
} }
QFileInfo pathinfo(item->text(1)); QFileInfo pathinfo(item->text(1));
QMenu menu("SearchResults", this); QMenu menu("SearchResults", this);
createSearchResultMenu(menu, pathinfo); createSearchResutlMenu(menu, pathinfo);
menu.exec(QCursor::pos()); menu.exec(QCursor::pos());
} }
MainWindow::~MainWindow() MainWindow::~MainWindow()
{ {
syncerThread.terminate(); syncerThread.terminate();
ipcClientThread.terminate();
delete this->indexSyncer; delete this->indexSyncer;
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;

View File

@ -12,9 +12,8 @@
#include <QProgressDialog> #include <QProgressDialog>
#include "../shared/looqsquery.h" #include "../shared/looqsquery.h"
#include "../shared/indexsyncer.h" #include "../shared/indexsyncer.h"
#include "previewcoordinator.h" #include "ipcpreviewclient.h"
#include "indexer.h" #include "indexer.h"
#include "tagmanager.h"
namespace Ui namespace Ui
{ {
class MainWindow; class MainWindow;
@ -28,9 +27,8 @@ class MainWindow : public QMainWindow
DatabaseFactory *dbFactory; DatabaseFactory *dbFactory;
SqliteDbService *dbService; SqliteDbService *dbService;
Ui::MainWindow *ui; Ui::MainWindow *ui;
IPCPreviewClient ipcPreviewClient;
PreviewCoordinator previewCoordinator; QThread ipcClientThread;
QThread syncerThread; QThread syncerThread;
Indexer *indexer; Indexer *indexer;
IndexSyncer *indexSyncer; IndexSyncer *indexSyncer;
@ -38,15 +36,18 @@ class MainWindow : public QMainWindow
QFileIconProvider iconProvider; QFileIconProvider iconProvider;
QSqlDatabase db; QSqlDatabase db;
QFutureWatcher<QVector<SearchResult>> searchWatcher; QFutureWatcher<QVector<SearchResult>> searchWatcher;
QVector<SearchResult> previewableSearchResults;
LooqsQuery contentSearchQuery; LooqsQuery contentSearchQuery;
QVector<QString> searchHistory; QVector<QString> searchHistory;
TagManager *tagManager;
int currentSearchHistoryIndex = 0; int currentSearchHistoryIndex = 0;
QString currentSavedSearchText; QString currentSavedSearchText;
QHash<QString, int> previewOrder; /* Quick lookup for the order a preview should have */
QMap<int, QWidget *>
previewWidgetOrderCache /* Saves those that arrived out of order to be inserted later at the correct pos */;
bool previewDirty = false; bool previewDirty = false;
int previewsPerPage = 20; int previewsPerPage = 20;
unsigned int processedPdfPreviews = 0;
unsigned int currentPreviewGeneration = 1;
void connectSignals(); void connectSignals();
void makePreviews(int page); void makePreviews(int page);
@ -55,20 +56,20 @@ class MainWindow : public QMainWindow
void keyPressEvent(QKeyEvent *event) override; void keyPressEvent(QKeyEvent *event) override;
void handleSearchResults(const QVector<SearchResult> &results); void handleSearchResults(const QVector<SearchResult> &results);
void handleSearchError(QString error); void handleSearchError(QString error);
void createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo); void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo);
void openDocument(QString path, int num); void openDocument(QString path, int num);
void openFile(QString path); void openFile(QString path);
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();
void treeSearchItemActivated(QTreeWidgetItem *item, int i); void treeSearchItemActivated(QTreeWidgetItem *item, int i);
void showSearchResultsContextMenu(const QPoint &point); void showSearchResultsContextMenu(const QPoint &point);
void tabChanged(); void tabChanged();
void previewReceived(); void previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration);
void comboScaleChanged(int i); void comboScaleChanged(int i);
void spinPreviewPageValueChanged(int val); void spinPreviewPageValueChanged(int val);
void startIndexing(); void startIndexing();

View File

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

View File

@ -1,97 +0,0 @@
#include "previewcoordinator.h"
#include <QFileInfo>
PreviewCoordinator::PreviewCoordinator()
{
this->ipcPreviewClient.moveToThread(&this->ipcClientThread);
connect(&ipcPreviewClient, &IPCPreviewClient::previewReceived, this, &PreviewCoordinator::handleReceivedPreview,
Qt::QueuedConnection);
connect(&ipcPreviewClient, &IPCPreviewClient::finished, this, [&] { emit completedGeneration(); });
connect(this, &PreviewCoordinator::ipcStartGeneration, &ipcPreviewClient, &IPCPreviewClient::startGeneration,
Qt::QueuedConnection);
this->ipcClientThread.start();
}
void PreviewCoordinator::init(const QVector<SearchResult> &searchResults)
{
this->previewableSearchResults.clear();
for(const SearchResult &result : searchResults)
{
if(result.wasContentSearch)
{
QString path = result.fileData.absPath;
// HACK until we can preview them properly
if(path.endsWith(".html") || path.endsWith(".htm"))
{
continue;
}
QFileInfo info{path};
if(info.exists())
{
this->previewableSearchResults.append(result);
}
}
}
}
void PreviewCoordinator::setSocketPath(QString socketPath)
{
this->socketPath = socketPath;
this->ipcPreviewClient.setSocketPath(socketPath);
}
int PreviewCoordinator::previewableCount() const
{
return this->previewableSearchResults.count();
}
QSharedPointer<PreviewResult> PreviewCoordinator::resultAt(int index)
{
if(this->previewResults.size() > index)
{
return {this->previewResults[index]};
}
return {nullptr};
}
const QVector<SearchResult> &PreviewCoordinator::getPreviewableSearchResults() const
{
return this->previewableSearchResults;
}
void PreviewCoordinator::handleReceivedPreview(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration)
{
if(previewGeneration < this->currentPreviewGeneration)
{
return;
}
if(!preview.isNull() && preview->hasPreview())
{
QString docPath = preview->getDocumentPath();
auto previewPage = preview->getPage();
int pos = previewOrder[docPath + QString::number(previewPage)];
this->previewResults[pos] = preview;
emit previewReady();
}
}
void PreviewCoordinator::startGeneration(RenderConfig config, const QVector<RenderTarget> &targets)
{
++this->currentPreviewGeneration;
this->previewOrder.clear();
this->previewResults.clear();
this->previewResults.resize(targets.size());
this->previewResults.fill(nullptr);
int i = 0;
for(const RenderTarget &target : targets)
{
this->previewOrder[target.path + QString::number(target.page)] = i++;
}
emit ipcStartGeneration(config, targets);
}

View File

@ -1,48 +0,0 @@
#ifndef PREVIEWCOORDINATOR_H
#define PREVIEWCOORDINATOR_H
#include <QVector>
#include <QObject>
#include <QThread>
#include "searchresult.h"
#include "previewresult.h"
#include "ipcpreviewclient.h"
#include "rendertarget.h"
class PreviewCoordinator : public QObject
{
Q_OBJECT
private:
QThread ipcClientThread;
IPCPreviewClient ipcPreviewClient;
QString socketPath;
QVector<QSharedPointer<PreviewResult>> previewResults;
QVector<SearchResult> previewableSearchResults;
unsigned int currentPreviewGeneration = 1;
/* Quick lookup table for the order a preview should have */
QHash<QString, int> previewOrder;
public:
PreviewCoordinator();
void init(const QVector<SearchResult> &searchResults);
int previewableCount() const;
const QVector<SearchResult> &getPreviewableSearchResults() const;
QSharedPointer<PreviewResult> resultAt(int index);
void setSocketPath(QString socketPath);
public slots:
void startGeneration(RenderConfig config, const QVector<RenderTarget> &targets);
void handleReceivedPreview(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration);
signals:
void previewReady();
void completedGeneration();
void error(QString);
void ipcStartGeneration(RenderConfig config, const QVector<RenderTarget> &targets);
};
#endif // PREVIEWCOORDINATOR_H

View File

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

View File

@ -20,8 +20,6 @@ Poppler::Document *PreviewGeneratorPdf::document(QString path)
return nullptr; return nullptr;
} }
result->setRenderHint(Poppler::Document::TextAntialiasing); result->setRenderHint(Poppler::Document::TextAntialiasing);
result->setRenderHint(Poppler::Document::TextHinting);
result->setRenderHint(Poppler::Document::TextSlightHinting);
locker.relock(); locker.relock();
documentcache.insert(path, result); documentcache.insert(path, result);

View File

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

View File

@ -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)
{ {
@ -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;
@ -185,7 +159,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
} }
} }
} }
SaveFileResult result = this->dbService->saveFile(fileInfo, pageData, 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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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";

View File

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

View File

@ -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
{ {
@ -181,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); };
@ -285,10 +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;
}
// 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")
{ {

View File

@ -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;
} }
@ -91,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

View File

@ -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';

View File

@ -4,6 +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>
</qresource> </qresource>
</RCC> </RCC>

View File

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

View File

@ -3,7 +3,7 @@
#include "odtprocessor.h" #include "odtprocessor.h"
#include "tagstripperprocessor.h" #include "tagstripperprocessor.h"
QVector<PageData> OdtProcessor::process(const QByteArray & /*data*/) const QVector<PageData> OdtProcessor::process(const QByteArray &data) const
{ {
throw LooqsGeneralException("Not implemented yet"); throw LooqsGeneralException("Not implemented yet");
} }

View File

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

View File

@ -60,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 \
@ -75,7 +74,6 @@ HEADERS += sqlitesearch.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 \
@ -94,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 \

View File

@ -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 execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ? AND filetype = ?", {path, mtime, fileType}); return false;
}
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,40 +148,11 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid
return true; return true;
} }
QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args) SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData)
{
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, QVector<PageData> &pageData, 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);
@ -320,8 +186,6 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
return DBFAIL; return DBFAIL;
} }
if(!pathsOnly)
{
int lastid = inserterQuery.lastInsertId().toInt(); int lastid = inserterQuery.lastInsertId().toInt();
if(!insertToFTS(false, db, lastid, pageData)) if(!insertToFTS(false, db, lastid, pageData))
{ {
@ -335,8 +199,6 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
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(!db.commit()) if(!db.commit())
{ {
db.rollback(); db.rollback();
@ -345,123 +207,3 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
} }
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;
}

View File

@ -1,8 +1,6 @@
#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 "pagedata.h" #include "pagedata.h"
@ -17,31 +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, QVector<PageData> &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);
}; };
#endif // SQLITEDBSERVICE_H #endif // SQLITEDBSERVICE_H

View File

@ -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,17 +143,13 @@ 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()}};
}
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;
if(query.getTokens().isEmpty()) if(query.getTokens().isEmpty())
@ -184,18 +180,18 @@ 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

View File

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

View File

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

View File

@ -19,8 +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_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

@ -1 +1 @@
Subproject commit 44b9a17becf6882e1b3728cbf885ae9e5a6717af Subproject commit 769f729dc51f2feb8bc3cbb2a48ed91ff2d56bf3