Comparer les révisions
	
		
			1 Révisions
		
	
	
		
			WIP/outlin
			...
			a9d3f7897a
		
	
	| Auteur | SHA1 | Date | |
|---|---|---|---|
| a9d3f7897a | 
							
								
								
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,24 +1,5 @@
 | 
			
		||||
# 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:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								README.md
									
									
									
									
									
								
							@@ -28,7 +28,7 @@ There is no need to write the long form of filters. There are also booleans avai
 | 
			
		||||
The screenshots in this section may occasionally be slightly outdated, but they are usually recent enough to get an overall impression of the current state of the GUI.
 | 
			
		||||
 | 
			
		||||
## Current status
 | 
			
		||||
Latest version: 2023-05-07, v0.9
 | 
			
		||||
Latest version: 2022-11-19, v0.8.1
 | 
			
		||||
 | 
			
		||||
Please keep in mind: looqs is still at an early stage and may exhibit some weirdness and contain bugs.
 | 
			
		||||
 | 
			
		||||
@@ -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 libqt5sql5-sqlite libpoppler-qt5-dev libuchardet-dev libquazip5-dev
 | 
			
		||||
sudo apt install build-essential qtbase5-dev libpoppler-qt5-dev libuchardet-dev libquazip5-dev
 | 
			
		||||
qmake
 | 
			
		||||
make
 | 
			
		||||
```
 | 
			
		||||
@@ -97,9 +97,7 @@ 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 23.04, 22.10, 22.04
 | 
			
		||||
### Ubuntu 22.04, 22.10
 | 
			
		||||
Latest release can be installed using apt from the repo.
 | 
			
		||||
```
 | 
			
		||||
# First, obtain key, assume it's trusted.
 | 
			
		||||
@@ -110,8 +108,6 @@ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] ht
 | 
			
		||||
sudo apt-get update
 | 
			
		||||
sudo apt-get 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
 | 
			
		||||
@@ -138,7 +134,7 @@ An AppImage may accompany the tarball in the future.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Other distros
 | 
			
		||||
I appreciate help for others distros. If you create a package, let me know!
 | 
			
		||||
I'll probably add a package for voidlinux at some point and maybe will provide a Gentoo ebuild. However, I would appreciate help for others distros. If you create a package, let me know!
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Signature verification
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								USAGE.md
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								USAGE.md
									
									
									
									
									
								
							@@ -165,8 +165,6 @@ 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.
 | 
			
		||||
 | 
			
		||||
@@ -179,5 +177,11 @@ 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|Equivalent to c:("invoice") p:("Downloads")|
 | 
			
		||||
|p:(Downloads) invoice|Equivalent to c:("invoice") p:("Downloads")|
 | 
			
		||||
|c:("invoice") Downloads|This query is equivalent to c:("invoice") p:("Downloads")|
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,7 +15,6 @@ DEFINES += QT_DEPRECATED_WARNINGS
 | 
			
		||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
 | 
			
		||||
#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 \
 | 
			
		||||
@@ -28,7 +27,6 @@ HEADERS += \
 | 
			
		||||
    command.h \
 | 
			
		||||
    commandadd.h \
 | 
			
		||||
    commanddelete.h \
 | 
			
		||||
    commandtag.h \
 | 
			
		||||
    commandupdate.h \
 | 
			
		||||
    commandsearch.h \
 | 
			
		||||
    commandlist.h
 | 
			
		||||
@@ -46,8 +44,6 @@ packagesExist(quazip1-qt5) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
INCLUDEPATH += $$PWD/../shared
 | 
			
		||||
INCLUDEPATH += /usr/include/poppler/qt5/
 | 
			
		||||
 | 
			
		||||
DEPENDPATH += $$PWD/../shared
 | 
			
		||||
 | 
			
		||||
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
#include <QThread>
 | 
			
		||||
#include <QDebug>
 | 
			
		||||
#include "command.h"
 | 
			
		||||
#include "looqsgeneralexception.h"
 | 
			
		||||
 | 
			
		||||
