Compare commits
38 Commits
20a1f8b2cd
...
595684c364
Author | SHA1 | Date | |
---|---|---|---|
595684c364 | |||
22fee1d064 | |||
50a5c399c4 | |||
4b3ebb08c2 | |||
4c5643e342 | |||
e8d217e191 | |||
4604970f9d | |||
6cd7a92576 | |||
9540f27c95 | |||
244e6aa95e | |||
3e3a4d0cd4 | |||
94fbdb5a92 | |||
abd1b94235 | |||
d2dcc2f95b | |||
f324da0369 | |||
a3cfb7ade1 | |||
44b9986166 | |||
4fe745e858 | |||
a0b95479e2 | |||
07630c3b36 | |||
a7c4ad5e7c | |||
32c2653b0f | |||
a869d677a3 | |||
2550af307f | |||
0b829215e5 | |||
566c4a8c58 | |||
3d0c236cb3 | |||
590a8888fc | |||
ccc4d09b36 | |||
8298b675aa | |||
71789b5b56 | |||
363d207ccc | |||
4b1522b82a | |||
efca45b88a | |||
0cd19b53e4 | |||
889725033a | |||
8485a25b21 | |||
57f0afaf91 |
19
CHANGELOG.md
19
CHANGELOG.md
@ -1,5 +1,24 @@
|
||||
# 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
|
||||
|
||||
CHANGES:
|
||||
|
2
LICENSE
2
LICENSE
@ -1,4 +1,4 @@
|
||||
Copyright (c) 2018-2022: Albert Schwarzkopf <looqs at quitesimple period org>
|
||||
Copyright (c) 2018-2023: Albert Schwarzkopf <looqs at quitesimple period org>
|
||||
|
||||
looqs is made available under the following license:
|
||||
|
||||
|
10
README.md
10
README.md
@ -76,7 +76,7 @@ To build on Ubuntu and Debian, clone the repo and then run:
|
||||
```
|
||||
git submodule init
|
||||
git submodule update
|
||||
sudo apt install build-essential qtbase5-dev libpoppler-qt5-dev libuchardet-dev libquazip5-dev
|
||||
sudo apt install build-essential qtbase5-dev libqt5sql5-sqlite libpoppler-qt5-dev libuchardet-dev libquazip5-dev
|
||||
qmake
|
||||
make
|
||||
```
|
||||
@ -97,7 +97,9 @@ The GUI is located in `gui/looqs-gui`, the binary for the CLI is in `cli/looqs`
|
||||
## 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.
|
||||
```
|
||||
# First, obtain key, assume it's trusted.
|
||||
@ -108,6 +110,8 @@ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] ht
|
||||
sudo apt-get update
|
||||
sudo apt-get install looqs
|
||||
```
|
||||
### Gentoo (EXPERIMENTAL)
|
||||
Available in this overlay: https://github.com/quitesimpleorg/quitesimple-overlay
|
||||
|
||||
### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL)
|
||||
looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is
|
||||
@ -134,7 +138,7 @@ An AppImage may accompany the tarball in the future.
|
||||
|
||||
|
||||
### Other distros
|
||||
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!
|
||||
I appreciate help for others distros. If you create a package, let me know!
|
||||
|
||||
|
||||
### Signature verification
|
||||
|
12
USAGE.md
12
USAGE.md
@ -165,6 +165,8 @@ A number of search filters are available.
|
||||
| path.begins:(term) | pb:(term) | Filters path beginning with the specified term |
|
||||
| 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 |
|
||||
| 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 "!".
|
||||
The AND boolean is implicit and thus entering it strictly optional.
|
||||
|
||||
@ -177,11 +179,5 @@ Examples:
|
||||
|p:(notes) (pe:(odt) OR pe:(docx)) |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.|
|
||||
|c:("I think, therefore")|Performs a FTS search for the phrase "I think, therefore".|
|
||||
|c:("invoice") Downloads|This query is equivalent to c:("invoice") p:("Downloads")|
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|c:("invoice") Downloads|Equivalent to c:("invoice") p:("Downloads")|
|
||||
|p:(Downloads) invoice|Equivalent to c:("invoice") p:("Downloads")|
|
||||
|
@ -15,6 +15,7 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||
# 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
|
||||
SOURCES += \
|
||||
commandtag.cpp \
|
||||
main.cpp \
|
||||
commandadd.cpp \
|
||||
commanddelete.cpp \
|
||||
@ -27,6 +28,7 @@ HEADERS += \
|
||||
command.h \
|
||||
commandadd.h \
|
||||
commanddelete.h \
|
||||
commandtag.h \
|
||||
commandupdate.h \
|
||||
commandsearch.h \
|
||||
commandlist.h
|
||||
|
@ -2,7 +2,6 @@
|
||||
#include <QThread>
|
||||
#include <QDebug>
|
||||
#include "command.h"
|
||||
#include "looqsgeneralexception.h"
|
||||
|
||||
void Command::execute()
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ void CommandAdd::indexerFinished()
|
||||
if(failedPathsCount > 0)
|
||||
{
|
||||
Logger::info() << "Failed paths: " << Qt::endl;
|
||||
for(QString paths : result.failedPaths())
|
||||
for(const QString &paths : result.failedPaths())
|
||||
{
|
||||
Logger::info() << paths << Qt::endl;
|
||||
}
|
||||
@ -41,23 +41,36 @@ int CommandAdd::handle(QStringList arguments)
|
||||
{
|
||||
QCommandLineParser parser;
|
||||
parser.addOptions({{{"c", "continue"},
|
||||
"Continue adding files, don't exit on first error. If this option is not given, looqs will "
|
||||
"exit asap, but it's possible that a few files will still be processed. "
|
||||
"Continue adding files, don't exit on first error. Exit code will be 0. If this option is not "
|
||||
"given, looqs will "
|
||||
"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. "},
|
||||
{{"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"}});
|
||||
|
||||
parser.addHelpOption();
|
||||
parser.addPositionalArgument("add", "Add paths to the index",
|
||||
"add [paths...]. If no path is given, read from stdin, one path per line.");
|
||||
|
||||
parser.process(arguments);
|
||||
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"))
|
||||
{
|
||||
QString threadsCount = parser.value("threads");
|
||||
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();
|
||||
|
||||
if(files.length() == 0)
|
||||
@ -71,15 +84,47 @@ 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->setFileSaverOptions(fileSaverOptions);
|
||||
|
||||
indexer->setTargetPaths(files.toVector());
|
||||
indexer->setKeepGoing(keepGoing);
|
||||
|
||||
if(verbose)
|
||||
{
|
||||
indexer->setProgressReportThreshold(1);
|
||||
}
|
||||
|
||||
connect(indexer, &Indexer::pathsCountChanged, this,
|
||||
[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; });
|
||||
connect(indexer, &Indexer::indexProgress, this,
|
||||
[](int pathsCount, unsigned int added, unsigned int skipped, unsigned int failed, unsigned int totalCount)
|
||||
{ Logger::info() << "Processed files: " << pathsCount << Qt::endl; });
|
||||
[verbose, this](int pathsCount, unsigned int /*added*/, unsigned int /*skipped*/, unsigned int /*failed*/,
|
||||
unsigned int /*totalCount*/)
|
||||
{
|
||||
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);
|
||||
|
||||
this->autoFinish = false;
|
||||
|
@ -13,6 +13,8 @@ class CommandAdd : public Command
|
||||
bool keepGoing = true;
|
||||
|
||||
protected:
|
||||
IndexResult currentResult;
|
||||
|
||||
public:
|
||||
using Command::Command;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include <QCommandLineParser>
|
||||
#include "commandlist.h"
|
||||
#include "databasefactory.h"
|
||||
#include "logger.h"
|
||||
|
||||
int CommandList::handle(QStringList arguments)
|
||||
|
@ -1,6 +1,5 @@
|
||||
#include <QCommandLineParser>
|
||||
#include "commandsearch.h"
|
||||
#include "databasefactory.h"
|
||||
#include "logger.h"
|
||||
|
||||
int CommandSearch::handle(QStringList arguments)
|
||||
|
153
cli/commandtag.cpp
Normal file
153
cli/commandtag.cpp
Normal file
@ -0,0 +1,153 @@
|
||||
#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;
|
||||
}
|
16
cli/commandtag.h
Normal file
16
cli/commandtag.h
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef COMMANDTAG_H
|
||||
#define COMMANDTAG_H
|
||||
#include "command.h"
|
||||
|
||||
class CommandTag : public Command
|
||||
{
|
||||
protected:
|
||||
bool ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths);
|
||||
|
||||
public:
|
||||
using Command::Command;
|
||||
|
||||
int handle(QStringList arguments) override;
|
||||
};
|
||||
|
||||
#endif // COMMANDTAG_H
|
@ -38,10 +38,13 @@ int CommandUpdate::handle(QStringList arguments)
|
||||
QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt());
|
||||
}
|
||||
|
||||
bool hasErrors = false;
|
||||
IndexSyncer *syncer = new IndexSyncer(*this->dbService);
|
||||
syncer->setKeepGoing(keepGoing);
|
||||
syncer->setVerbose(verbose);
|
||||
|
||||
FileSaverOptions fileOptions;
|
||||
fileOptions.keepGoing = keepGoing;
|
||||
fileOptions.verbose = verbose;
|
||||
|
||||
syncer->setFileSaverOptions(fileOptions);
|
||||
syncer->setPattern(pattern);
|
||||
syncer->setDryRun(dryRun);
|
||||
syncer->setRemoveDeletedFromIndex(deleteMissing);
|
||||
@ -60,7 +63,7 @@ int CommandUpdate::handle(QStringList arguments)
|
||||
/* TODO: updated not printed, handled be verbose in FileSaver, but this can be improved */
|
||||
}
|
||||
connect(syncer, &IndexSyncer::finished, this,
|
||||
[&](unsigned int totalUpdated, unsigned int totalRemoved, unsigned int totalErrors)
|
||||
[this, dryRun, keepGoing](unsigned int totalUpdated, unsigned int totalRemoved, unsigned int totalErrors)
|
||||
{
|
||||
Logger::info() << "Syncing finished" << Qt::endl;
|
||||
|
||||
@ -72,7 +75,7 @@ int CommandUpdate::handle(QStringList arguments)
|
||||
}
|
||||
|
||||
int retval = 0;
|
||||
if(hasErrors && !keepGoing)
|
||||
if(this->hasErrors && !keepGoing)
|
||||
{
|
||||
retval = 1;
|
||||
}
|
||||
@ -82,7 +85,7 @@ int CommandUpdate::handle(QStringList arguments)
|
||||
[&](QString error)
|
||||
{
|
||||
Logger::error() << error << Qt::endl;
|
||||
hasErrors = true;
|
||||
this->hasErrors = true;
|
||||
});
|
||||
|
||||
this->autoFinish = false;
|
||||
|
@ -4,6 +4,9 @@
|
||||
#include "filesaver.h"
|
||||
class CommandUpdate : public Command
|
||||
{
|
||||
protected:
|
||||
bool hasErrors = false;
|
||||
|
||||
public:
|
||||
using Command::Command;
|
||||
int handle(QStringList arguments) override;
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "commandupdate.h"
|
||||
#include "commandsearch.h"
|
||||
#include "commandlist.h"
|
||||
#include "commandtag.h"
|
||||
#include "databasefactory.h"
|
||||
#include "logger.h"
|
||||
#include "sandboxedprocessor.h"
|
||||
@ -31,7 +32,7 @@
|
||||
void printUsage(QString argv0)
|
||||
{
|
||||
qInfo() << "Usage:" << argv0 << "command";
|
||||
qInfo() << "Valid commands: add, update, delete, search, list. Each command has a --help option.";
|
||||
qInfo() << "Valid commands: add, update, search, delete, tag, list. Each command has a --help option.";
|
||||
}
|
||||
|
||||
Command *commandFromName(QString name, SqliteDbService &dbService)
|
||||
@ -56,6 +57,10 @@ Command *commandFromName(QString name, SqliteDbService &dbService)
|
||||
{
|
||||
return new CommandList(dbService);
|
||||
}
|
||||
if(name == "tag")
|
||||
{
|
||||
return new CommandTag(dbService);
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ SOURCES += \
|
||||
main.cpp \
|
||||
mainwindow.cpp \
|
||||
clicklabel.cpp \
|
||||
previewcoordinator.cpp \
|
||||
previewgenerator.cpp \
|
||||
previewgeneratormapfunctor.cpp \
|
||||
previewgeneratorodt.cpp \
|
||||
@ -54,6 +55,7 @@ HEADERS += \
|
||||
ipcserver.h \
|
||||
mainwindow.h \
|
||||
clicklabel.h \
|
||||
previewcoordinator.h \
|
||||
previewgenerator.h \
|
||||
previewgeneratormapfunctor.h \
|
||||
previewgeneratorodt.h \
|
||||
|
@ -28,7 +28,7 @@ void enableIpcSandbox()
|
||||
policy->namespace_options = EXILE_UNSHARE_USER | EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_NETWORK;
|
||||
policy->no_new_privs = 1;
|
||||
policy->drop_caps = 1;
|
||||
policy->vow_promises = exile_vows_from_str("thread cpath rpath unix stdio proc error");
|
||||
policy->vow_promises = exile_vows_from_str("thread cpath rpath wpath unix stdio proc error");
|
||||
policy->mount_path_policies_to_chroot = 1;
|
||||
|
||||
QString ipcSocketPath = Common::ipcSocketPath();
|
||||
@ -193,7 +193,7 @@ int main(int argc, char *argv[])
|
||||
Logger::error() << error << Qt::endl;
|
||||
QMessageBox::critical(nullptr, "Error during upgrade",
|
||||
error);
|
||||
qApp->quit();
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
);
|
||||
|
@ -16,14 +16,15 @@
|
||||
#include <QScreen>
|
||||
#include <QProgressDialog>
|
||||
#include <QDesktopWidget>
|
||||
#include <QWidgetAction>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include "clicklabel.h"
|
||||
#include "../shared/sqlitesearch.h"
|
||||
#include "../shared/looqsgeneralexception.h"
|
||||
#include "../shared/common.h"
|
||||
#include "ipcpreviewclient.h"
|
||||
#include "previewgenerator.h"
|
||||
#include "aboutdialog.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent, QString socketPath)
|
||||
@ -32,8 +33,7 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
|
||||
this->progressDialog.cancel(); // because constructing it shows it, quite weird
|
||||
ui->setupUi(this);
|
||||
setWindowTitle(QCoreApplication::applicationName());
|
||||
this->ipcPreviewClient.moveToThread(&this->ipcClientThread);
|
||||
this->ipcPreviewClient.setSocketPath(socketPath);
|
||||
|
||||
QSettings settings;
|
||||
|
||||
this->dbFactory = new DatabaseFactory(Common::databasePath());
|
||||
@ -45,6 +45,9 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
|
||||
|
||||
indexer = new Indexer(*(this->dbService));
|
||||
indexer->setParent(this);
|
||||
|
||||
tagManager = new TagManager(*(this->dbService));
|
||||
|
||||
connectSignals();
|
||||
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||
ui->tabWidget->setCurrentIndex(0);
|
||||
@ -78,7 +81,7 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
|
||||
ui->txtSearch->installEventFilter(this);
|
||||
ui->scrollArea->viewport()->installEventFilter(this);
|
||||
|
||||
this->ipcClientThread.start();
|
||||
this->previewCoordinator.setSocketPath(socketPath);
|
||||
}
|
||||
|
||||
void MainWindow::addPathToIndex()
|
||||
@ -150,7 +153,7 @@ void MainWindow::connectSignals()
|
||||
connect(this->indexer, &Indexer::finished, this, &MainWindow::finishIndexing);
|
||||
|
||||
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); });
|
||||
|
||||
connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); });
|
||||
@ -170,30 +173,29 @@ void MainWindow::connectSignals()
|
||||
}
|
||||
});
|
||||
connect(ui->menuAboutAction, &QAction::triggered, this,
|
||||
[this](bool checked)
|
||||
[this](bool /*checked*/)
|
||||
{
|
||||
AboutDialog aboutDialog(this);
|
||||
|
||||
aboutDialog.exec();
|
||||
});
|
||||
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->menuOpenUserManualAction, &QAction::triggered, this,
|
||||
[this]() { QDesktopServices::openUrl(Common::userManualUrl()); });
|
||||
[]() { QDesktopServices::openUrl(Common::userManualUrl()); });
|
||||
|
||||
connect(indexSyncer, &IndexSyncer::finished, this,
|
||||
[&](unsigned int totalUpdated, unsigned int totalDeleted, unsigned int totalErrored)
|
||||
{
|
||||
this->progressDialog.cancel();
|
||||
connect(
|
||||
indexSyncer, &IndexSyncer::finished, this,
|
||||
[&](unsigned int totalUpdated, unsigned int totalDeleted, unsigned int totalErrored)
|
||||
{
|
||||
this->progressDialog.cancel();
|
||||
|
||||
QMessageBox::information(
|
||||
this, "Syncing finished",
|
||||
QString("Syncing finished\n\nTotal updated: %1\nTotal deleted: %2\nTotal errors: %3\n")
|
||||
.arg(QString::number(totalUpdated))
|
||||
.arg(QString::number(totalDeleted))
|
||||
.arg(QString::number(totalErrored)));
|
||||
});
|
||||
QMessageBox::information(
|
||||
this, "Syncing finished",
|
||||
QString("Syncing finished\n\nTotal updated: %1\nTotal deleted: %2\nTotal errors: %3\n")
|
||||
.arg(QString::number(totalUpdated), QString::number(totalDeleted), QString::number(totalErrored)));
|
||||
});
|
||||
connect(this, &MainWindow::beginIndexSync, indexSyncer, &IndexSyncer::sync);
|
||||
connect(&this->progressDialog, &QProgressDialog::canceled, indexSyncer, &IndexSyncer::cancel);
|
||||
connect(ui->btnSaveSettings, &QPushButton::clicked, this, &MainWindow::saveSettings);
|
||||
@ -208,9 +210,9 @@ void MainWindow::connectSignals()
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
connect(&ipcPreviewClient, &IPCPreviewClient::previewReceived, this, &MainWindow::previewReceived,
|
||||
connect(&previewCoordinator, &PreviewCoordinator::previewReady, this, &MainWindow::previewReceived,
|
||||
Qt::QueuedConnection);
|
||||
connect(&ipcPreviewClient, &IPCPreviewClient::finished, this,
|
||||
connect(&previewCoordinator, &PreviewCoordinator::completedGeneration, this,
|
||||
[&]
|
||||
{
|
||||
this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->maximum());
|
||||
@ -218,22 +220,24 @@ void MainWindow::connectSignals()
|
||||
this->ui->comboPreviewFiles->setEnabled(true);
|
||||
ui->txtSearch->setEnabled(true);
|
||||
});
|
||||
connect(&ipcPreviewClient, &IPCPreviewClient::error, this,
|
||||
connect(&previewCoordinator, &PreviewCoordinator::error, this,
|
||||
[this](QString msg)
|
||||
{
|
||||
qCritical() << msg << Qt::endl;
|
||||
QMessageBox::critical(this, "IPC error", msg);
|
||||
});
|
||||
|
||||
connect(this, &MainWindow::startIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::startGeneration,
|
||||
Qt::QueuedConnection);
|
||||
connect(this, &MainWindow::stopIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::stopGeneration,
|
||||
Qt::QueuedConnection);
|
||||
connect(ui->radioMetadataOnly, &QRadioButton::toggled, this,
|
||||
[this](bool toggled)
|
||||
{
|
||||
if(toggled)
|
||||
{
|
||||
this->ui->chkFillContentForContentless->setChecked(false);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::exportFailedPaths()
|
||||
{
|
||||
|
||||
QString filename =
|
||||
QString("/tmp/looqs_indexresult_failed_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss"));
|
||||
QFile outFile(filename);
|
||||
@ -266,8 +270,11 @@ void MainWindow::startIndexSync()
|
||||
progressDialog.setValue(0);
|
||||
progressDialog.open();
|
||||
|
||||
indexSyncer->setKeepGoing(true);
|
||||
indexSyncer->setVerbose(false);
|
||||
FileSaverOptions options;
|
||||
options.keepGoing = true;
|
||||
options.verbose = false;
|
||||
|
||||
indexSyncer->setFileSaverOptions(options);
|
||||
indexSyncer->setDryRun(false);
|
||||
indexSyncer->setRemoveDeletedFromIndex(true);
|
||||
|
||||
@ -286,6 +293,7 @@ void MainWindow::startIndexing()
|
||||
if(this->indexer->isRunning())
|
||||
{
|
||||
ui->btnStartIndexing->setEnabled(false);
|
||||
|
||||
ui->btnStartIndexing->setText("Start indexing");
|
||||
this->indexer->requestCancellation();
|
||||
return;
|
||||
@ -295,6 +303,8 @@ void MainWindow::startIndexing()
|
||||
ui->resultsTab->setEnabled(false);
|
||||
ui->settingsTab->setEnabled(false);
|
||||
ui->txtPathScanAdd->setEnabled(false);
|
||||
ui->btnAddPath->setEnabled(false);
|
||||
ui->btnChoosePath->setEnabled(false);
|
||||
ui->txtSearch->setEnabled(false);
|
||||
ui->previewProcessBar->setValue(0);
|
||||
ui->previewProcessBar->setVisible(true);
|
||||
@ -311,6 +321,15 @@ void MainWindow::startIndexing()
|
||||
this->indexer->setTargetPaths(paths);
|
||||
QString ignorePatterns = ui->txtIgnorePatterns->text();
|
||||
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();
|
||||
QSettings settings;
|
||||
settings.setValue("indexPaths", pathSettingsValue);
|
||||
@ -333,6 +352,8 @@ void MainWindow::finishIndexing()
|
||||
ui->resultsTab->setEnabled(true);
|
||||
ui->settingsTab->setEnabled(true);
|
||||
ui->txtPathScanAdd->setEnabled(true);
|
||||
ui->btnAddPath->setEnabled(true);
|
||||
ui->btnChoosePath->setEnabled(true);
|
||||
ui->txtSearch->setEnabled(true);
|
||||
if(result.erroredPaths > 0)
|
||||
{
|
||||
@ -340,7 +361,7 @@ void MainWindow::finishIndexing()
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::comboScaleChanged(int i)
|
||||
void MainWindow::comboScaleChanged(int /*i*/)
|
||||
{
|
||||
QSettings scaleSetting;
|
||||
scaleSetting.setValue("currentScale", ui->comboScale->currentText());
|
||||
@ -386,7 +407,8 @@ void MainWindow::processShortcut(int key)
|
||||
{
|
||||
ui->txtSearch->setFocus();
|
||||
QString currentText = ui->txtSearch->text().trimmed();
|
||||
int index = currentText.lastIndexOf(QRegularExpression("[\\s\\)]"));
|
||||
static QRegularExpression separatorRegex("[\\s\\)]");
|
||||
int index = currentText.lastIndexOf(separatorRegex);
|
||||
if(index != -1)
|
||||
{
|
||||
bool isFilter = (index == currentText.length() - 1);
|
||||
@ -632,13 +654,17 @@ void MainWindow::saveSettings()
|
||||
qApp->quit();
|
||||
}
|
||||
|
||||
void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration)
|
||||
void MainWindow::previewReceived()
|
||||
{
|
||||
if(previewGeneration < this->currentPreviewGeneration)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->value() + 1);
|
||||
QBoxLayout *layout = static_cast<QBoxLayout *>(ui->scrollAreaWidgetContents->layout());
|
||||
int index = layout->count();
|
||||
if(index > 0)
|
||||
{
|
||||
--index;
|
||||
}
|
||||
QSharedPointer<PreviewResult> preview = this->previewCoordinator.resultAt(index);
|
||||
|
||||
if(!preview.isNull() && preview->hasPreview())
|
||||
{
|
||||
QString docPath = preview->getDocumentPath();
|
||||
@ -661,8 +687,8 @@ void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned
|
||||
{
|
||||
QFileInfo fileInfo{docPath};
|
||||
QMenu menu("labeRightClick", this);
|
||||
createSearchResutlMenu(menu, fileInfo);
|
||||
menu.addAction("Copy page number",
|
||||
createSearchResultMenu(menu, fileInfo);
|
||||
menu.addAction("Copy page number", this,
|
||||
[previewPage] { QGuiApplication::clipboard()->setText(QString::number(previewPage)); });
|
||||
menu.exec(QCursor::pos());
|
||||
};
|
||||
@ -684,24 +710,7 @@ void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned
|
||||
|
||||
previewWidget->setLayout(previewLayout);
|
||||
|
||||
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;
|
||||
}
|
||||
layout->insertWidget(index, previewWidget);
|
||||
}
|
||||
}
|
||||
|
||||
@ -818,7 +827,6 @@ void MainWindow::lineEditReturnPressed()
|
||||
|
||||
void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
||||
{
|
||||
this->previewableSearchResults.clear();
|
||||
qDeleteAll(ui->scrollAreaWidgetContents->children());
|
||||
|
||||
ui->treeResultsList->clear();
|
||||
@ -827,6 +835,8 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
||||
ui->comboPreviewFiles->setVisible(true);
|
||||
ui->lblTotalPreviewPagesCount->setText("");
|
||||
|
||||
this->previewCoordinator.init(results);
|
||||
|
||||
bool hasDeleted = false;
|
||||
QHash<QString, bool> seenMap;
|
||||
for(const SearchResult &result : results)
|
||||
@ -847,34 +857,29 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
||||
item->setText(3, this->locale().formattedDataSize(result.fileData.size));
|
||||
}
|
||||
bool exists = pathInfo.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
|
||||
if(!exists)
|
||||
{
|
||||
hasDeleted = 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(1);
|
||||
ui->treeResultsList->resizeColumnToContents(2);
|
||||
previewDirty = !this->previewableSearchResults.empty();
|
||||
|
||||
previewDirty = this->previewCoordinator.previewableCount() > 0;
|
||||
|
||||
ui->spinPreviewPage->setValue(1);
|
||||
|
||||
@ -883,8 +888,10 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
|
||||
makePreviews(1);
|
||||
}
|
||||
|
||||
ui->tabWidget->setTabEnabled(1, previewDirty);
|
||||
|
||||
QString statusText = "Results: " + QString::number(results.size()) + " files";
|
||||
statusText += ", previewable: " + QString::number(this->previewableSearchResults.count());
|
||||
statusText += ", previewable: " + QString::number(this->previewCoordinator.previewableCount());
|
||||
if(hasDeleted)
|
||||
{
|
||||
statusText += " WARNING: Some files are inaccessible. No preview available for those. Index may be out of sync";
|
||||
@ -901,7 +908,7 @@ int MainWindow::currentSelectedScale()
|
||||
|
||||
void MainWindow::makePreviews(int page)
|
||||
{
|
||||
if(this->previewableSearchResults.empty())
|
||||
if(this->previewCoordinator.previewableCount() == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -918,11 +925,10 @@ void MainWindow::makePreviews(int page)
|
||||
ui->scrollAreaWidgetContents->setLayout(new QVBoxLayout());
|
||||
ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignCenter);
|
||||
}
|
||||
ui->previewProcessBar->setMaximum(this->previewableSearchResults.size());
|
||||
processedPdfPreviews = 0;
|
||||
ui->previewProcessBar->setMaximum(this->previewCoordinator.previewableCount());
|
||||
|
||||
QVector<QString> wordsToHighlight;
|
||||
QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#");
|
||||
static QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#");
|
||||
for(const Token &token : this->contentSearchQuery.getTokens())
|
||||
{
|
||||
if(token.type == FILTER_CONTENT_CONTAINS)
|
||||
@ -954,12 +960,8 @@ void MainWindow::makePreviews(int page)
|
||||
renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * (currentScale / 100.);
|
||||
renderConfig.wordsToHighlight = wordsToHighlight;
|
||||
|
||||
this->previewOrder.clear();
|
||||
this->previewWidgetOrderCache.clear();
|
||||
|
||||
int previewPos = 0;
|
||||
QVector<RenderTarget> targets;
|
||||
for(SearchResult &sr : this->previewableSearchResults)
|
||||
for(const SearchResult &sr : this->previewCoordinator.getPreviewableSearchResults())
|
||||
{
|
||||
if(ui->comboPreviewFiles->currentIndex() != 0)
|
||||
{
|
||||
@ -971,11 +973,8 @@ void MainWindow::makePreviews(int page)
|
||||
RenderTarget renderTarget;
|
||||
renderTarget.path = sr.fileData.absPath;
|
||||
renderTarget.page = (int)sr.page;
|
||||
targets.append(renderTarget);
|
||||
|
||||
int pos = previewPos - beginOffset;
|
||||
this->previewOrder[renderTarget.path + QString::number(renderTarget.page)] = pos;
|
||||
++previewPos;
|
||||
targets.append(renderTarget);
|
||||
}
|
||||
int numpages = ceil(static_cast<double>(targets.size()) / previewsPerPage);
|
||||
ui->spinPreviewPage->setMaximum(numpages);
|
||||
@ -985,12 +984,12 @@ void MainWindow::makePreviews(int page)
|
||||
ui->previewProcessBar->setMaximum(targets.count());
|
||||
ui->previewProcessBar->setMinimum(0);
|
||||
ui->previewProcessBar->setValue(0);
|
||||
ui->previewProcessBar->setVisible(this->previewableSearchResults.size() > 0);
|
||||
++this->currentPreviewGeneration;
|
||||
ui->previewProcessBar->setVisible(this->previewCoordinator.previewableCount() > 0);
|
||||
this->ui->spinPreviewPage->setEnabled(false);
|
||||
this->ui->comboPreviewFiles->setEnabled(false);
|
||||
this->ui->txtSearch->setEnabled(false);
|
||||
emit startIpcPreviews(renderConfig, targets);
|
||||
|
||||
this->previewCoordinator.startGeneration(renderConfig, targets);
|
||||
}
|
||||
|
||||
void MainWindow::handleSearchError(QString error)
|
||||
@ -998,27 +997,87 @@ void MainWindow::handleSearchError(QString error)
|
||||
ui->lblSearchResults->setText("Error:" + error);
|
||||
}
|
||||
|
||||
void MainWindow::createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo)
|
||||
void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
|
||||
{
|
||||
menu.addAction("Copy filename to clipboard",
|
||||
menu.addAction("Copy filename to clipboard", this,
|
||||
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); });
|
||||
menu.addAction("Copy full path to clipboard",
|
||||
menu.addAction("Copy full path to clipboard", this,
|
||||
[&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); });
|
||||
menu.addAction("Open containing folder", [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); });
|
||||
menu.addAction("Open containing folder", this, [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); });
|
||||
|
||||
auto previewables = this->previewCoordinator.getPreviewableSearchResults();
|
||||
auto result =
|
||||
std::find_if(this->previewableSearchResults.begin(), this->previewableSearchResults.end(),
|
||||
[this, &fileInfo](SearchResult &a) { return fileInfo.absoluteFilePath() == a.fileData.absPath; });
|
||||
std::find_if(previewables.begin(), previewables.end(),
|
||||
[&fileInfo](SearchResult &a) { return fileInfo.absoluteFilePath() == a.fileData.absPath; });
|
||||
|
||||
if(result != this->previewableSearchResults.end())
|
||||
if(result != previewables.end())
|
||||
{
|
||||
menu.addAction("Show previews for this file",
|
||||
menu.addAction("Show previews for this file", this,
|
||||
[this, &fileInfo]
|
||||
{
|
||||
ui->tabWidget->setCurrentIndex(1);
|
||||
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)
|
||||
@ -1048,7 +1107,7 @@ void MainWindow::openFile(QString path)
|
||||
QDesktopServices::openUrl(QUrl::fromLocalFile(path));
|
||||
}
|
||||
|
||||
void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i)
|
||||
void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int /*i*/)
|
||||
{
|
||||
openFile(item->text(1));
|
||||
}
|
||||
@ -1062,22 +1121,22 @@ void MainWindow::showSearchResultsContextMenu(const QPoint &point)
|
||||
}
|
||||
QFileInfo pathinfo(item->text(1));
|
||||
QMenu menu("SearchResults", this);
|
||||
createSearchResutlMenu(menu, pathinfo);
|
||||
createSearchResultMenu(menu, pathinfo);
|
||||
menu.exec(QCursor::pos());
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
syncerThread.terminate();
|
||||
ipcClientThread.terminate();
|
||||
delete this->indexSyncer;
|
||||
delete this->dbService;
|
||||
delete this->dbFactory;
|
||||
delete this->indexer;
|
||||
delete this->tagManager;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void MainWindow::closeEvent(QCloseEvent *event)
|
||||
void MainWindow::closeEvent(QCloseEvent * /*event*/)
|
||||
{
|
||||
QStringList list = this->searchHistory.toList();
|
||||
QSettings settings;
|
||||
|
@ -12,8 +12,9 @@
|
||||
#include <QProgressDialog>
|
||||
#include "../shared/looqsquery.h"
|
||||
#include "../shared/indexsyncer.h"
|
||||
#include "ipcpreviewclient.h"
|
||||
#include "previewcoordinator.h"
|
||||
#include "indexer.h"
|
||||
#include "tagmanager.h"
|
||||
namespace Ui
|
||||
{
|
||||
class MainWindow;
|
||||
@ -27,8 +28,9 @@ class MainWindow : public QMainWindow
|
||||
DatabaseFactory *dbFactory;
|
||||
SqliteDbService *dbService;
|
||||
Ui::MainWindow *ui;
|
||||
IPCPreviewClient ipcPreviewClient;
|
||||
QThread ipcClientThread;
|
||||
|
||||
PreviewCoordinator previewCoordinator;
|
||||
|
||||
QThread syncerThread;
|
||||
Indexer *indexer;
|
||||
IndexSyncer *indexSyncer;
|
||||
@ -36,18 +38,15 @@ class MainWindow : public QMainWindow
|
||||
QFileIconProvider iconProvider;
|
||||
QSqlDatabase db;
|
||||
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
||||
QVector<SearchResult> previewableSearchResults;
|
||||
LooqsQuery contentSearchQuery;
|
||||
QVector<QString> searchHistory;
|
||||
|
||||
TagManager *tagManager;
|
||||
|
||||
int currentSearchHistoryIndex = 0;
|
||||
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;
|
||||
int previewsPerPage = 20;
|
||||
unsigned int processedPdfPreviews = 0;
|
||||
unsigned int currentPreviewGeneration = 1;
|
||||
|
||||
void connectSignals();
|
||||
void makePreviews(int page);
|
||||
@ -56,20 +55,20 @@ class MainWindow : public QMainWindow
|
||||
void keyPressEvent(QKeyEvent *event) override;
|
||||
void handleSearchResults(const QVector<SearchResult> &results);
|
||||
void handleSearchError(QString error);
|
||||
void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo);
|
||||
void createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo);
|
||||
void openDocument(QString path, int num);
|
||||
void openFile(QString path);
|
||||
void initSettingsTabs();
|
||||
int currentSelectedScale();
|
||||
void processShortcut(int key);
|
||||
bool eventFilter(QObject *object, QEvent *event);
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
|
||||
private slots:
|
||||
void lineEditReturnPressed();
|
||||
void treeSearchItemActivated(QTreeWidgetItem *item, int i);
|
||||
void showSearchResultsContextMenu(const QPoint &point);
|
||||
void tabChanged();
|
||||
void previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration);
|
||||
void previewReceived();
|
||||
void comboScaleChanged(int i);
|
||||
void spinPreviewPageValueChanged(int val);
|
||||
void startIndexing();
|
||||
|
@ -18,16 +18,13 @@
|
||||
<item>
|
||||
<widget class="QLineEdit" name="txtSearch"/>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="tabPosition">
|
||||
<enum>QTabWidget::South</enum>
|
||||
</property>
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="resultsTab">
|
||||
<attribute name="title">
|
||||
@ -82,7 +79,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1244</width>
|
||||
<height>633</height>
|
||||
<height>641</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout"/>
|
||||
@ -195,62 +192,6 @@
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<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">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::PreventContextMenu</enum>
|
||||
@ -452,6 +393,108 @@
|
||||
</layout>
|
||||
</widget>
|
||||
</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>
|
||||
</widget>
|
||||
<widget class="QWidget" name="settingsTab">
|
||||
@ -701,5 +744,22 @@
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<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>
|
||||
|
97
gui/previewcoordinator.cpp
Normal file
97
gui/previewcoordinator.cpp
Normal file
@ -0,0 +1,97 @@
|
||||
#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);
|
||||
}
|
48
gui/previewcoordinator.h
Normal file
48
gui/previewcoordinator.h
Normal file
@ -0,0 +1,48 @@
|
||||
#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
|
@ -24,7 +24,7 @@ QSharedPointer<PreviewResult> PreviewGeneratorOdt::generate(RenderConfig config,
|
||||
throw LooqsGeneralException("Error while reading content.xml of " + documentPath);
|
||||
}
|
||||
TagStripperProcessor tsp;
|
||||
QString content = tsp.process(entireContent).first().content;
|
||||
QString content = tsp.process(entireContent).constFirst().content;
|
||||
|
||||
PreviewGeneratorPlainText plainTextGenerator;
|
||||
result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName()));
|
||||
|
@ -20,6 +20,8 @@ Poppler::Document *PreviewGeneratorPdf::document(QString path)
|
||||
return nullptr;
|
||||
}
|
||||
result->setRenderHint(Poppler::Document::TextAntialiasing);
|
||||
result->setRenderHint(Poppler::Document::TextHinting);
|
||||
result->setRenderHint(Poppler::Document::TextSlightHinting);
|
||||
|
||||
locker.relock();
|
||||
documentcache.insert(path, result);
|
||||
|
@ -195,7 +195,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
|
||||
int totalWordsA = 0;
|
||||
int differentWordsB = 0;
|
||||
int totalWordsB = 0;
|
||||
for(int count : a.wordCountMap.values())
|
||||
for(int count : qAsConst(a.wordCountMap))
|
||||
{
|
||||
if(count > 0)
|
||||
{
|
||||
@ -203,7 +203,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
|
||||
}
|
||||
totalWordsA += count;
|
||||
}
|
||||
for(int count : b.wordCountMap.values())
|
||||
for(int count : qAsConst(b.wordCountMap))
|
||||
{
|
||||
if(count > 0)
|
||||
{
|
||||
@ -246,17 +246,21 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
|
||||
totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
|
||||
}
|
||||
}
|
||||
if(isTruncated)
|
||||
if(!resultText.isEmpty())
|
||||
{
|
||||
header += "(truncated) ";
|
||||
}
|
||||
for(QString &word : config.wordsToHighlight)
|
||||
{
|
||||
header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
|
||||
}
|
||||
header += "<hr>";
|
||||
if(isTruncated)
|
||||
{
|
||||
header += "(truncated) ";
|
||||
}
|
||||
for(QString &word : config.wordsToHighlight)
|
||||
{
|
||||
header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
|
||||
}
|
||||
header += "<hr>";
|
||||
|
||||
return header + resultText;
|
||||
resultText = header + resultText;
|
||||
}
|
||||
return resultText;
|
||||
}
|
||||
|
||||
QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath,
|
||||
|
@ -25,10 +25,22 @@ SaveFileResult FileSaver::addFile(QString path)
|
||||
QString absPath = info.absoluteFilePath();
|
||||
|
||||
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 saveFile(info);
|
||||
}
|
||||
|
||||
@ -38,18 +50,17 @@ SaveFileResult FileSaver::updateFile(QString path)
|
||||
return saveFile(info);
|
||||
}
|
||||
|
||||
int FileSaver::addFiles(const QVector<QString> paths, bool keepGoing, bool verbose)
|
||||
int FileSaver::addFiles(const QVector<QString> paths)
|
||||
{
|
||||
return processFiles(paths, std::bind(&FileSaver::addFile, this, std::placeholders::_1), keepGoing, verbose);
|
||||
return processFiles(paths, std::bind(&FileSaver::addFile, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
int FileSaver::updateFiles(const QVector<QString> paths, bool keepGoing, bool verbose)
|
||||
int FileSaver::updateFiles(const QVector<QString> paths)
|
||||
{
|
||||
return processFiles(paths, std::bind(&FileSaver::updateFile, this, std::placeholders::_1), keepGoing, verbose);
|
||||
return processFiles(paths, std::bind(&FileSaver::updateFile, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc,
|
||||
bool keepGoing, bool verbose)
|
||||
int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc)
|
||||
{
|
||||
std::atomic<bool> terminate{false};
|
||||
std::atomic<int> processedCount{0};
|
||||
@ -60,7 +71,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
|
||||
{
|
||||
return;
|
||||
}
|
||||
if(verbose)
|
||||
if(this->fileSaverOptions.verbose)
|
||||
{
|
||||
Logger::info() << "Processing " << path << Qt::endl;
|
||||
}
|
||||
@ -68,7 +79,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
|
||||
if(result == DBFAIL || result == PROCESSFAIL)
|
||||
{
|
||||
Logger::error() << "Failed to process " << path << Qt::endl;
|
||||
if(!keepGoing)
|
||||
if(!this->fileSaverOptions.keepGoing)
|
||||
{
|
||||
terminate = true;
|
||||
}
|
||||
@ -76,7 +87,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
|
||||
else
|
||||
{
|
||||
++processedCount;
|
||||
if(verbose)
|
||||
if(this->fileSaverOptions.verbose)
|
||||
{
|
||||
if(result == SKIPPED)
|
||||
{
|
||||
@ -120,11 +131,26 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
|
||||
{
|
||||
if(canonicalPath.startsWith(excludedPath))
|
||||
{
|
||||
if(this->fileSaverOptions.verbose)
|
||||
{
|
||||
Logger::info() << "Skipped due to excluded path";
|
||||
}
|
||||
return SKIPPED;
|
||||
}
|
||||
}
|
||||
|
||||
if(fileInfo.size() > 0)
|
||||
bool mustFillContent = this->fileSaverOptions.fillExistingContentless;
|
||||
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;
|
||||
QStringList args;
|
||||
@ -159,7 +185,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
SaveFileResult result = this->dbService->saveFile(fileInfo, pageData);
|
||||
SaveFileResult result = this->dbService->saveFile(fileInfo, pageData, this->fileSaverOptions.metadataOnly);
|
||||
if(result == OK && processorReturnCode == OK_WASEMPTY)
|
||||
{
|
||||
return OK_WASEMPTY;
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define FILESAVER_H
|
||||
#include <QSqlDatabase>
|
||||
#include <QFileInfo>
|
||||
#include "filesaveroptions.h"
|
||||
#include "pagedata.h"
|
||||
#include "filedata.h"
|
||||
#include "sqlitedbservice.h"
|
||||
@ -11,16 +12,21 @@ class FileSaver
|
||||
private:
|
||||
SqliteDbService *dbService;
|
||||
QStringList excludedPaths = Common::excludedPaths();
|
||||
FileSaverOptions fileSaverOptions;
|
||||
|
||||
public:
|
||||
FileSaver(SqliteDbService &dbService);
|
||||
SaveFileResult addFile(QString path);
|
||||
SaveFileResult updateFile(QString path);
|
||||
SaveFileResult saveFile(const QFileInfo &fileInfo);
|
||||
int processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc,
|
||||
bool keepGoing, bool verbose);
|
||||
int addFiles(const QVector<QString> paths, bool keepGoing, bool verbose);
|
||||
int updateFiles(const QVector<QString> paths, bool keepGoing, bool verbose);
|
||||
int processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc);
|
||||
int addFiles(const QVector<QString> paths);
|
||||
int updateFiles(const QVector<QString> paths);
|
||||
|
||||
void setFileSaverOptions(FileSaverOptions options)
|
||||
{
|
||||
this->fileSaverOptions = options;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FILESAVER_H
|
||||
|
14
shared/filesaveroptions.h
Normal file
14
shared/filesaveroptions.h
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef FILESAVEROPTIONS_H
|
||||
#define FILESAVEROPTIONS_H
|
||||
|
||||
class FileSaverOptions
|
||||
{
|
||||
public:
|
||||
bool verbose = false;
|
||||
bool keepGoing = false;
|
||||
bool metadataOnly = false;
|
||||
/* Whether those previously explicitly without content should be filled */
|
||||
bool fillExistingContentless = false;
|
||||
};
|
||||
|
||||
#endif // FILESAVEROPTIONS_H
|
@ -12,6 +12,7 @@ FileScanWorker::FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &qu
|
||||
void FileScanWorker::run()
|
||||
{
|
||||
FileSaver saver{*this->dbService};
|
||||
saver.setFileSaverOptions(this->fileSaverOptions);
|
||||
auto paths = queue->dequeue(batchsize);
|
||||
for(QString &path : paths)
|
||||
{
|
||||
@ -20,11 +21,18 @@ void FileScanWorker::run()
|
||||
{
|
||||
sfr = saver.addFile(path);
|
||||
}
|
||||
catch(LooqsGeneralException &e)
|
||||
{
|
||||
Logger::error() << e.message << Qt::endl;
|
||||
sfr = PROCESSFAIL;
|
||||
}
|
||||
|
||||
catch(std::exception &e)
|
||||
{
|
||||
Logger::error() << e.what();
|
||||
Logger::error() << e.what() << Qt::endl;
|
||||
sfr = PROCESSFAIL; // well...
|
||||
}
|
||||
|
||||
emit result({path, sfr});
|
||||
if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck
|
||||
{
|
||||
@ -34,3 +42,8 @@ void FileScanWorker::run()
|
||||
}
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void FileScanWorker::setFileSaverOptions(FileSaverOptions options)
|
||||
{
|
||||
this->fileSaverOptions = options;
|
||||
}
|
||||
|
@ -15,12 +15,14 @@ class FileScanWorker : public QObject, public QRunnable
|
||||
protected:
|
||||
SqliteDbService *dbService;
|
||||
ConcurrentQueue<QString> *queue;
|
||||
FileSaverOptions fileSaverOptions;
|
||||
int batchsize;
|
||||
std::atomic<bool> *stopToken;
|
||||
|
||||
public:
|
||||
FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize, std::atomic<bool> &stopToken);
|
||||
void run() override;
|
||||
void setFileSaverOptions(FileSaverOptions options);
|
||||
signals:
|
||||
void result(FileScanResult);
|
||||
void finished();
|
||||
|
@ -73,16 +73,6 @@ void Indexer::setTargetPaths(QVector<QString> pathsToScan)
|
||||
this->pathsToScan = pathsToScan;
|
||||
}
|
||||
|
||||
void Indexer::setVerbose(bool verbose)
|
||||
{
|
||||
this->verbose = verbose;
|
||||
}
|
||||
|
||||
void Indexer::setKeepGoing(bool keepGoing)
|
||||
{
|
||||
this->keepGoing = keepGoing;
|
||||
}
|
||||
|
||||
void Indexer::requestCancellation()
|
||||
{
|
||||
this->dirScanner->cancel();
|
||||
@ -108,6 +98,7 @@ void Indexer::launchWorker(ConcurrentQueue<QString> &queue, int batchsize)
|
||||
FileScanWorker *runnable = new FileScanWorker(*this->db, queue, batchsize, this->workerCancellationToken);
|
||||
connect(runnable, &FileScanWorker::result, this, &Indexer::processFileScanResult);
|
||||
connect(runnable, &FileScanWorker::finished, this, &Indexer::processFinishedWorker);
|
||||
runnable->setFileSaverOptions(this->fileSaverOptions);
|
||||
++this->runningWorkers;
|
||||
QThreadPool::globalInstance()->start(runnable);
|
||||
}
|
||||
@ -120,24 +111,6 @@ void Indexer::dirScanProgress(int current, int total)
|
||||
|
||||
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 */
|
||||
if(result.second == OK || result.second == OK_WASEMPTY)
|
||||
{
|
||||
@ -152,6 +125,24 @@ void Indexer::processFileScanResult(FileScanResult result)
|
||||
++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();
|
||||
if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10)
|
||||
{
|
||||
@ -175,3 +166,13 @@ void Indexer::processFinishedWorker()
|
||||
emit finished();
|
||||
}
|
||||
}
|
||||
|
||||
void Indexer::setFileSaverOptions(FileSaverOptions options)
|
||||
{
|
||||
this->fileSaverOptions = options;
|
||||
}
|
||||
|
||||
void Indexer::setProgressReportThreshold(int threshold)
|
||||
{
|
||||
this->progressReportThreshold = threshold;
|
||||
}
|
||||
|
@ -52,8 +52,7 @@ class Indexer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
bool verbose = false;
|
||||
bool keepGoing = true;
|
||||
FileSaverOptions fileSaverOptions;
|
||||
SqliteDbService *db;
|
||||
|
||||
int progressReportThreshold = 50;
|
||||
@ -80,8 +79,10 @@ class Indexer : public QObject
|
||||
void beginIndexing();
|
||||
void setIgnorePattern(QStringList ignorePattern);
|
||||
void setTargetPaths(QVector<QString> pathsToScan);
|
||||
void setVerbose(bool verbose);
|
||||
void setKeepGoing(bool keepGoing);
|
||||
|
||||
void setFileSaverOptions(FileSaverOptions options);
|
||||
|
||||
void setProgressReportThreshold(int threshold);
|
||||
|
||||
void requestCancellation();
|
||||
|
||||
|
@ -7,21 +7,16 @@ IndexSyncer::IndexSyncer(SqliteDbService &dbService)
|
||||
this->dbService = &dbService;
|
||||
}
|
||||
|
||||
void IndexSyncer::setFileSaverOptions(FileSaverOptions options)
|
||||
{
|
||||
fileSaverOptions = options;
|
||||
}
|
||||
|
||||
void IndexSyncer::setDryRun(bool 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)
|
||||
{
|
||||
this->removeDeletedFromIndex = removeDeletedFromIndex;
|
||||
@ -35,7 +30,7 @@ void IndexSyncer::setPattern(QString pattern)
|
||||
void IndexSyncer::sync()
|
||||
{
|
||||
this->stopToken.store(false, std::memory_order_relaxed);
|
||||
FileSaver saver(*this->dbService);
|
||||
|
||||
QVector<FileData> files;
|
||||
int offset = 0;
|
||||
int limit = 10000;
|
||||
@ -87,7 +82,7 @@ void IndexSyncer::sync()
|
||||
if(!this->dbService->deleteFile(fileData.absPath))
|
||||
{
|
||||
emit error("Error: Failed to delete " + fileData.absPath + " from the index");
|
||||
if(!this->keepGoing)
|
||||
if(!this->fileSaverOptions.keepGoing)
|
||||
{
|
||||
emit finished(totalUpdatesFilesCount, totalDeletedFilesCount, totalErroredFilesCount);
|
||||
return;
|
||||
@ -104,13 +99,15 @@ void IndexSyncer::sync()
|
||||
}
|
||||
}
|
||||
|
||||
unsigned int updatedFilesCount = saver.updateFiles(filePathsToUpdate, keepGoing, verbose);
|
||||
FileSaver saver(*this->dbService);
|
||||
saver.setFileSaverOptions(this->fileSaverOptions);
|
||||
unsigned int updatedFilesCount = saver.updateFiles(filePathsToUpdate);
|
||||
unsigned int shouldHaveUpdatedCount = static_cast<unsigned int>(filePathsToUpdate.size());
|
||||
if(updatedFilesCount != shouldHaveUpdatedCount)
|
||||
{
|
||||
|
||||
totalErroredFilesCount += (shouldHaveUpdatedCount - updatedFilesCount);
|
||||
if(!keepGoing)
|
||||
if(!this->fileSaverOptions.keepGoing)
|
||||
{
|
||||
QString errorMsg = QString("Failed to update all files selected for updating in this batch. Updated") +
|
||||
updatedFilesCount + "out of" + shouldHaveUpdatedCount + "selected for updating";
|
||||
|
@ -1,16 +1,15 @@
|
||||
#ifndef INDEXSYNCER_H
|
||||
#define INDEXSYNCER_H
|
||||
#include "sqlitedbservice.h"
|
||||
|
||||
#include "filesaveroptions.h"
|
||||
class IndexSyncer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
SqliteDbService *dbService = nullptr;
|
||||
bool keepGoing = true;
|
||||
FileSaverOptions fileSaverOptions;
|
||||
bool removeDeletedFromIndex = true;
|
||||
bool dryRun = false;
|
||||
bool verbose = false;
|
||||
QString pattern;
|
||||
|
||||
std::atomic<bool> stopToken{false};
|
||||
@ -18,12 +17,12 @@ class IndexSyncer : public QObject
|
||||
public:
|
||||
IndexSyncer(SqliteDbService &dbService);
|
||||
|
||||
void setFileSaverOptions(FileSaverOptions options);
|
||||
|
||||
public slots:
|
||||
void sync();
|
||||
void cancel();
|
||||
void setDryRun(bool dryRun);
|
||||
void setVerbose(bool verbose);
|
||||
void setKeepGoing(bool keepGoing);
|
||||
void setRemoveDeletedFromIndex(bool removeDeletedFromIndex);
|
||||
void setPattern(QString pattern);
|
||||
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include <optional>
|
||||
#include <algorithm>
|
||||
#include "looqsquery.h"
|
||||
#include "looqsgeneralexception.h"
|
||||
|
||||
const QVector<Token> &LooqsQuery::getTokens() const
|
||||
{
|
||||
@ -180,8 +181,9 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
|
||||
|
||||
QStringList loneWords;
|
||||
LooqsQuery result;
|
||||
QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([^\\s])+)|(?<boolean>AND|OR)"
|
||||
"|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>[^\\s]+))");
|
||||
static QRegularExpression rx(
|
||||
"((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([^\\s])+)|(?<boolean>AND|OR)"
|
||||
"|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>[^\\s]+))");
|
||||
QRegularExpressionMatchIterator i = rx.globalMatch(expression);
|
||||
auto previousWasBool = [&result] { return !result.tokens.empty() && ((result.tokens.last().type & BOOL) == BOOL); };
|
||||
auto previousWas = [&result](TokenType t) { return !result.tokens.empty() && (result.tokens.last().type == t); };
|
||||
@ -283,6 +285,10 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
|
||||
{
|
||||
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
|
||||
else if(filtername == "sort")
|
||||
{
|
||||
|
@ -2,7 +2,6 @@
|
||||
#define LOOQSQUERY_H
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
#include "looqsgeneralexception.h"
|
||||
#include "token.h"
|
||||
/* Fields that can be queried or sorted */
|
||||
enum QueryField
|
||||
@ -46,7 +45,7 @@ class LooqsQuery
|
||||
void addToken(Token t);
|
||||
void updateTokensMask()
|
||||
{
|
||||
for(const Token &t : tokens)
|
||||
for(const Token &t : qAsConst(tokens))
|
||||
{
|
||||
this->tokensMask |= t.type;
|
||||
}
|
||||
@ -92,14 +91,6 @@ class LooqsQuery
|
||||
this->sortConditions = sortConditions;
|
||||
updateTokensMask();
|
||||
}
|
||||
|
||||
LooqsQuery(const LooqsQuery &o)
|
||||
{
|
||||
this->tokens = o.tokens;
|
||||
this->sortConditions = o.sortConditions;
|
||||
this->tokensMask = o.tokensMask;
|
||||
this->limit = o.limit;
|
||||
}
|
||||
};
|
||||
|
||||
#endif // LOOQSQUERY_H
|
||||
|
6
shared/migrations/5.sql
Normal file
6
shared/migrations/5.sql
Normal file
@ -0,0 +1,6 @@
|
||||
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';
|
@ -4,5 +4,6 @@
|
||||
<file>2.sql</file>
|
||||
<file>3.sql</file>
|
||||
<file>4.sql</file>
|
||||
<file>5.sql</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
@ -10,7 +10,7 @@ class NothingProcessor : public Processor
|
||||
NothingProcessor();
|
||||
|
||||
public:
|
||||
QVector<PageData> process(const QByteArray &data) const override
|
||||
QVector<PageData> process(const QByteArray & /*data*/) const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include "odtprocessor.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");
|
||||
}
|
||||
|
@ -1,5 +1,3 @@
|
||||
#include "paralleldirscanner.h"
|
||||
|
||||
#include <QRunnable>
|
||||
#include <QMutex>
|
||||
#include <QDirIterator>
|
||||
@ -7,7 +5,7 @@
|
||||
#include <QThreadPool>
|
||||
#include <functional>
|
||||
#include "dirscanworker.h"
|
||||
#include "logger.h"
|
||||
#include "paralleldirscanner.h"
|
||||
|
||||
ParallelDirScanner::ParallelDirScanner()
|
||||
{
|
||||
|
@ -60,6 +60,7 @@ SOURCES += sqlitesearch.cpp \
|
||||
processor.cpp \
|
||||
sandboxedprocessor.cpp \
|
||||
sqlitedbservice.cpp \
|
||||
tagmanager.cpp \
|
||||
tagstripperprocessor.cpp \
|
||||
utils.cpp \
|
||||
../submodules/exile.h/exile.c \
|
||||
@ -74,6 +75,7 @@ HEADERS += sqlitesearch.h \
|
||||
encodingdetector.h \
|
||||
filedata.h \
|
||||
filesaver.h \
|
||||
filesaveroptions.h \
|
||||
filescanworker.h \
|
||||
indexer.h \
|
||||
indexsyncer.h \
|
||||
@ -92,6 +94,7 @@ HEADERS += sqlitesearch.h \
|
||||
savefileresult.h \
|
||||
searchresult.h \
|
||||
sqlitedbservice.h \
|
||||
tagmanager.h \
|
||||
tagstripperprocessor.h \
|
||||
token.h \
|
||||
common.h \
|
||||
|
@ -2,25 +2,10 @@
|
||||
#include <QFileInfo>
|
||||
#include <QDateTime>
|
||||
#include <QSqlError>
|
||||
#include "looqsgeneralexception.h"
|
||||
#include "sqlitedbservice.h"
|
||||
#include "filedata.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)
|
||||
{
|
||||
@ -29,20 +14,29 @@ QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
|
||||
return searcher.search(query);
|
||||
}
|
||||
|
||||
bool SqliteDbService::fileExistsInDatabase(QString path)
|
||||
std::optional<QChar> SqliteDbService::queryFileType(QString absPath)
|
||||
{
|
||||
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
||||
query.prepare("SELECT 1 FROM file WHERE path = ?");
|
||||
query.addBindValue(path);
|
||||
if(!query.exec())
|
||||
{
|
||||
throw LooqsGeneralException("Error while trying to query for file existance: " + query.lastError().text());
|
||||
}
|
||||
auto query = exec("SELECT filetype FROM file WHERE path = ?", {absPath});
|
||||
if(!query.next())
|
||||
{
|
||||
return false;
|
||||
return {};
|
||||
}
|
||||
return query.value(0).toBool();
|
||||
return query.value(0).toChar();
|
||||
}
|
||||
|
||||
bool SqliteDbService::fileExistsInDatabase(QString path)
|
||||
{
|
||||
return execBool("SELECT 1 FROM file WHERE path = ?", {path});
|
||||
}
|
||||
|
||||
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime)
|
||||
{
|
||||
return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ?", {path, mtime});
|
||||
}
|
||||
|
||||
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime, QChar fileType)
|
||||
{
|
||||
return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ? AND filetype = ?", {path, mtime, fileType});
|
||||
}
|
||||
|
||||
SqliteDbService::SqliteDbService(DatabaseFactory &dbFactory)
|
||||
@ -110,6 +104,117 @@ unsigned int SqliteDbService::getFiles(QVector<FileData> &results, QString wildC
|
||||
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)
|
||||
{
|
||||
QString ftsInsertStatement;
|
||||
@ -148,11 +253,40 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid
|
||||
return true;
|
||||
}
|
||||
|
||||
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData)
|
||||
QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args)
|
||||
{
|
||||
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
||||
query.prepare(querystr);
|
||||
for(const QVariant &v : args)
|
||||
{
|
||||
query.addBindValue(v);
|
||||
}
|
||||
if(!query.exec())
|
||||
{
|
||||
throw LooqsGeneralException("Error while exec(): " + query.lastError().text() + " for query: " + querystr);
|
||||
}
|
||||
return query;
|
||||
}
|
||||
|
||||
bool SqliteDbService::execBool(QString querystr, std::initializer_list<QVariant> args)
|
||||
{
|
||||
auto query = exec(querystr, args);
|
||||
if(!query.next())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return query.value(0).toBool();
|
||||
}
|
||||
|
||||
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly)
|
||||
{
|
||||
QString absPath = fileInfo.absoluteFilePath();
|
||||
auto mtime = fileInfo.lastModified().toSecsSinceEpoch();
|
||||
QChar fileType = fileInfo.isDir() ? 'd' : 'f';
|
||||
QChar fileType = fileInfo.isDir() ? 'd' : 'c';
|
||||
if(pathsOnly)
|
||||
{
|
||||
fileType = 'f';
|
||||
}
|
||||
|
||||
QSqlDatabase db = dbFactory->forCurrentThread();
|
||||
QSqlQuery delQuery(db);
|
||||
@ -186,19 +320,23 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
|
||||
return DBFAIL;
|
||||
}
|
||||
|
||||
int lastid = inserterQuery.lastInsertId().toInt();
|
||||
if(!insertToFTS(false, db, lastid, pageData))
|
||||
if(!pathsOnly)
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
|
||||
return DBFAIL;
|
||||
}
|
||||
if(!insertToFTS(true, db, lastid, pageData))
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
|
||||
return DBFAIL;
|
||||
int lastid = inserterQuery.lastInsertId().toInt();
|
||||
if(!insertToFTS(false, db, lastid, pageData))
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
|
||||
return DBFAIL;
|
||||
}
|
||||
if(!insertToFTS(true, db, lastid, pageData))
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
|
||||
return DBFAIL;
|
||||
}
|
||||
}
|
||||
|
||||
if(!db.commit())
|
||||
{
|
||||
db.rollback();
|
||||
@ -207,3 +345,123 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
|
||||
}
|
||||
return OK;
|
||||
}
|
||||
|
||||
bool SqliteDbService::addTag(QString tag, QString path)
|
||||
{
|
||||
QVector<QString> paths;
|
||||
paths.append(path);
|
||||
return addTag(tag, paths);
|
||||
}
|
||||
|
||||
bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
|
||||
{
|
||||
QSqlDatabase db = dbFactory->forCurrentThread();
|
||||
QSqlQuery tagQuery(db);
|
||||
QSqlQuery fileTagQuery(db);
|
||||
|
||||
tag = tag.toLower();
|
||||
|
||||
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
|
||||
tagQuery.addBindValue(tag);
|
||||
|
||||
fileTagQuery.prepare("INSERT INTO filetag(fileid, tagid) VALUES((SELECT id FROM file WHERE path = ?), (SELECT id "
|
||||
"FROM tag WHERE name = ?))");
|
||||
fileTagQuery.bindValue(1, tag);
|
||||
if(!db.transaction())
|
||||
{
|
||||
Logger::error() << "Failed to open transaction to add paths for tag " << tag << " : " << db.lastError()
|
||||
<< Qt::endl;
|
||||
return false;
|
||||
}
|
||||
if(!tagQuery.exec())
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed INSERT query" << tagQuery.lastError() << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
for(const QString &path : paths)
|
||||
{
|
||||
fileTagQuery.bindValue(0, path);
|
||||
if(!fileTagQuery.exec())
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed to add paths to tag" << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!db.commit())
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Failed to commit tag insertion transaction" << db.lastError() << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqliteDbService::removePathsForTag(QString tag, const QVector<QString> &paths)
|
||||
{
|
||||
QSqlDatabase db = dbFactory->forCurrentThread();
|
||||
QSqlQuery tagQuery(db);
|
||||
QSqlQuery fileTagQuery(db);
|
||||
|
||||
tag = tag.toLower();
|
||||
|
||||
fileTagQuery.prepare(
|
||||
"DELETE FROM filetag WHERE fileid = (SELECT id FROM file WHERE path = ?) AND tagid = (SELECT id "
|
||||
"FROM tag WHERE name = ?)");
|
||||
|
||||
fileTagQuery.bindValue(1, tag);
|
||||
for(const QString &path : paths)
|
||||
{
|
||||
fileTagQuery.bindValue(0, path);
|
||||
if(!fileTagQuery.exec())
|
||||
{
|
||||
Logger::error() << "An error occured while trying to remove paths from tag assignment" << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SqliteDbService::deleteTag(QString tag)
|
||||
{
|
||||
QSqlDatabase db = dbFactory->forCurrentThread();
|
||||
if(!db.transaction())
|
||||
{
|
||||
Logger::error() << "Failed to open transaction while trying to delete tag " << tag << " : " << db.lastError()
|
||||
<< Qt::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
tag = tag.toLower();
|
||||
QSqlQuery assignmentDeleteQuery(db);
|
||||
assignmentDeleteQuery.prepare("DELETE FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)");
|
||||
assignmentDeleteQuery.addBindValue(tag);
|
||||
if(!assignmentDeleteQuery.exec())
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
QSqlQuery deleteTagQuery(db);
|
||||
deleteTagQuery.prepare("DELETE FROM tag WHERE name = ?");
|
||||
deleteTagQuery.addBindValue(tag);
|
||||
if(!deleteTagQuery.exec())
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!db.commit())
|
||||
{
|
||||
db.rollback();
|
||||
Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
#ifndef SQLITEDBSERVICE_H
|
||||
#define SQLITEDBSERVICE_H
|
||||
#include <QFileInfo>
|
||||
#include <optional>
|
||||
|
||||
#include "databasefactory.h"
|
||||
#include "utils.h"
|
||||
#include "pagedata.h"
|
||||
@ -15,14 +17,31 @@ class SqliteDbService
|
||||
DatabaseFactory *dbFactory = nullptr;
|
||||
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:
|
||||
SqliteDbService(DatabaseFactory &dbFactory);
|
||||
SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData);
|
||||
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
|
||||
SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
|
||||
|
||||
bool deleteFile(QString path);
|
||||
bool fileExistsInDatabase(QString path);
|
||||
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);
|
||||
|
||||
std::optional<QChar> queryFileType(QString absPath);
|
||||
};
|
||||
|
||||
#endif // SQLITEDBSERVICE_H
|
||||
|
@ -69,7 +69,7 @@ QString SqliteSearch::createSortSql(const QVector<SortCondition> sortConditions)
|
||||
QString SqliteSearch::escapeFtsArgument(QString ftsArg)
|
||||
{
|
||||
QString result;
|
||||
QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#");
|
||||
static QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#");
|
||||
QRegularExpressionMatchIterator i = extractor.globalMatch(ftsArg);
|
||||
while(i.hasNext())
|
||||
{
|
||||
@ -143,13 +143,17 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
|
||||
{
|
||||
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)");
|
||||
}
|
||||
|
||||
QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
|
||||
{
|
||||
QString whereSql;
|
||||
QString joinSql;
|
||||
QVector<QString> bindValues;
|
||||
bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
|
||||
if(query.getTokens().isEmpty())
|
||||
@ -180,18 +184,18 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
|
||||
}
|
||||
QString whereSqlTrigram = whereSql;
|
||||
whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
|
||||
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, "
|
||||
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||
"content.fileid "
|
||||
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
|
||||
whereSql +
|
||||
"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.id = "
|
||||
"content.fileid " +
|
||||
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + whereSqlTrigram +
|
||||
" ) " + sortSql;
|
||||
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, "
|
||||
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||
"content.fileid "
|
||||
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
|
||||
whereSql +
|
||||
"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.id = "
|
||||
"content.fileid " +
|
||||
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " +
|
||||
whereSqlTrigram + " ) " + sortSql;
|
||||
++bindIterations;
|
||||
}
|
||||
else
|
||||
|
66
shared/tagmanager.cpp
Normal file
66
shared/tagmanager.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#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);
|
||||
}
|
28
shared/tagmanager.h
Normal file
28
shared/tagmanager.h
Normal file
@ -0,0 +1,28 @@
|
||||
#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
|
@ -19,7 +19,8 @@ enum TokenType
|
||||
FILTER_PATH_SIZE,
|
||||
FILTER_PATH_ENDS,
|
||||
FILTER_PATH_STARTS,
|
||||
FILTER_CONTENT = 512,
|
||||
FILTER_TAG_ASSIGNED,
|
||||
FILTER_CONTENT = 512, /* Everything below here is content search (except LIMIT) */
|
||||
FILTER_CONTENT_CONTAINS,
|
||||
FILTER_CONTENT_PAGE,
|
||||
LIMIT = 1024
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 769f729dc51f2feb8bc3cbb2a48ed91ff2d56bf3
|
||||
Subproject commit 44b9a17becf6882e1b3728cbf885ae9e5a6717af
|
Loading…
Reference in New Issue
Block a user