void Command::execute()
 | 
			
		||||
{
 | 
			
		||||
 
 | 
			
		||||
@@ -41,13 +41,12 @@ int CommandAdd::handle(QStringList arguments)
 | 
			
		||||
{
 | 
			
		||||
	QCommandLineParser parser;
 | 
			
		||||
	parser.addOptions({{{"c", "continue"},
 | 
			
		||||
						"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."
 | 
			
		||||
						"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. "
 | 
			
		||||
						"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"},
 | 
			
		||||
					   {"tags", "Comma-separated list of tags to assign"},
 | 
			
		||||
					   {{"t", "threads"}, "Number of threads to use.", "threads"}});
 | 
			
		||||
	parser.addHelpOption();
 | 
			
		||||
	parser.addPositionalArgument("add", "Add paths to the index",
 | 
			
		||||
@@ -57,8 +56,6 @@ int CommandAdd::handle(QStringList 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");
 | 
			
		||||
@@ -88,43 +85,18 @@ int CommandAdd::handle(QStringList arguments)
 | 
			
		||||
	fileSaverOptions.keepGoing = keepGoing;
 | 
			
		||||
	fileSaverOptions.fillExistingContentless = fillContent;
 | 
			
		||||
	fileSaverOptions.metadataOnly = pathsOnly;
 | 
			
		||||
	fileSaverOptions.verbose = verbose;
 | 
			
		||||
	fileSaverOptions.verbose = false;
 | 
			
		||||
 | 
			
		||||
	indexer = new Indexer(*this->dbService);
 | 
			
		||||
	indexer->setFileSaverOptions(fileSaverOptions);
 | 
			
		||||
 | 
			
		||||
	indexer->setTargetPaths(files.toVector());
 | 
			
		||||
 | 
			
		||||
	if(verbose)
 | 
			
		||||
	{
 | 
			
		||||
		indexer->setProgressReportThreshold(1);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	connect(indexer, &Indexer::pathsCountChanged, this,
 | 
			
		||||
			[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; });
 | 
			
		||||
	connect(indexer, &Indexer::indexProgress, this,
 | 
			
		||||
			[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;
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	);
 | 
			
		||||
			[](int pathsCount, unsigned int /*added*/, unsigned int /*skipped*/, unsigned int /*failed*/,
 | 
			
		||||
			   unsigned int /*totalCount*/) { Logger::info() << "Processed files: " << pathsCount << Qt::endl; });
 | 
			
		||||
	connect(indexer, &Indexer::finished, this, &CommandAdd::indexerFinished);
 | 
			
		||||
 | 
			
		||||
	this->autoFinish = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -13,8 +13,6 @@ class CommandAdd : public Command
 | 
			
		||||
	bool keepGoing = true;
 | 
			
		||||
 | 
			
		||||
  protected:
 | 
			
		||||
	IndexResult currentResult;
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	using Command::Command;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
#include <QCommandLineParser>
 | 
			
		||||
#include "commandsearch.h"
 | 
			
		||||
#include "databasefactory.h"
 | 
			
		||||
#include "logger.h"
 | 
			
		||||
 | 
			
		||||
int CommandSearch::handle(QStringList arguments)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,153 +0,0 @@
 | 
			
		||||
#include <QCommandLineParser>
 | 
			
		||||
#include "commandtag.h"
 | 
			
		||||
#include "logger.h"
 | 
			
		||||
#include "tagmanager.h"
 | 
			
		||||
 | 
			
		||||
bool CommandTag::ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths)
 | 
			
		||||
{
 | 
			
		||||
	for(const QString &path : paths)
 | 
			
		||||
	{
 | 
			
		||||
		QFileInfo info{path};
 | 
			
		||||
		if(!info.exists())
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Can't add tag for file  " + info.absoluteFilePath() + " because it does not exist"
 | 
			
		||||
							<< Qt::endl;
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		QString absolutePath = info.absoluteFilePath();
 | 
			
		||||
		if(!this->dbService->fileExistsInDatabase(absolutePath))
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Only files that have been indexed can be tagged. File not in index: " + absolutePath
 | 
			
		||||
							<< Qt::endl;
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		absolutePaths.append(absolutePath);
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int CommandTag::handle(QStringList arguments)
 | 
			
		||||
{
 | 
			
		||||
	QCommandLineParser parser;
 | 
			
		||||
	parser.addPositionalArgument("add", "Adds a tag to a file",
 | 
			
		||||
								 "add [tag] [paths...]. Adds the tag to the specified paths");
 | 
			
		||||
	parser.addPositionalArgument("remove", "Removes a path associated to a tag", "remove [tag] [path]");
 | 
			
		||||
	parser.addPositionalArgument("delete", "Deletes a tag", "delete [tag]");
 | 
			
		||||
	parser.addPositionalArgument("list", "Lists paths associated with a tag, or all tags", "list [tag]");
 | 
			
		||||
	parser.addPositionalArgument("show", "Lists tags associated with a path", "show [path]");
 | 
			
		||||
 | 
			
		||||
	parser.addHelpOption();
 | 
			
		||||
 | 
			
		||||
	parser.parse(arguments);
 | 
			
		||||
 | 
			
		||||
	QStringList args = parser.positionalArguments();
 | 
			
		||||
	if(args.length() == 0)
 | 
			
		||||
	{
 | 
			
		||||
		parser.showHelp(EXIT_FAILURE);
 | 
			
		||||
		return EXIT_FAILURE;
 | 
			
		||||
	}
 | 
			
		||||
	TagManager tagManager{*this->dbService};
 | 
			
		||||
	QString cmd = args[0];
 | 
			
		||||
	if(cmd == "add")
 | 
			
		||||
	{
 | 
			
		||||
		if(args.length() < 3)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Not enough arguments provided. 'add' requires a tag followed by at least one path"
 | 
			
		||||
							<< Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		QString tag = args[1];
 | 
			
		||||
		QVector<QString> paths = args.mid(2).toVector();
 | 
			
		||||
 | 
			
		||||
		QVector<QString> absolutePaths;
 | 
			
		||||
		if(!ensureAbsolutePaths(paths, absolutePaths))
 | 
			
		||||
		{
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		bool result = tagManager.addPathsToTag(tag, absolutePaths);
 | 
			
		||||
		if(!result)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Failed to assign tags" << Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		return EXIT_SUCCESS;
 | 
			
		||||
	}
 | 
			
		||||
	if(cmd == "list")
 | 
			
		||||
	{
 | 
			
		||||
 | 
			
		||||
		QString tag;
 | 
			
		||||
		if(args.length() >= 2)
 | 
			
		||||
		{
 | 
			
		||||
			tag = args[1];
 | 
			
		||||
		}
 | 
			
		||||
		QVector<QString> entries;
 | 
			
		||||
		if(tag.isEmpty())
 | 
			
		||||
		{
 | 
			
		||||
			entries = tagManager.getTags();
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			entries = tagManager.getPaths(tag);
 | 
			
		||||
		}
 | 
			
		||||
		for(const QString &entry : entries)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::info() << entry << Qt::endl;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(cmd == "remove")
 | 
			
		||||
	{
 | 
			
		||||
		if(args.length() < 3)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Not enough arguments provided. 'remove' requires a tag followed by at least one path"
 | 
			
		||||
							<< Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		QString tag = args[1];
 | 
			
		||||
		QVector<QString> paths = args.mid(2).toVector();
 | 
			
		||||
 | 
			
		||||
		QVector<QString> absolutePaths;
 | 
			
		||||
		if(!ensureAbsolutePaths(paths, absolutePaths))
 | 
			
		||||
		{
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(!tagManager.removePathsForTag(tag, absolutePaths))
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Failed to remove path assignments" << Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(cmd == "delete")
 | 
			
		||||
	{
 | 
			
		||||
		if(args.length() != 2)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "The 'delete' command requires the tag to delete" << Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		if(!tagManager.deleteTag(args[1]))
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Failed to delete tag" << Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(cmd == "show")
 | 
			
		||||
	{
 | 
			
		||||
		if(args.length() != 2)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "The 'show' command requires a path to show the assigned tags" << Qt::endl;
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		QString path = args[1];
 | 
			
		||||
		QVector<QString> absolutePaths;
 | 
			
		||||
		if(!ensureAbsolutePaths({path}, absolutePaths))
 | 
			
		||||
		{
 | 
			
		||||
			return EXIT_FAILURE;
 | 
			
		||||
		}
 | 
			
		||||
		QVector<QString> tags = tagManager.getTags(absolutePaths.at(0));
 | 
			
		||||
		for(const QString &entry : tags)
 | 
			
		||||
		{
 | 
			
		||||
			Logger::info() << entry << Qt::endl;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return EXIT_SUCCESS;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,16 +0,0 @@
 | 
			
		||||
#ifndef COMMANDTAG_H
 | 
			
		||||
#define COMMANDTAG_H
 | 
			
		||||
#include "command.h"
 | 
			
		||||
 | 
			
		||||
class CommandTag : public Command
 | 
			
		||||
{
 | 
			
		||||
  protected:
 | 
			
		||||
	bool ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths);
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	using Command::Command;
 | 
			
		||||
 | 
			
		||||
	int handle(QStringList arguments) override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // COMMANDTAG_H
 | 
			
		||||
@@ -21,7 +21,6 @@
 | 
			
		||||
#include "commandupdate.h"
 | 
			
		||||
#include "commandsearch.h"
 | 
			
		||||
#include "commandlist.h"
 | 
			
		||||
#include "commandtag.h"
 | 
			
		||||
#include "databasefactory.h"
 | 
			
		||||
#include "logger.h"
 | 
			
		||||
#include "sandboxedprocessor.h"
 | 
			
		||||
@@ -32,7 +31,7 @@
 | 
			
		||||
void printUsage(QString argv0)
 | 
			
		||||
{
 | 
			
		||||
	qInfo() << "Usage:" << argv0 << "command";
 | 
			
		||||
	qInfo() << "Valid commands: add, update, search, delete, tag, list. Each command has a --help option.";
 | 
			
		||||
	qInfo() << "Valid commands: add, update, delete, search, list. Each command has a --help option.";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Command *commandFromName(QString name, SqliteDbService &dbService)
 | 
			
		||||
@@ -57,10 +56,6 @@ Command *commandFromName(QString name, SqliteDbService &dbService)
 | 
			
		||||
	{
 | 
			
		||||
		return new CommandList(dbService);
 | 
			
		||||
	}
 | 
			
		||||
	if(name == "tag")
 | 
			
		||||
	{
 | 
			
		||||
		return new CommandTag(dbService);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nullptr;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -193,7 +193,7 @@ int main(int argc, char *argv[])
 | 
			
		||||
															   Logger::error() << error << Qt::endl;
 | 
			
		||||
															   QMessageBox::critical(nullptr, "Error during upgrade",
 | 
			
		||||
																					 error);
 | 
			
		||||
															   exit(EXIT_FAILURE);
 | 
			
		||||
															   qApp->quit();
 | 
			
		||||
														   }
 | 
			
		||||
 | 
			
		||||
								 );
 | 
			
		||||
 
 | 
			
		||||
@@ -16,9 +16,6 @@
 | 
			
		||||
#include <QScreen>
 | 
			
		||||
#include <QProgressDialog>
 | 
			
		||||
#include <QDesktopWidget>
 | 
			
		||||
#include <QWidgetAction>
 | 
			
		||||
#include <QInputDialog>
 | 
			
		||||
 | 
			
		||||
#include "mainwindow.h"
 | 
			
		||||
#include "ui_mainwindow.h"
 | 
			
		||||
#include "clicklabel.h"
 | 
			
		||||
@@ -45,9 +42,6 @@ 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);
 | 
			
		||||
@@ -293,7 +287,6 @@ void MainWindow::startIndexing()
 | 
			
		||||
	if(this->indexer->isRunning())
 | 
			
		||||
	{
 | 
			
		||||
		ui->btnStartIndexing->setEnabled(false);
 | 
			
		||||
 | 
			
		||||
		ui->btnStartIndexing->setText("Start indexing");
 | 
			
		||||
		this->indexer->requestCancellation();
 | 
			
		||||
		return;
 | 
			
		||||
@@ -303,8 +296,6 @@ 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);
 | 
			
		||||
@@ -352,8 +343,6 @@ 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)
 | 
			
		||||
	{
 | 
			
		||||
@@ -888,8 +877,6 @@ 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->previewCoordinator.previewableCount());
 | 
			
		||||
	if(hasDeleted)
 | 
			
		||||
@@ -1019,65 +1006,6 @@ void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
 | 
			
		||||
						   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)
 | 
			
		||||
@@ -1132,7 +1060,6 @@ MainWindow::~MainWindow()
 | 
			
		||||
	delete this->dbService;
 | 
			
		||||
	delete this->dbFactory;
 | 
			
		||||
	delete this->indexer;
 | 
			
		||||
	delete this->tagManager;
 | 
			
		||||
	delete ui;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@
 | 
			
		||||
#include "../shared/indexsyncer.h"
 | 
			
		||||
#include "previewcoordinator.h"
 | 
			
		||||
#include "indexer.h"
 | 
			
		||||
#include "tagmanager.h"
 | 
			
		||||
namespace Ui
 | 
			
		||||
{
 | 
			
		||||
class MainWindow;
 | 
			
		||||
@@ -40,9 +39,6 @@ class MainWindow : public QMainWindow
 | 
			
		||||
	QFutureWatcher<QVector<SearchResult>> searchWatcher;
 | 
			
		||||
	LooqsQuery contentSearchQuery;
 | 
			
		||||
	QVector<QString> searchHistory;
 | 
			
		||||
 | 
			
		||||
	TagManager *tagManager;
 | 
			
		||||
 | 
			
		||||
	int currentSearchHistoryIndex = 0;
 | 
			
		||||
	QString currentSavedSearchText;
 | 
			
		||||
	bool previewDirty = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -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).pages.constFirst().content;
 | 
			
		||||
	QString content = tsp.process(entireContent).constFirst().content;
 | 
			
		||||
 | 
			
		||||
	PreviewGeneratorPlainText plainTextGenerator;
 | 
			
		||||
	result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName()));
 | 
			
		||||
 
 | 
			
		||||
@@ -246,21 +246,17 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
 | 
			
		||||
			totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if(!resultText.isEmpty())
 | 
			
		||||
	if(isTruncated)
 | 
			
		||||
	{
 | 
			
		||||
		if(isTruncated)
 | 
			
		||||
		{
 | 
			
		||||
			header += "(truncated) ";
 | 
			
		||||
		}
 | 
			
		||||
		for(QString &word : config.wordsToHighlight)
 | 
			
		||||
		{
 | 
			
		||||
			header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
 | 
			
		||||
		}
 | 
			
		||||
		header += "<hr>";
 | 
			
		||||
 | 
			
		||||
		resultText = header + resultText;
 | 
			
		||||
		header += "(truncated) ";
 | 
			
		||||
	}
 | 
			
		||||
	return resultText;
 | 
			
		||||
	for(QString &word : config.wordsToHighlight)
 | 
			
		||||
	{
 | 
			
		||||
		header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
 | 
			
		||||
	}
 | 
			
		||||
	header += "<hr>";
 | 
			
		||||
 | 
			
		||||
	return header + resultText;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath,
 | 
			
		||||
 
 | 
			
		||||
@@ -24,9 +24,7 @@ QString DefaultTextProcessor::processText(const QByteArray &data) const
 | 
			
		||||
	return {};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DocumentProcessResult DefaultTextProcessor::process(const QByteArray &data) const
 | 
			
		||||
QVector<PageData> DefaultTextProcessor::process(const QByteArray &data) const
 | 
			
		||||
{
 | 
			
		||||
	DocumentProcessResult result;
 | 
			
		||||
	result.pages.append({0, processText(data)});
 | 
			
		||||
	return result;
 | 
			
		||||
	return {{0, processText(data)}};
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ class DefaultTextProcessor : public Processor
 | 
			
		||||
  public:
 | 
			
		||||
	DefaultTextProcessor();
 | 
			
		||||
	QString processText(const QByteArray &data) const;
 | 
			
		||||
	DocumentProcessResult process(const QByteArray &data) const override;
 | 
			
		||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // DEFAULTTEXTPROCESSOR_H
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
#include "documentoutlineentry.h"
 | 
			
		||||
 | 
			
		||||
DocumentOutlineEntry::DocumentOutlineEntry()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDataStream &operator<<(QDataStream &out, const DocumentOutlineEntry &pd)
 | 
			
		||||
{
 | 
			
		||||
	out << pd.text << pd.type << pd.destinationPage;
 | 
			
		||||
	out << pd.children.size();
 | 
			
		||||
	for(const DocumentOutlineEntry &entry : pd.children)
 | 
			
		||||
	{
 | 
			
		||||
		out << entry;
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDataStream &operator>>(QDataStream &in, DocumentOutlineEntry &pd)
 | 
			
		||||
{
 | 
			
		||||
	in >> pd.text >> pd.type >> pd.destinationPage;
 | 
			
		||||
 | 
			
		||||
	int numChildren;
 | 
			
		||||
	in >> numChildren;
 | 
			
		||||
	for(int i = 0; i < numChildren; i++)
 | 
			
		||||
	{
 | 
			
		||||
		DocumentOutlineEntry entry;
 | 
			
		||||
		in >> entry;
 | 
			
		||||
		pd.children.append(entry);
 | 
			
		||||
	}
 | 
			
		||||
	return in;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
#ifndef DOCUMENTOUTLINEENTRY_H
 | 
			
		||||
#define DOCUMENTOUTLINEENTRY_H
 | 
			
		||||
#include <QMetaType>
 | 
			
		||||
#include <QDataStream>
 | 
			
		||||
#include <QString>
 | 
			
		||||
 | 
			
		||||
enum OutlineDestinationType
 | 
			
		||||
{
 | 
			
		||||
	OUTLINE_DESTINATION_TYPE_NONE,
 | 
			
		||||
	OUTLINE_DESTINATION_TYPE_PAGE
 | 
			
		||||
	/* In the future, links, or #anchors are possible */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
class DocumentOutlineEntry
 | 
			
		||||
{
 | 
			
		||||
  public:
 | 
			
		||||
	DocumentOutlineEntry();
 | 
			
		||||
	QVector<DocumentOutlineEntry> children;
 | 
			
		||||
	OutlineDestinationType type;
 | 
			
		||||
	QString text;
 | 
			
		||||
	unsigned int destinationPage;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Q_DECLARE_METATYPE(DocumentOutlineEntry);
 | 
			
		||||
 | 
			
		||||
QDataStream &operator<<(QDataStream &out, const DocumentOutlineEntry &pd);
 | 
			
		||||
QDataStream &operator>>(QDataStream &in, DocumentOutlineEntry &pd);
 | 
			
		||||
 | 
			
		||||
#endif // DOCUMENTOUTLINEENTRY_H
 | 
			
		||||
@@ -1,39 +0,0 @@
 | 
			
		||||
#include "documentprocessresult.h"
 | 
			
		||||
 | 
			
		||||
QDataStream &operator<<(QDataStream &out, const DocumentProcessResult &pd)
 | 
			
		||||
{
 | 
			
		||||
	out << pd.pages.size();
 | 
			
		||||
	out << pd.outlines.size();
 | 
			
		||||
	for(const PageData &pd : pd.pages)
 | 
			
		||||
	{
 | 
			
		||||
		out << pd;
 | 
			
		||||
	}
 | 
			
		||||
	for(const DocumentOutlineEntry &outline : pd.outlines)
 | 
			
		||||
	{
 | 
			
		||||
		out << outline;
 | 
			
		||||
	}
 | 
			
		||||
	return out;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QDataStream &operator>>(QDataStream &in, DocumentProcessResult &pd)
 | 
			
		||||
{
 | 
			
		||||
	int numPages, numOutlines;
 | 
			
		||||
	in >> numPages;
 | 
			
		||||
	in >> numOutlines;
 | 
			
		||||
 | 
			
		||||
	for(int i = 0; i < numPages; i++)
 | 
			
		||||
	{
 | 
			
		||||
		PageData data;
 | 
			
		||||
		in >> data;
 | 
			
		||||
		pd.pages.append(data);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for(int i = 0; i < numOutlines; i++)
 | 
			
		||||
	{
 | 
			
		||||
		DocumentOutlineEntry outline;
 | 
			
		||||
		in >> outline;
 | 
			
		||||
		pd.outlines.append(outline);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return in;
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
#ifndef DOCUMENTPROCESSRESULT_H
 | 
			
		||||
#define DOCUMENTPROCESSRESULT_H
 | 
			
		||||
#include <pagedata.h>
 | 
			
		||||
#include <documentoutlineentry.h>
 | 
			
		||||
 | 
			
		||||
class DocumentProcessResult
 | 
			
		||||
{
 | 
			
		||||
  public:
 | 
			
		||||
	QVector<PageData> pages;
 | 
			
		||||
	QVector<DocumentOutlineEntry> outlines;
 | 
			
		||||
};
 | 
			
		||||
Q_DECLARE_METATYPE(DocumentProcessResult);
 | 
			
		||||
 | 
			
		||||
QDataStream &operator<<(QDataStream &out, const DocumentProcessResult &pd);
 | 
			
		||||
QDataStream &operator>>(QDataStream &in, DocumentProcessResult &pd);
 | 
			
		||||
 | 
			
		||||
#endif // DOCUMENTPROCESSRESULT_H
 | 
			
		||||
@@ -25,22 +25,10 @@ SaveFileResult FileSaver::addFile(QString path)
 | 
			
		||||
	QString absPath = info.absoluteFilePath();
 | 
			
		||||
 | 
			
		||||
	auto mtime = info.lastModified().toSecsSinceEpoch();
 | 
			
		||||
 | 
			
		||||
	bool exists = false;
 | 
			
		||||
	if(this->fileSaverOptions.fillExistingContentless)
 | 
			
		||||
	{
 | 
			
		||||
		exists = this->dbService->fileExistsInDatabase(absPath, mtime, 'c');
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		exists = this->dbService->fileExistsInDatabase(absPath, mtime);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(exists)
 | 
			
		||||
	if(this->dbService->fileExistsInDatabase(absPath, mtime))
 | 
			
		||||
	{
 | 
			
		||||
		return SKIPPED;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return saveFile(info);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -110,7 +98,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
 | 
			
		||||
 | 
			
		||||
SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
			
		||||
{
 | 
			
		||||
	DocumentProcessResult processResult;
 | 
			
		||||
	QVector<PageData> pageData;
 | 
			
		||||
	QString canonicalPath = fileInfo.canonicalFilePath();
 | 
			
		||||
 | 
			
		||||
	int processorReturnCode = -1;
 | 
			
		||||
@@ -146,7 +134,10 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
			
		||||
			if(mustFillContent)
 | 
			
		||||
			{
 | 
			
		||||
				auto filetype = this->dbService->queryFileType(fileInfo.absolutePath());
 | 
			
		||||
				mustFillContent = !filetype.has_value() || filetype.value() == 'c';
 | 
			
		||||
				if(filetype)
 | 
			
		||||
				{
 | 
			
		||||
					mustFillContent = filetype.value() == 'c';
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -169,10 +160,11 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
			
		||||
			 * finishes.
 | 
			
		||||
			 */
 | 
			
		||||
			QDataStream in(process.readAllStandardOutput());
 | 
			
		||||
 | 
			
		||||
			if(!in.atEnd())
 | 
			
		||||
			while(!in.atEnd())
 | 
			
		||||
			{
 | 
			
		||||
				in >> processResult;
 | 
			
		||||
				PageData pd;
 | 
			
		||||
				in >> pd;
 | 
			
		||||
				pageData.append(pd);
 | 
			
		||||
			}
 | 
			
		||||
			processorReturnCode = process.exitCode();
 | 
			
		||||
			if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY)
 | 
			
		||||
@@ -184,7 +176,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	SaveFileResult result = this->dbService->saveFile(fileInfo, processResult, this->fileSaverOptions.metadataOnly);
 | 
			
		||||
	SaveFileResult result = this->dbService->saveFile(fileInfo, pageData, this->fileSaverOptions.metadataOnly);
 | 
			
		||||
	if(result == OK && processorReturnCode == OK_WASEMPTY)
 | 
			
		||||
	{
 | 
			
		||||
		return OK_WASEMPTY;
 | 
			
		||||
 
 | 
			
		||||
@@ -21,18 +21,11 @@ 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() << Qt::endl;
 | 
			
		||||
			Logger::error() << e.what();
 | 
			
		||||
			sfr = PROCESSFAIL; // well...
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		emit result({path, sfr});
 | 
			
		||||
		if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck
 | 
			
		||||
		{
 | 
			
		||||
 
 | 
			
		||||
@@ -111,20 +111,6 @@ void Indexer::dirScanProgress(int current, int total)
 | 
			
		||||
 | 
			
		||||
void Indexer::processFileScanResult(FileScanResult result)
 | 
			
		||||
{
 | 
			
		||||
	/* TODO: OK_WASEMPTY might need a special list */
 | 
			
		||||
	if(result.second == OK || result.second == OK_WASEMPTY)
 | 
			
		||||
	{
 | 
			
		||||
		++this->currentIndexResult.addedPaths;
 | 
			
		||||
	}
 | 
			
		||||
	else if(result.second == SKIPPED)
 | 
			
		||||
	{
 | 
			
		||||
		++this->currentIndexResult.skippedPaths;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		++this->currentIndexResult.erroredPaths;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(isErrorSaveFileResult(result.second))
 | 
			
		||||
	{
 | 
			
		||||
		this->currentIndexResult.results.append(result);
 | 
			
		||||
@@ -143,6 +129,20 @@ void Indexer::processFileScanResult(FileScanResult result)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* TODO: OK_WASEMPTY might need a special list */
 | 
			
		||||
	if(result.second == OK || result.second == OK_WASEMPTY)
 | 
			
		||||
	{
 | 
			
		||||
		++this->currentIndexResult.addedPaths;
 | 
			
		||||
	}
 | 
			
		||||
	else if(result.second == SKIPPED)
 | 
			
		||||
	{
 | 
			
		||||
		++this->currentIndexResult.skippedPaths;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		++this->currentIndexResult.erroredPaths;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QTime currentTime = QTime::currentTime();
 | 
			
		||||
	if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10)
 | 
			
		||||
	{
 | 
			
		||||
@@ -171,8 +171,3 @@ void Indexer::setFileSaverOptions(FileSaverOptions options)
 | 
			
		||||
{
 | 
			
		||||
	this->fileSaverOptions = options;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void Indexer::setProgressReportThreshold(int threshold)
 | 
			
		||||
{
 | 
			
		||||
	this->progressReportThreshold = threshold;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -82,8 +82,6 @@ class Indexer : public QObject
 | 
			
		||||
 | 
			
		||||
	void setFileSaverOptions(FileSaverOptions options);
 | 
			
		||||
 | 
			
		||||
	void setProgressReportThreshold(int threshold);
 | 
			
		||||
 | 
			
		||||
	void requestCancellation();
 | 
			
		||||
 | 
			
		||||
	Indexer(SqliteDbService &db);
 | 
			
		||||
 
 | 
			
		||||
@@ -29,11 +29,6 @@ bool LooqsQuery::hasContentSearch() const
 | 
			
		||||
	return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LooqsQuery::hasOutlineSearch() const
 | 
			
		||||
{
 | 
			
		||||
	return (this->getTokensMask() & FILTER_OUTLINE_CONTAINS) == FILTER_OUTLINE_CONTAINS;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool LooqsQuery::hasPathSearch() const
 | 
			
		||||
{
 | 
			
		||||
	return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH;
 | 
			
		||||
@@ -290,14 +285,6 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
 | 
			
		||||
			{
 | 
			
		||||
				tokenType = FILTER_CONTENT_PAGE;
 | 
			
		||||
			}
 | 
			
		||||
			else if(filtername == "t" || filtername == "tag")
 | 
			
		||||
			{
 | 
			
		||||
				tokenType = FILTER_TAG_ASSIGNED;
 | 
			
		||||
			}
 | 
			
		||||
			else if(filtername == "toc" || filtername == "outline")
 | 
			
		||||
			{
 | 
			
		||||
				tokenType = FILTER_OUTLINE_CONTAINS;
 | 
			
		||||
			}
 | 
			
		||||
			// TODO: given this is not really a "filter", this feels slightly misplaced here
 | 
			
		||||
			else if(filtername == "sort")
 | 
			
		||||
			{
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,6 @@ class LooqsQuery
 | 
			
		||||
		this->limit = limit;
 | 
			
		||||
	}
 | 
			
		||||
	bool hasContentSearch() const;
 | 
			
		||||
	bool hasOutlineSearch() const;
 | 
			
		||||
	bool hasPathSearch() const;
 | 
			
		||||
 | 
			
		||||
	void addSortCondition(SortCondition sc);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +0,0 @@
 | 
			
		||||
CREATE TABLE tag(id integer PRIMARY KEY, name varchar(128) UNIQUE);
 | 
			
		||||
CREATE TABLE filetag(fileid integer, tagid integer);
 | 
			
		||||
CREATE INDEX filetag_fileid ON filetag(fileid);
 | 
			
		||||
CREATE INDEX tag_id ON tag(id);
 | 
			
		||||
CREATE INDEX file_path ON file ( path );
 | 
			
		||||
UPDATE file SET filetype='c' WHERE filetype='f';
 | 
			
		||||
@@ -1,2 +0,0 @@
 | 
			
		||||
CREATE TABLE outline(id INTEGER PRIMARY KEY, fileid INTEGER REFERENCES file (id) ON DELETE CASCADE, text varchar(1024), page integer);
 | 
			
		||||
CREATE INDEX outline_fileid ON outline (fileid);
 | 
			
		||||
@@ -4,7 +4,5 @@
 | 
			
		||||
        <file>2.sql</file>
 | 
			
		||||
        <file>3.sql</file>
 | 
			
		||||
        <file>4.sql</file>
 | 
			
		||||
        <file>5.sql</file>
 | 
			
		||||
        <file>6.sql</file>
 | 
			
		||||
    </qresource>
 | 
			
		||||
</RCC>
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ class NothingProcessor : public Processor
 | 
			
		||||
	NothingProcessor();
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	DocumentProcessResult process(const QByteArray & /*data*/) const override
 | 
			
		||||
	QVector<PageData> process(const QByteArray & /*data*/) const override
 | 
			
		||||
	{
 | 
			
		||||
		return {};
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@
 | 
			
		||||
#include "odtprocessor.h"
 | 
			
		||||
#include "tagstripperprocessor.h"
 | 
			
		||||
 | 
			
		||||
DocumentProcessResult OdtProcessor::process(const QByteArray & /*data*/) const
 | 
			
		||||
QVector<PageData> OdtProcessor::process(const QByteArray & /*data*/) const
 | 
			
		||||
{
 | 
			
		||||
	throw LooqsGeneralException("Not implemented yet");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DocumentProcessResult OdtProcessor::process(QString path) const
 | 
			
		||||
QVector<PageData> OdtProcessor::process(QString path) const
 | 
			
		||||
{
 | 
			
		||||
	QuaZipFile zipFile(path);
 | 
			
		||||
	zipFile.setFileName("content.xml");
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,9 @@ class OdtProcessor : public Processor
 | 
			
		||||
	{
 | 
			
		||||
		this->PREFERED_DATA_SOURCE = FILEPATH;
 | 
			
		||||
	}
 | 
			
		||||
	DocumentProcessResult process(const QByteArray &data) const override;
 | 
			
		||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
			
		||||
 | 
			
		||||
	DocumentProcessResult process(QString path) const override;
 | 
			
		||||
	QVector<PageData> process(QString path) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // ODTPROCESSOR_H
 | 
			
		||||
 
 | 
			
		||||
@@ -5,30 +5,9 @@ PdfProcessor::PdfProcessor()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVector<DocumentOutlineEntry> PdfProcessor::createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const
 | 
			
		||||
QVector<PageData> PdfProcessor::process(const QByteArray &data) const
 | 
			
		||||
{
 | 
			
		||||
	QVector<DocumentOutlineEntry> result;
 | 
			
		||||
	for(const Poppler::OutlineItem &outlineItem : outlineItems)
 | 
			
		||||
	{
 | 
			
		||||
		DocumentOutlineEntry documentOutlineEntry;
 | 
			
		||||
		documentOutlineEntry.text = outlineItem.name();
 | 
			
		||||
		documentOutlineEntry.type = OUTLINE_DESTINATION_TYPE_PAGE;
 | 
			
		||||
		if(!outlineItem.destination().isNull())
 | 
			
		||||
		{
 | 
			
		||||
			documentOutlineEntry.destinationPage = outlineItem.destination()->pageNumber();
 | 
			
		||||
		}
 | 
			
		||||
		if(outlineItem.hasChildren())
 | 
			
		||||
		{
 | 
			
		||||
			documentOutlineEntry.children = createOutline(outlineItem.children());
 | 
			
		||||
		}
 | 
			
		||||
		result.append(documentOutlineEntry);
 | 
			
		||||
	}
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DocumentProcessResult PdfProcessor::process(const QByteArray &data) const
 | 
			
		||||
{
 | 
			
		||||
	DocumentProcessResult result;
 | 
			
		||||
	QVector<PageData> result;
 | 
			
		||||
	QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data));
 | 
			
		||||
	if(doc.isNull())
 | 
			
		||||
	{
 | 
			
		||||
@@ -47,13 +26,12 @@ DocumentProcessResult PdfProcessor::process(const QByteArray &data) const
 | 
			
		||||
	for(auto i = 0; i < pagecount; i++)
 | 
			
		||||
	{
 | 
			
		||||
		QString text = doc->page(i)->text(entirePage);
 | 
			
		||||
		result.pages.append({static_cast<unsigned int>(i + 1), text});
 | 
			
		||||
		result.append({static_cast<unsigned int>(i + 1), text});
 | 
			
		||||
		/*TODO: hack, so we can fts search several words over the whole document, not just pages.
 | 
			
		||||
		 * this of course uses more space and should be solved differently.
 | 
			
		||||
		 */
 | 
			
		||||
		entire += text;
 | 
			
		||||
	}
 | 
			
		||||
	result.pages.append({0, entire});
 | 
			
		||||
	result.outlines = createOutline(doc->outline());
 | 
			
		||||
	result.append({0, entire});
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
#ifndef PDFPROCESSOR_H
 | 
			
		||||
#define PDFPROCESSOR_H
 | 
			
		||||
#include <poppler-qt5.h>
 | 
			
		||||
#include "processor.h"
 | 
			
		||||
class PdfProcessor : public Processor
 | 
			
		||||
{
 | 
			
		||||
@@ -8,8 +7,7 @@ class PdfProcessor : public Processor
 | 
			
		||||
	PdfProcessor();
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	QVector<DocumentOutlineEntry> createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const;
 | 
			
		||||
	DocumentProcessResult process(const QByteArray &data) const override;
 | 
			
		||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // PDFPROCESSOR_H
 | 
			
		||||
 
 | 
			
		||||
@@ -2,8 +2,8 @@
 | 
			
		||||
#define PROCESSOR_H
 | 
			
		||||
#include <QVector>
 | 
			
		||||
#include <QFile>
 | 
			
		||||
#include "pagedata.h"
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
#include "documentprocessresult.h"
 | 
			
		||||
enum DataSource
 | 
			
		||||
{
 | 
			
		||||
	FILEPATH,
 | 
			
		||||
@@ -18,8 +18,8 @@ class Processor
 | 
			
		||||
	 * a single file */
 | 
			
		||||
	DataSource PREFERED_DATA_SOURCE = ARRAY;
 | 
			
		||||
	Processor();
 | 
			
		||||
	virtual DocumentProcessResult process(const QByteArray &data) const = 0;
 | 
			
		||||
	virtual DocumentProcessResult process(QString path) const
 | 
			
		||||
	virtual QVector<PageData> process(const QByteArray &data) const = 0;
 | 
			
		||||
	virtual QVector<PageData> process(QString path) const
 | 
			
		||||
	{
 | 
			
		||||
		return process(Utils::readFile(path));
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -65,12 +65,18 @@ void SandboxedProcessor::enableSandbox(QString readablePath)
 | 
			
		||||
	exile_free_policy(policy);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void SandboxedProcessor::printResults(const DocumentProcessResult &result)
 | 
			
		||||
void SandboxedProcessor::printResults(const QVector<PageData> &pageData)
 | 
			
		||||
{
 | 
			
		||||
	QFile fsstdout;
 | 
			
		||||
	fsstdout.open(stdout, QIODevice::WriteOnly);
 | 
			
		||||
	QDataStream stream(&fsstdout);
 | 
			
		||||
	stream << result;
 | 
			
		||||
 | 
			
		||||
	for(const PageData &data : pageData)
 | 
			
		||||
	{
 | 
			
		||||
		stream << data;
 | 
			
		||||
		// fsstdout.flush();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fsstdout.close();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -96,7 +102,7 @@ SaveFileResult SandboxedProcessor::process()
 | 
			
		||||
		return OK;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	DocumentProcessResult processResult;
 | 
			
		||||
	QVector<PageData> pageData;
 | 
			
		||||
	QString absPath = fileInfo.absoluteFilePath();
 | 
			
		||||
 | 
			
		||||
	try
 | 
			
		||||
@@ -105,13 +111,13 @@ SaveFileResult SandboxedProcessor::process()
 | 
			
		||||
		{
 | 
			
		||||
			/* Read access to FS needed... doh..*/
 | 
			
		||||
			enableSandbox(absPath);
 | 
			
		||||
			processResult = processor->process(absPath);
 | 
			
		||||
			pageData = processor->process(absPath);
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			QByteArray data = Utils::readFile(absPath);
 | 
			
		||||
			enableSandbox();
 | 
			
		||||
			processResult = processor->process(data);
 | 
			
		||||
			pageData = processor->process(data);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	catch(LooqsGeneralException &e)
 | 
			
		||||
@@ -120,6 +126,6 @@ SaveFileResult SandboxedProcessor::process()
 | 
			
		||||
		return PROCESSFAIL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	printResults(processResult);
 | 
			
		||||
	return processResult.pages.isEmpty() ? OK_WASEMPTY : OK;
 | 
			
		||||
	printResults(pageData);
 | 
			
		||||
	return pageData.isEmpty() ? OK_WASEMPTY : OK;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
#define SANDBOXEDPROCESSOR_H
 | 
			
		||||
#include <QString>
 | 
			
		||||
#include <QMimeDatabase>
 | 
			
		||||
#include "documentprocessresult.h"
 | 
			
		||||
#include "pagedata.h"
 | 
			
		||||
#include "savefileresult.h"
 | 
			
		||||
 | 
			
		||||
class SandboxedProcessor
 | 
			
		||||
@@ -12,7 +12,7 @@ class SandboxedProcessor
 | 
			
		||||
	QMimeDatabase mimeDatabase;
 | 
			
		||||
 | 
			
		||||
	void enableSandbox(QString readablePath = "");
 | 
			
		||||
	void printResults(const DocumentProcessResult &pageData);
 | 
			
		||||
	void printResults(const QVector<PageData> &pageData);
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	SandboxedProcessor(QString filepath)
 | 
			
		||||
 
 | 
			
		||||
@@ -42,8 +42,6 @@ SOURCES += sqlitesearch.cpp \
 | 
			
		||||
    dbmigrator.cpp \
 | 
			
		||||
    defaulttextprocessor.cpp \
 | 
			
		||||
    dirscanworker.cpp \
 | 
			
		||||
    documentoutlineentry.cpp \
 | 
			
		||||
    documentprocessresult.cpp \
 | 
			
		||||
    encodingdetector.cpp \
 | 
			
		||||
    filesaver.cpp \
 | 
			
		||||
    filescanworker.cpp \
 | 
			
		||||
@@ -62,7 +60,6 @@ SOURCES += sqlitesearch.cpp \
 | 
			
		||||
    processor.cpp \
 | 
			
		||||
    sandboxedprocessor.cpp \
 | 
			
		||||
    sqlitedbservice.cpp \
 | 
			
		||||
    tagmanager.cpp \
 | 
			
		||||
    tagstripperprocessor.cpp \
 | 
			
		||||
    utils.cpp \
 | 
			
		||||
    ../submodules/exile.h/exile.c \
 | 
			
		||||
@@ -74,8 +71,6 @@ HEADERS += sqlitesearch.h \
 | 
			
		||||
    dbmigrator.h \
 | 
			
		||||
    defaulttextprocessor.h \
 | 
			
		||||
    dirscanworker.h \
 | 
			
		||||
    documentoutlineentry.h \
 | 
			
		||||
    documentprocessresult.h \
 | 
			
		||||
    encodingdetector.h \
 | 
			
		||||
    filedata.h \
 | 
			
		||||
    filesaver.h \
 | 
			
		||||
@@ -98,7 +93,6 @@ HEADERS += sqlitesearch.h \
 | 
			
		||||
    savefileresult.h \
 | 
			
		||||
    searchresult.h \
 | 
			
		||||
    sqlitedbservice.h \
 | 
			
		||||
    tagmanager.h \
 | 
			
		||||
    tagstripperprocessor.h \
 | 
			
		||||
    token.h \
 | 
			
		||||
    common.h \
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,6 @@
 | 
			
		||||
#include <QFileInfo>
 | 
			
		||||
#include <QDateTime>
 | 
			
		||||
#include <QSqlError>
 | 
			
		||||
#include "looqsgeneralexception.h"
 | 
			
		||||
#include "sqlitedbservice.h"
 | 
			
		||||
#include "filedata.h"
 | 
			
		||||
#include "logger.h"
 | 
			
		||||
@@ -16,7 +15,7 @@ QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
 | 
			
		||||
 | 
			
		||||
std::optional<QChar> SqliteDbService::queryFileType(QString absPath)
 | 
			
		||||
{
 | 
			
		||||
	auto query = exec("SELECT filetype FROM file WHERE path = ?", {absPath});
 | 
			
		||||
	auto query = exec("SELCET filetype FROM file WHERE path = ?", {absPath});
 | 
			
		||||
	if(!query.next())
 | 
			
		||||
	{
 | 
			
		||||
		return {};
 | 
			
		||||
@@ -104,117 +103,6 @@ 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;
 | 
			
		||||
@@ -253,29 +141,6 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool SqliteDbService::insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines)
 | 
			
		||||
{
 | 
			
		||||
	QSqlQuery outlineQuery(db);
 | 
			
		||||
	outlineQuery.prepare("INSERT INTO outline(fileid, text, page) VALUES(?,?,?)");
 | 
			
		||||
	outlineQuery.addBindValue(fileid);
 | 
			
		||||
	for(const DocumentOutlineEntry &outline : outlines)
 | 
			
		||||
	{
 | 
			
		||||
		outlineQuery.bindValue(1, outline.text.toLower());
 | 
			
		||||
		outlineQuery.bindValue(2, outline.destinationPage);
 | 
			
		||||
		if(!outlineQuery.exec())
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Failed outline insertion " << outlineQuery.lastError() << Qt::endl;
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
		if(!insertOutline(db, fileid, outline.children))
 | 
			
		||||
		{
 | 
			
		||||
			Logger::error() << "Failed outline insertion (children)) " << outlineQuery.lastError() << Qt::endl;
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args)
 | 
			
		||||
{
 | 
			
		||||
	auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
			
		||||
@@ -286,7 +151,7 @@ QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant
 | 
			
		||||
	}
 | 
			
		||||
	if(!query.exec())
 | 
			
		||||
	{
 | 
			
		||||
		throw LooqsGeneralException("Error while exec(): " + query.lastError().text() + " for query: " + querystr);
 | 
			
		||||
		throw LooqsGeneralException("Error while trying to query for file existance: " + query.lastError().text());
 | 
			
		||||
	}
 | 
			
		||||
	return query;
 | 
			
		||||
}
 | 
			
		||||
@@ -301,7 +166,7 @@ bool SqliteDbService::execBool(QString querystr, std::initializer_list<QVariant>
 | 
			
		||||
	return query.value(0).toBool();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResult &processResult, bool pathsOnly)
 | 
			
		||||
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly)
 | 
			
		||||
{
 | 
			
		||||
	QString absPath = fileInfo.absoluteFilePath();
 | 
			
		||||
	auto mtime = fileInfo.lastModified().toSecsSinceEpoch();
 | 
			
		||||
@@ -346,24 +211,18 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResu
 | 
			
		||||
	if(!pathsOnly)
 | 
			
		||||
	{
 | 
			
		||||
		int lastid = inserterQuery.lastInsertId().toInt();
 | 
			
		||||
		if(!insertToFTS(false, db, lastid, processResult.pages))
 | 
			
		||||
		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, processResult.pages))
 | 
			
		||||
		if(!insertToFTS(true, db, lastid, pageData))
 | 
			
		||||
		{
 | 
			
		||||
			db.rollback();
 | 
			
		||||
			Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
 | 
			
		||||
			return DBFAIL;
 | 
			
		||||
		}
 | 
			
		||||
		if(!insertOutline(db, lastid, processResult.outlines))
 | 
			
		||||
		{
 | 
			
		||||
			db.rollback();
 | 
			
		||||
			Logger::error() << "Failed to insert outline data " << Qt::endl;
 | 
			
		||||
			return DBFAIL;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(!db.commit())
 | 
			
		||||
@@ -374,123 +233,3 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResu
 | 
			
		||||
	}
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@
 | 
			
		||||
 | 
			
		||||
#include "databasefactory.h"
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
#include "documentprocessresult.h"
 | 
			
		||||
#include "pagedata.h"
 | 
			
		||||
#include "filedata.h"
 | 
			
		||||
#include "../shared/sqlitesearch.h"
 | 
			
		||||
#include "../shared/token.h"
 | 
			
		||||
@@ -22,27 +22,15 @@ class SqliteDbService
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	SqliteDbService(DatabaseFactory &dbFactory);
 | 
			
		||||
	SaveFileResult saveFile(QFileInfo fileInfo, DocumentProcessResult &pageData, bool pathsOnly);
 | 
			
		||||
 | 
			
		||||
	SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
 | 
			
		||||
	unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
 | 
			
		||||
	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);
 | 
			
		||||
	bool insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // SQLITEDBSERVICE_H
 | 
			
		||||
 
 | 
			
		||||
@@ -143,16 +143,6 @@ 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()}};
 | 
			
		||||
	}
 | 
			
		||||
	if(token.type == FILTER_OUTLINE_CONTAINS)
 | 
			
		||||
	{
 | 
			
		||||
		return {" outline.text LIKE '%' || ? || '%'  ", {value.toLower()}};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	throw LooqsGeneralException("Unknown token passed (should not happen)");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -161,7 +151,6 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
 | 
			
		||||
	QString whereSql;
 | 
			
		||||
	QVector<QString> bindValues;
 | 
			
		||||
	bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
 | 
			
		||||
	bool isOutlineSearch = query.hasOutlineSearch();
 | 
			
		||||
	if(query.getTokens().isEmpty())
 | 
			
		||||
	{
 | 
			
		||||
		throw LooqsGeneralException("Nothing to search for supplied");
 | 
			
		||||
@@ -190,38 +179,31 @@ 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
 | 
			
		||||
	{
 | 
			
		||||
		QString pageColumn = "'0' as page";
 | 
			
		||||
		QString joiners = "";
 | 
			
		||||
		if(isOutlineSearch)
 | 
			
		||||
		{
 | 
			
		||||
			pageColumn = "outline.page as page";
 | 
			
		||||
			joiners = " INNER JOIN outline ON outline.fileid = file.id ";
 | 
			
		||||
		}
 | 
			
		||||
		if(sortSql.isEmpty())
 | 
			
		||||
		{
 | 
			
		||||
			sortSql = "ORDER BY file.mtime DESC";
 | 
			
		||||
		}
 | 
			
		||||
		prepSql = "SELECT DISTINCT file.path AS path, " + pageColumn +
 | 
			
		||||
				  ",file.mtime AS mtime, file.size AS size, "
 | 
			
		||||
				  "file.filetype AS filetype FROM file" +
 | 
			
		||||
				  joiners + " WHERE  1=1 AND " + whereSql + " " + sortSql;
 | 
			
		||||
		prepSql = "SELECT file.path AS path, '0' as page,  file.mtime AS mtime, file.size AS size, file.filetype AS "
 | 
			
		||||
				  "filetype FROM file WHERE  1=1 AND " +
 | 
			
		||||
				  whereSql + " " + sortSql;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(query.getLimit() > 0)
 | 
			
		||||
	{
 | 
			
		||||
		prepSql += " LIMIT " + QString::number(query.getLimit());
 | 
			
		||||
@@ -255,7 +237,7 @@ QVector<SearchResult> SqliteSearch::search(const LooqsQuery &query)
 | 
			
		||||
		throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool contentSearch = query.hasContentSearch() || query.hasOutlineSearch();
 | 
			
		||||
	bool contentSearch = query.hasContentSearch();
 | 
			
		||||
	while(dbQuery.next())
 | 
			
		||||
	{
 | 
			
		||||
		SearchResult result;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,66 +0,0 @@
 | 
			
		||||
#include "tagmanager.h"
 | 
			
		||||
 | 
			
		||||
TagManager::TagManager(SqliteDbService &dbService)
 | 
			
		||||
{
 | 
			
		||||
	this->dbService = &dbService;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TagManager::addTagsToPath(QString path, const QSet<QString> &tags)
 | 
			
		||||
{
 | 
			
		||||
	QVector<QString> currentTags = this->dbService->getTagsForPath(path);
 | 
			
		||||
	for(const QString &tag : tags)
 | 
			
		||||
	{
 | 
			
		||||
		currentTags.append(tag.toLower());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	QSet<QString> newTags{currentTags.begin(), currentTags.end()};
 | 
			
		||||
	return this->dbService->setTags(path, newTags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TagManager::removeTagsForPath(QString path, const QSet<QString> &tags)
 | 
			
		||||
{
 | 
			
		||||
	QVector<QString> currentTags = this->dbService->getTagsForPath(path);
 | 
			
		||||
	for(const QString &tag : tags)
 | 
			
		||||
	{
 | 
			
		||||
		currentTags.removeAll(tag);
 | 
			
		||||
	}
 | 
			
		||||
	QSet<QString> newTags{currentTags.begin(), currentTags.end()};
 | 
			
		||||
	return this->dbService->setTags(path, newTags);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TagManager::removePathsForTag(QString tag, const QVector<QString> &paths)
 | 
			
		||||
{
 | 
			
		||||
	return this->dbService->removePathsForTag(tag, paths);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TagManager::deleteTag(QString tag)
 | 
			
		||||
{
 | 
			
		||||
	return this->dbService->deleteTag(tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVector<QString> TagManager::getTags(QString path)
 | 
			
		||||
{
 | 
			
		||||
	return this->dbService->getTagsForPath(path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVector<QString> TagManager::getTags()
 | 
			
		||||
{
 | 
			
		||||
	return this->dbService->getTags();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
QVector<QString> TagManager::getPaths(QString tag)
 | 
			
		||||
{
 | 
			
		||||
	return this->dbService->getPathsForTag(tag);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TagManager::addTagsToPath(QString path, QString tagstring, QChar delim)
 | 
			
		||||
{
 | 
			
		||||
	auto splitted = tagstring.split(delim);
 | 
			
		||||
 | 
			
		||||
	return addTagsToPath(path, QSet<QString>{splitted.begin(), splitted.end()});
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool TagManager::addPathsToTag(QString tag, const QVector<QString> &paths)
 | 
			
		||||
{
 | 
			
		||||
	return this->dbService->addTag(tag, paths);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,28 +0,0 @@
 | 
			
		||||
#ifndef TAGMANAGER_H
 | 
			
		||||
#define TAGMANAGER_H
 | 
			
		||||
#include "sqlitedbservice.h"
 | 
			
		||||
 | 
			
		||||
class TagManager
 | 
			
		||||
{
 | 
			
		||||
  private:
 | 
			
		||||
	SqliteDbService *dbService = nullptr;
 | 
			
		||||
	bool ensurePathOkay(QString inpath);
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	TagManager(SqliteDbService &dbService);
 | 
			
		||||
 | 
			
		||||
	bool addTagsToPath(QString path, const QSet<QString> &tags);
 | 
			
		||||
	bool addTagsToPath(QString path, QString tagstring, QChar delim);
 | 
			
		||||
 | 
			
		||||
	bool addPathsToTag(QString tag, const QVector<QString> &paths);
 | 
			
		||||
	bool removeTagsForPath(QString path, const QSet<QString> &tags);
 | 
			
		||||
 | 
			
		||||
	bool removePathsForTag(QString tag, const QVector<QString> &paths);
 | 
			
		||||
	bool deleteTag(QString tag);
 | 
			
		||||
 | 
			
		||||
	QVector<QString> getTags(QString path);
 | 
			
		||||
	QVector<QString> getTags();
 | 
			
		||||
	QVector<QString> getPaths(QString tag);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // TAGMANAGER_H
 | 
			
		||||
@@ -4,11 +4,11 @@ TagStripperProcessor::TagStripperProcessor()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DocumentProcessResult TagStripperProcessor::process(const QByteArray &data) const
 | 
			
		||||
QVector<PageData> TagStripperProcessor::process(const QByteArray &data) const
 | 
			
		||||
{
 | 
			
		||||
	auto result = DefaultTextProcessor::process(data);
 | 
			
		||||
	// TODO: does not work properly with <br> and does not deal with entities...
 | 
			
		||||
	Q_ASSERT(result.pages.size() > 0);
 | 
			
		||||
	result.pages[0].content.remove(QRegExp("<[^>]*>"));
 | 
			
		||||
 | 
			
		||||
	result[0].content.remove(QRegExp("<[^>]*>"));
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@ class TagStripperProcessor : public DefaultTextProcessor
 | 
			
		||||
	TagStripperProcessor();
 | 
			
		||||
 | 
			
		||||
  public:
 | 
			
		||||
	DocumentProcessResult process(const QByteArray &data) const override;
 | 
			
		||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // XMLSTRIPPERPROCESSOR_H
 | 
			
		||||
 
 | 
			
		||||
@@ -19,9 +19,7 @@ enum TokenType
 | 
			
		||||
	FILTER_PATH_SIZE,
 | 
			
		||||
	FILTER_PATH_ENDS,
 | 
			
		||||
	FILTER_PATH_STARTS,
 | 
			
		||||
	FILTER_TAG_ASSIGNED,
 | 
			
		||||
	FILTER_OUTLINE_CONTAINS,
 | 
			
		||||
	FILTER_CONTENT = 512, /* Everything below here is content search (except LIMIT) */
 | 
			
		||||
	FILTER_CONTENT = 512,
 | 
			
		||||
	FILTER_CONTENT_CONTAINS,
 | 
			
		||||
	FILTER_CONTENT_PAGE,
 | 
			
		||||
	LIMIT = 1024
 | 
			
		||||
 
 | 
			
		||||
		Référencer dans un nouveau ticket
	
	Bloquer un utilisateur