Déan comparáid idir tiomáintí
	
		
			37 Tiomáintí
		
	
	
		
			a9d3f7897a
			...
			WIP/outlin
		
	
	| Údar | SHA1 | Dáta | |
|---|---|---|---|
| 02dd7b64b5 | |||
| 7c30124743 | |||
| 6a8323f2cf | |||
| 763bc47a89 | |||
| 517e62dca2 | |||
| 0f47f581b3 | |||
| 18b18d5103 | |||
| f4eed7a6ef | |||
| 6878f7846a | |||
| b2ae0e488f | |||
| 02a371b81e | |||
| d960570171 | |||
| c5713f5839 | |||
| 8550506517 | |||
| 2b1dc72410 | |||
| 22fee1d064 | |||
| 50a5c399c4 | |||
| 4b3ebb08c2 | |||
| 4c5643e342 | |||
| e8d217e191 | |||
| 4604970f9d | |||
| 6cd7a92576 | |||
| 9540f27c95 | |||
| 244e6aa95e | |||
| 3e3a4d0cd4 | |||
| 94fbdb5a92 | |||
| abd1b94235 | |||
| d2dcc2f95b | |||
| f324da0369 | |||
| a3cfb7ade1 | |||
| 44b9986166 | |||
| 4fe745e858 | |||
| a0b95479e2 | |||
| 07630c3b36 | |||
| a7c4ad5e7c | |||
| 32c2653b0f | |||
| a869d677a3 | 
							
								
								
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										19
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -1,5 +1,24 @@
 | 
				
			|||||||
# looqs: Release notes
 | 
					# looqs: Release notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 2023-05-07 - v0.9
 | 
				
			||||||
 | 
					Highlights: Tag support. Also begin new index mode to only index metadata (currently only path + file size, more to come).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Note: Upgrading can take some time as new column indexes will be added
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CHANGES:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 - gui: Improve font rendering in previews
 | 
				
			||||||
 | 
					 - gui: Allow indexing only metadata
 | 
				
			||||||
 | 
					 - gui: Allow adding content for files which only had metadata indexed before
 | 
				
			||||||
 | 
					 - gui: Allow assigning tags by right clicking on paths
 | 
				
			||||||
 | 
					 - cli: "add" command: Implement --verbose (-v)
 | 
				
			||||||
 | 
					 - cli: "add" command: Implement --no-content and --fill-content
 | 
				
			||||||
 | 
					 - cli: Add "tag" command which allows managing tags for paths.
 | 
				
			||||||
 | 
					 - search: Add "tag:()", "t:()" filters
 | 
				
			||||||
 | 
					 - Minor improvements and refactorings under the hood
 | 
				
			||||||
 | 
					 - Add packages: Ubuntu 23.04.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2022-11-19 - v0.8.1
 | 
					## 2022-11-19 - v0.8.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CHANGES:
 | 
					CHANGES:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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.
 | 
					The screenshots in this section may occasionally be slightly outdated, but they are usually recent enough to get an overall impression of the current state of the GUI.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Current status
 | 
					## Current status
 | 
				
			||||||
Latest version: 2022-11-19, v0.8.1
 | 
					Latest version: 2023-05-07, v0.9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Please keep in mind: looqs is still at an early stage and may exhibit some weirdness and contain bugs.
 | 
					Please keep in mind: looqs is still at an early stage and may exhibit some weirdness and contain bugs.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -76,7 +76,7 @@ To build on Ubuntu and Debian, clone the repo and then run:
 | 
				
			|||||||
```
 | 
					```
 | 
				
			||||||
git submodule init
 | 
					git submodule init
 | 
				
			||||||
git submodule update
 | 
					git submodule update
 | 
				
			||||||
sudo apt install build-essential qtbase5-dev libpoppler-qt5-dev libuchardet-dev libquazip5-dev
 | 
					sudo apt install build-essential qtbase5-dev libqt5sql5-sqlite libpoppler-qt5-dev libuchardet-dev libquazip5-dev
 | 
				
			||||||
qmake
 | 
					qmake
 | 
				
			||||||
make
 | 
					make
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
@@ -97,7 +97,9 @@ The GUI is located in `gui/looqs-gui`, the binary for the CLI is in `cli/looqs`
 | 
				
			|||||||
## Packages
 | 
					## Packages
 | 
				
			||||||
At this point, looqs is not in any official distro package repo, but I maintain some packages.
 | 
					At this point, looqs is not in any official distro package repo, but I maintain some packages.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Ubuntu 22.04, 22.10
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Ubuntu 23.04, 22.10, 22.04
 | 
				
			||||||
Latest release can be installed using apt from the repo.
 | 
					Latest release can be installed using apt from the repo.
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
# First, obtain key, assume it's trusted.
 | 
					# First, obtain key, assume it's trusted.
 | 
				
			||||||
@@ -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 update
 | 
				
			||||||
sudo apt-get install looqs
 | 
					sudo apt-get install looqs
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					### Gentoo (EXPERIMENTAL)
 | 
				
			||||||
 | 
					Available in this overlay: https://github.com/quitesimpleorg/quitesimple-overlay
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL)
 | 
					### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL)
 | 
				
			||||||
looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is
 | 
					looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is
 | 
				
			||||||
@@ -134,7 +138,7 @@ An AppImage may accompany the tarball in the future.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Other distros
 | 
					### 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
 | 
					### 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 |
 | 
					| path.begins:(term) | pb:(term) |  Filters path beginning with the specified term |
 | 
				
			||||||
| contains:(terms) | c:(terms) | Full-text search, also understands quotes |
 | 
					| contains:(terms) | c:(terms) | Full-text search, also understands quotes |
 | 
				
			||||||
| limit:(integer) | - | Limits the number of results. The default is 1000. Say "limit:0" to see all results |
 | 
					| limit:(integer) | - | Limits the number of results. The default is 1000. Say "limit:0" to see all results |
 | 
				
			||||||
 | 
					| tag:(tagname) | t:(tagname) | Filter for files that have been tagged with the corresponding tag |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Filters can be combined. The booleans AND and OR are supported. Negations can be applied too, except for c:(). Negations are specified with "!".
 | 
					Filters can be combined. The booleans AND and OR are supported. Negations can be applied too, except for c:(). Negations are specified with "!".
 | 
				
			||||||
The AND boolean is implicit and thus entering it strictly optional.
 | 
					The AND boolean is implicit and thus entering it strictly optional.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -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'|
 | 
					|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.|
 | 
					|memcpy !(pe:(.c) OR pe:(.cpp))| Performs a FTS search for 'memcpy' but excludes .cpp and .c files.|
 | 
				
			||||||
|c:("I think, therefore")|Performs a FTS search for the phrase "I think, therefore".|
 | 
					|c:("I think, therefore")|Performs a FTS search for the phrase "I think, therefore".|
 | 
				
			||||||
|c:("invoice") Downloads|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.
 | 
					# You can also select to disable deprecated APIs only up to a certain version of Qt.
 | 
				
			||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 | 
					#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000    # disables all the APIs deprecated before Qt 6.0.0
 | 
				
			||||||
SOURCES += \
 | 
					SOURCES += \
 | 
				
			||||||
 | 
					    commandtag.cpp \
 | 
				
			||||||
        main.cpp \
 | 
					        main.cpp \
 | 
				
			||||||
    commandadd.cpp \
 | 
					    commandadd.cpp \
 | 
				
			||||||
    commanddelete.cpp \
 | 
					    commanddelete.cpp \
 | 
				
			||||||
@@ -27,6 +28,7 @@ HEADERS += \
 | 
				
			|||||||
    command.h \
 | 
					    command.h \
 | 
				
			||||||
    commandadd.h \
 | 
					    commandadd.h \
 | 
				
			||||||
    commanddelete.h \
 | 
					    commanddelete.h \
 | 
				
			||||||
 | 
					    commandtag.h \
 | 
				
			||||||
    commandupdate.h \
 | 
					    commandupdate.h \
 | 
				
			||||||
    commandsearch.h \
 | 
					    commandsearch.h \
 | 
				
			||||||
    commandlist.h
 | 
					    commandlist.h
 | 
				
			||||||
@@ -44,6 +46,8 @@ packagesExist(quazip1-qt5) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
INCLUDEPATH += $$PWD/../shared
 | 
					INCLUDEPATH += $$PWD/../shared
 | 
				
			||||||
 | 
					INCLUDEPATH += /usr/include/poppler/qt5/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
DEPENDPATH += $$PWD/../shared
 | 
					DEPENDPATH += $$PWD/../shared
 | 
				
			||||||
 | 
					
 | 
				
			||||||
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a
 | 
					win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,6 @@
 | 
				
			|||||||
#include <QThread>
 | 
					#include <QThread>
 | 
				
			||||||
#include <QDebug>
 | 
					#include <QDebug>
 | 
				
			||||||
#include "command.h"
 | 
					#include "command.h"
 | 
				
			||||||
#include "looqsgeneralexception.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void Command::execute()
 | 
					void Command::execute()
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -41,12 +41,13 @@ int CommandAdd::handle(QStringList arguments)
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	QCommandLineParser parser;
 | 
						QCommandLineParser parser;
 | 
				
			||||||
	parser.addOptions({{{"c", "continue"},
 | 
						parser.addOptions({{{"c", "continue"},
 | 
				
			||||||
						"Continue adding files, don't exit on first error. If this option is not given, looqs will "
 | 
											"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."
 | 
											"exit asap, but it's possible that a few files will still be processed."
 | 
				
			||||||
						"Set -t 1 to avoid this behavior, but processing will be slower. "},
 | 
											"Set -t 1 to avoid this behavior, but processing will be slower. "},
 | 
				
			||||||
					   {{"n", "no-content"}, "Only add paths to database. Do not index content"},
 | 
										   {{"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"},
 | 
										   {{"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"}});
 | 
										   {{"t", "threads"}, "Number of threads to use.", "threads"}});
 | 
				
			||||||
	parser.addHelpOption();
 | 
						parser.addHelpOption();
 | 
				
			||||||
	parser.addPositionalArgument("add", "Add paths to the index",
 | 
						parser.addPositionalArgument("add", "Add paths to the index",
 | 
				
			||||||
@@ -56,6 +57,8 @@ int CommandAdd::handle(QStringList arguments)
 | 
				
			|||||||
	this->keepGoing = parser.isSet("continue");
 | 
						this->keepGoing = parser.isSet("continue");
 | 
				
			||||||
	bool pathsOnly = parser.isSet("no-content");
 | 
						bool pathsOnly = parser.isSet("no-content");
 | 
				
			||||||
	bool fillContent = parser.isSet("fill-content");
 | 
						bool fillContent = parser.isSet("fill-content");
 | 
				
			||||||
 | 
						bool verbose = parser.isSet("verbose");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(parser.isSet("threads"))
 | 
						if(parser.isSet("threads"))
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		QString threadsCount = parser.value("threads");
 | 
							QString threadsCount = parser.value("threads");
 | 
				
			||||||
@@ -85,18 +88,43 @@ int CommandAdd::handle(QStringList arguments)
 | 
				
			|||||||
	fileSaverOptions.keepGoing = keepGoing;
 | 
						fileSaverOptions.keepGoing = keepGoing;
 | 
				
			||||||
	fileSaverOptions.fillExistingContentless = fillContent;
 | 
						fileSaverOptions.fillExistingContentless = fillContent;
 | 
				
			||||||
	fileSaverOptions.metadataOnly = pathsOnly;
 | 
						fileSaverOptions.metadataOnly = pathsOnly;
 | 
				
			||||||
	fileSaverOptions.verbose = false;
 | 
						fileSaverOptions.verbose = verbose;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	indexer = new Indexer(*this->dbService);
 | 
						indexer = new Indexer(*this->dbService);
 | 
				
			||||||
	indexer->setFileSaverOptions(fileSaverOptions);
 | 
						indexer->setFileSaverOptions(fileSaverOptions);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	indexer->setTargetPaths(files.toVector());
 | 
						indexer->setTargetPaths(files.toVector());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(verbose)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							indexer->setProgressReportThreshold(1);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	connect(indexer, &Indexer::pathsCountChanged, this,
 | 
						connect(indexer, &Indexer::pathsCountChanged, this,
 | 
				
			||||||
			[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; });
 | 
								[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; });
 | 
				
			||||||
	connect(indexer, &Indexer::indexProgress, this,
 | 
						connect(indexer, &Indexer::indexProgress, this,
 | 
				
			||||||
			[](int pathsCount, unsigned int /*added*/, unsigned int /*skipped*/, unsigned int /*failed*/,
 | 
								[verbose, this](int pathsCount, unsigned int /*added*/, unsigned int /*skipped*/, unsigned int /*failed*/,
 | 
				
			||||||
			   unsigned int /*totalCount*/) { Logger::info() << "Processed files: " << pathsCount << Qt::endl; });
 | 
												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);
 | 
						connect(indexer, &Indexer::finished, this, &CommandAdd::indexerFinished);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	this->autoFinish = false;
 | 
						this->autoFinish = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,6 +13,8 @@ class CommandAdd : public Command
 | 
				
			|||||||
	bool keepGoing = true;
 | 
						bool keepGoing = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  protected:
 | 
					  protected:
 | 
				
			||||||
 | 
						IndexResult currentResult;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
	using Command::Command;
 | 
						using Command::Command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,5 @@
 | 
				
			|||||||
#include <QCommandLineParser>
 | 
					#include <QCommandLineParser>
 | 
				
			||||||
#include "commandsearch.h"
 | 
					#include "commandsearch.h"
 | 
				
			||||||
#include "databasefactory.h"
 | 
					 | 
				
			||||||
#include "logger.h"
 | 
					#include "logger.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
int CommandSearch::handle(QStringList arguments)
 | 
					int CommandSearch::handle(QStringList arguments)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										153
									
								
								cli/commandtag.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										153
									
								
								cli/commandtag.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -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
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										16
									
								
								cli/commandtag.h
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -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
 | 
				
			||||||
@@ -21,6 +21,7 @@
 | 
				
			|||||||
#include "commandupdate.h"
 | 
					#include "commandupdate.h"
 | 
				
			||||||
#include "commandsearch.h"
 | 
					#include "commandsearch.h"
 | 
				
			||||||
#include "commandlist.h"
 | 
					#include "commandlist.h"
 | 
				
			||||||
 | 
					#include "commandtag.h"
 | 
				
			||||||
#include "databasefactory.h"
 | 
					#include "databasefactory.h"
 | 
				
			||||||
#include "logger.h"
 | 
					#include "logger.h"
 | 
				
			||||||
#include "sandboxedprocessor.h"
 | 
					#include "sandboxedprocessor.h"
 | 
				
			||||||
@@ -31,7 +32,7 @@
 | 
				
			|||||||
void printUsage(QString argv0)
 | 
					void printUsage(QString argv0)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	qInfo() << "Usage:" << argv0 << "command";
 | 
						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)
 | 
					Command *commandFromName(QString name, SqliteDbService &dbService)
 | 
				
			||||||
@@ -56,6 +57,10 @@ Command *commandFromName(QString name, SqliteDbService &dbService)
 | 
				
			|||||||
	{
 | 
						{
 | 
				
			||||||
		return new CommandList(dbService);
 | 
							return new CommandList(dbService);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if(name == "tag")
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return new CommandTag(dbService);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nullptr;
 | 
						return nullptr;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -193,7 +193,7 @@ int main(int argc, char *argv[])
 | 
				
			|||||||
															   Logger::error() << error << Qt::endl;
 | 
																				   Logger::error() << error << Qt::endl;
 | 
				
			||||||
															   QMessageBox::critical(nullptr, "Error during upgrade",
 | 
																				   QMessageBox::critical(nullptr, "Error during upgrade",
 | 
				
			||||||
																					 error);
 | 
																										 error);
 | 
				
			||||||
															   qApp->quit();
 | 
																				   exit(EXIT_FAILURE);
 | 
				
			||||||
														   }
 | 
																			   }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
								 );
 | 
													 );
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,6 +16,9 @@
 | 
				
			|||||||
#include <QScreen>
 | 
					#include <QScreen>
 | 
				
			||||||
#include <QProgressDialog>
 | 
					#include <QProgressDialog>
 | 
				
			||||||
#include <QDesktopWidget>
 | 
					#include <QDesktopWidget>
 | 
				
			||||||
 | 
					#include <QWidgetAction>
 | 
				
			||||||
 | 
					#include <QInputDialog>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "mainwindow.h"
 | 
					#include "mainwindow.h"
 | 
				
			||||||
#include "ui_mainwindow.h"
 | 
					#include "ui_mainwindow.h"
 | 
				
			||||||
#include "clicklabel.h"
 | 
					#include "clicklabel.h"
 | 
				
			||||||
@@ -42,6 +45,9 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	indexer = new Indexer(*(this->dbService));
 | 
						indexer = new Indexer(*(this->dbService));
 | 
				
			||||||
	indexer->setParent(this);
 | 
						indexer->setParent(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tagManager = new TagManager(*(this->dbService));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	connectSignals();
 | 
						connectSignals();
 | 
				
			||||||
	ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
 | 
						ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
 | 
				
			||||||
	ui->tabWidget->setCurrentIndex(0);
 | 
						ui->tabWidget->setCurrentIndex(0);
 | 
				
			||||||
@@ -287,6 +293,7 @@ void MainWindow::startIndexing()
 | 
				
			|||||||
	if(this->indexer->isRunning())
 | 
						if(this->indexer->isRunning())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		ui->btnStartIndexing->setEnabled(false);
 | 
							ui->btnStartIndexing->setEnabled(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ui->btnStartIndexing->setText("Start indexing");
 | 
							ui->btnStartIndexing->setText("Start indexing");
 | 
				
			||||||
		this->indexer->requestCancellation();
 | 
							this->indexer->requestCancellation();
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
@@ -296,6 +303,8 @@ void MainWindow::startIndexing()
 | 
				
			|||||||
	ui->resultsTab->setEnabled(false);
 | 
						ui->resultsTab->setEnabled(false);
 | 
				
			||||||
	ui->settingsTab->setEnabled(false);
 | 
						ui->settingsTab->setEnabled(false);
 | 
				
			||||||
	ui->txtPathScanAdd->setEnabled(false);
 | 
						ui->txtPathScanAdd->setEnabled(false);
 | 
				
			||||||
 | 
						ui->btnAddPath->setEnabled(false);
 | 
				
			||||||
 | 
						ui->btnChoosePath->setEnabled(false);
 | 
				
			||||||
	ui->txtSearch->setEnabled(false);
 | 
						ui->txtSearch->setEnabled(false);
 | 
				
			||||||
	ui->previewProcessBar->setValue(0);
 | 
						ui->previewProcessBar->setValue(0);
 | 
				
			||||||
	ui->previewProcessBar->setVisible(true);
 | 
						ui->previewProcessBar->setVisible(true);
 | 
				
			||||||
@@ -343,6 +352,8 @@ void MainWindow::finishIndexing()
 | 
				
			|||||||
	ui->resultsTab->setEnabled(true);
 | 
						ui->resultsTab->setEnabled(true);
 | 
				
			||||||
	ui->settingsTab->setEnabled(true);
 | 
						ui->settingsTab->setEnabled(true);
 | 
				
			||||||
	ui->txtPathScanAdd->setEnabled(true);
 | 
						ui->txtPathScanAdd->setEnabled(true);
 | 
				
			||||||
 | 
						ui->btnAddPath->setEnabled(true);
 | 
				
			||||||
 | 
						ui->btnChoosePath->setEnabled(true);
 | 
				
			||||||
	ui->txtSearch->setEnabled(true);
 | 
						ui->txtSearch->setEnabled(true);
 | 
				
			||||||
	if(result.erroredPaths > 0)
 | 
						if(result.erroredPaths > 0)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@@ -877,6 +888,8 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results)
 | 
				
			|||||||
		makePreviews(1);
 | 
							makePreviews(1);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ui->tabWidget->setTabEnabled(1, previewDirty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QString statusText = "Results: " + QString::number(results.size()) + " files";
 | 
						QString statusText = "Results: " + QString::number(results.size()) + " files";
 | 
				
			||||||
	statusText += ", previewable: " + QString::number(this->previewCoordinator.previewableCount());
 | 
						statusText += ", previewable: " + QString::number(this->previewCoordinator.previewableCount());
 | 
				
			||||||
	if(hasDeleted)
 | 
						if(hasDeleted)
 | 
				
			||||||
@@ -1006,6 +1019,65 @@ void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
 | 
				
			|||||||
						   this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
 | 
											   this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
 | 
				
			||||||
					   });
 | 
										   });
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QMenu *tagMenu = menu.addMenu("Tag file with: ");
 | 
				
			||||||
 | 
						QVector<QString> allTags = this->dbService->getTags();
 | 
				
			||||||
 | 
						QHash<QString, bool> fileTags;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QString path = fileInfo.absoluteFilePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for(const QString &fileTag : this->dbService->getTagsForPath(path))
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							fileTags[fileTag] = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for(const QString &tag : allTags)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							QCheckBox *checkBox = new QCheckBox(tagMenu);
 | 
				
			||||||
 | 
							QWidgetAction *checkableAction = new QWidgetAction(tagMenu);
 | 
				
			||||||
 | 
							checkableAction->setDefaultWidget(checkBox);
 | 
				
			||||||
 | 
							checkBox->setText(tag);
 | 
				
			||||||
 | 
							checkBox->setChecked(fileTags.contains(tag));
 | 
				
			||||||
 | 
							tagMenu->addAction(checkableAction);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							connect(checkBox, &QCheckBox::stateChanged, this,
 | 
				
			||||||
 | 
									[this, checkBox, path]
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										QVector<QString> currentTags = this->dbService->getTagsForPath(path);
 | 
				
			||||||
 | 
										QString checkBoxText = checkBox->text();
 | 
				
			||||||
 | 
										if(checkBox->isChecked())
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											if(!this->tagManager->addTagsToPath(path, {checkBoxText}))
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												QMessageBox::critical(this, "Error while adding tag",
 | 
				
			||||||
 | 
																	  "An error occured while trying to add the tag");
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										else
 | 
				
			||||||
 | 
										{
 | 
				
			||||||
 | 
											if(!this->tagManager->removeTagsForPath(path, {checkBoxText}))
 | 
				
			||||||
 | 
											{
 | 
				
			||||||
 | 
												QMessageBox::critical(this, "Error while removing tag",
 | 
				
			||||||
 | 
																	  "An error occured while trying to remove the tag");
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tagMenu->addAction("Add new tags", this,
 | 
				
			||||||
 | 
										   [this, path]
 | 
				
			||||||
 | 
										   {
 | 
				
			||||||
 | 
											   bool ok;
 | 
				
			||||||
 | 
											   QString text =
 | 
				
			||||||
 | 
												   QInputDialog::getText(this, tr("Enter new tags"), tr("New tags (comma separated):"),
 | 
				
			||||||
 | 
																		 QLineEdit::Normal, "", &ok);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											   if(ok && !this->tagManager->addTagsToPath(path, text, ','))
 | 
				
			||||||
 | 
											   {
 | 
				
			||||||
 | 
												   QMessageBox::critical(this, "Error while trying to add tags",
 | 
				
			||||||
 | 
																		 "An error occured while trying to add tags");
 | 
				
			||||||
 | 
											   }
 | 
				
			||||||
 | 
										   });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::openDocument(QString path, int num)
 | 
					void MainWindow::openDocument(QString path, int num)
 | 
				
			||||||
@@ -1060,6 +1132,7 @@ MainWindow::~MainWindow()
 | 
				
			|||||||
	delete this->dbService;
 | 
						delete this->dbService;
 | 
				
			||||||
	delete this->dbFactory;
 | 
						delete this->dbFactory;
 | 
				
			||||||
	delete this->indexer;
 | 
						delete this->indexer;
 | 
				
			||||||
 | 
						delete this->tagManager;
 | 
				
			||||||
	delete ui;
 | 
						delete ui;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@
 | 
				
			|||||||
#include "../shared/indexsyncer.h"
 | 
					#include "../shared/indexsyncer.h"
 | 
				
			||||||
#include "previewcoordinator.h"
 | 
					#include "previewcoordinator.h"
 | 
				
			||||||
#include "indexer.h"
 | 
					#include "indexer.h"
 | 
				
			||||||
 | 
					#include "tagmanager.h"
 | 
				
			||||||
namespace Ui
 | 
					namespace Ui
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
class MainWindow;
 | 
					class MainWindow;
 | 
				
			||||||
@@ -39,6 +40,9 @@ class MainWindow : public QMainWindow
 | 
				
			|||||||
	QFutureWatcher<QVector<SearchResult>> searchWatcher;
 | 
						QFutureWatcher<QVector<SearchResult>> searchWatcher;
 | 
				
			||||||
	LooqsQuery contentSearchQuery;
 | 
						LooqsQuery contentSearchQuery;
 | 
				
			||||||
	QVector<QString> searchHistory;
 | 
						QVector<QString> searchHistory;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						TagManager *tagManager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int currentSearchHistoryIndex = 0;
 | 
						int currentSearchHistoryIndex = 0;
 | 
				
			||||||
	QString currentSavedSearchText;
 | 
						QString currentSavedSearchText;
 | 
				
			||||||
	bool previewDirty = false;
 | 
						bool previewDirty = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,7 @@ QSharedPointer<PreviewResult> PreviewGeneratorOdt::generate(RenderConfig config,
 | 
				
			|||||||
		throw LooqsGeneralException("Error while reading content.xml of " + documentPath);
 | 
							throw LooqsGeneralException("Error while reading content.xml of " + documentPath);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	TagStripperProcessor tsp;
 | 
						TagStripperProcessor tsp;
 | 
				
			||||||
	QString content = tsp.process(entireContent).constFirst().content;
 | 
						QString content = tsp.process(entireContent).pages.constFirst().content;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	PreviewGeneratorPlainText plainTextGenerator;
 | 
						PreviewGeneratorPlainText plainTextGenerator;
 | 
				
			||||||
	result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName()));
 | 
						result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName()));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -246,6 +246,8 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
 | 
				
			|||||||
			totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
 | 
								totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if(!resultText.isEmpty())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
		if(isTruncated)
 | 
							if(isTruncated)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			header += "(truncated) ";
 | 
								header += "(truncated) ";
 | 
				
			||||||
@@ -256,7 +258,9 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		header += "<hr>";
 | 
							header += "<hr>";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return header + resultText;
 | 
							resultText = header + resultText;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return resultText;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath,
 | 
					QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -24,7 +24,9 @@ QString DefaultTextProcessor::processText(const QByteArray &data) const
 | 
				
			|||||||
	return {};
 | 
						return {};
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QVector<PageData> DefaultTextProcessor::process(const QByteArray &data) const
 | 
					DocumentProcessResult DefaultTextProcessor::process(const QByteArray &data) const
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return {{0, processText(data)}};
 | 
						DocumentProcessResult result;
 | 
				
			||||||
 | 
						result.pages.append({0, processText(data)});
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ class DefaultTextProcessor : public Processor
 | 
				
			|||||||
  public:
 | 
					  public:
 | 
				
			||||||
	DefaultTextProcessor();
 | 
						DefaultTextProcessor();
 | 
				
			||||||
	QString processText(const QByteArray &data) const;
 | 
						QString processText(const QByteArray &data) const;
 | 
				
			||||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
						DocumentProcessResult process(const QByteArray &data) const override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif // DEFAULTTEXTPROCESSOR_H
 | 
					#endif // DEFAULTTEXTPROCESSOR_H
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										31
									
								
								shared/documentoutlineentry.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										31
									
								
								shared/documentoutlineentry.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					#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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										29
									
								
								shared/documentoutlineentry.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										29
									
								
								shared/documentoutlineentry.h
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,29 @@
 | 
				
			|||||||
 | 
					#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
 | 
				
			||||||
							
								
								
									
										39
									
								
								shared/documentprocessresult.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										39
									
								
								shared/documentprocessresult.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					#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;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								shared/documentprocessresult.h
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										17
									
								
								shared/documentprocessresult.h
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					#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,10 +25,22 @@ SaveFileResult FileSaver::addFile(QString path)
 | 
				
			|||||||
	QString absPath = info.absoluteFilePath();
 | 
						QString absPath = info.absoluteFilePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	auto mtime = info.lastModified().toSecsSinceEpoch();
 | 
						auto mtime = info.lastModified().toSecsSinceEpoch();
 | 
				
			||||||
	if(this->dbService->fileExistsInDatabase(absPath, mtime))
 | 
					
 | 
				
			||||||
 | 
						bool exists = false;
 | 
				
			||||||
 | 
						if(this->fileSaverOptions.fillExistingContentless)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							exists = this->dbService->fileExistsInDatabase(absPath, mtime, 'c');
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						else
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							exists = this->dbService->fileExistsInDatabase(absPath, mtime);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(exists)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		return SKIPPED;
 | 
							return SKIPPED;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return saveFile(info);
 | 
						return saveFile(info);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,7 +110,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
					SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	QVector<PageData> pageData;
 | 
						DocumentProcessResult processResult;
 | 
				
			||||||
	QString canonicalPath = fileInfo.canonicalFilePath();
 | 
						QString canonicalPath = fileInfo.canonicalFilePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	int processorReturnCode = -1;
 | 
						int processorReturnCode = -1;
 | 
				
			||||||
@@ -134,10 +146,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
				
			|||||||
			if(mustFillContent)
 | 
								if(mustFillContent)
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				auto filetype = this->dbService->queryFileType(fileInfo.absolutePath());
 | 
									auto filetype = this->dbService->queryFileType(fileInfo.absolutePath());
 | 
				
			||||||
				if(filetype)
 | 
									mustFillContent = !filetype.has_value() || filetype.value() == 'c';
 | 
				
			||||||
				{
 | 
					 | 
				
			||||||
					mustFillContent = filetype.value() == 'c';
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -160,11 +169,10 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
				
			|||||||
			 * finishes.
 | 
								 * finishes.
 | 
				
			||||||
			 */
 | 
								 */
 | 
				
			||||||
			QDataStream in(process.readAllStandardOutput());
 | 
								QDataStream in(process.readAllStandardOutput());
 | 
				
			||||||
			while(!in.atEnd())
 | 
					
 | 
				
			||||||
 | 
								if(!in.atEnd())
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
				PageData pd;
 | 
									in >> processResult;
 | 
				
			||||||
				in >> pd;
 | 
					 | 
				
			||||||
				pageData.append(pd);
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			processorReturnCode = process.exitCode();
 | 
								processorReturnCode = process.exitCode();
 | 
				
			||||||
			if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY)
 | 
								if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY)
 | 
				
			||||||
@@ -176,7 +184,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	SaveFileResult result = this->dbService->saveFile(fileInfo, pageData, this->fileSaverOptions.metadataOnly);
 | 
						SaveFileResult result = this->dbService->saveFile(fileInfo, processResult, this->fileSaverOptions.metadataOnly);
 | 
				
			||||||
	if(result == OK && processorReturnCode == OK_WASEMPTY)
 | 
						if(result == OK && processorReturnCode == OK_WASEMPTY)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		return OK_WASEMPTY;
 | 
							return OK_WASEMPTY;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,11 +21,18 @@ void FileScanWorker::run()
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			sfr = saver.addFile(path);
 | 
								sfr = saver.addFile(path);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							catch(LooqsGeneralException &e)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Logger::error() << e.message << Qt::endl;
 | 
				
			||||||
 | 
								sfr = PROCESSFAIL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		catch(std::exception &e)
 | 
							catch(std::exception &e)
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			Logger::error() << e.what();
 | 
								Logger::error() << e.what() << Qt::endl;
 | 
				
			||||||
			sfr = PROCESSFAIL; // well...
 | 
								sfr = PROCESSFAIL; // well...
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		emit result({path, sfr});
 | 
							emit result({path, sfr});
 | 
				
			||||||
		if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck
 | 
							if(stopToken->load(std::memory_order_relaxed)) // TODO: relaxed should suffice here, but recheck
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -111,6 +111,20 @@ void Indexer::dirScanProgress(int current, int total)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
void Indexer::processFileScanResult(FileScanResult result)
 | 
					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))
 | 
						if(isErrorSaveFileResult(result.second))
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		this->currentIndexResult.results.append(result);
 | 
							this->currentIndexResult.results.append(result);
 | 
				
			||||||
@@ -129,20 +143,6 @@ 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();
 | 
						QTime currentTime = QTime::currentTime();
 | 
				
			||||||
	if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10)
 | 
						if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@@ -171,3 +171,8 @@ void Indexer::setFileSaverOptions(FileSaverOptions options)
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	this->fileSaverOptions = options;
 | 
						this->fileSaverOptions = options;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void Indexer::setProgressReportThreshold(int threshold)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						this->progressReportThreshold = threshold;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -82,6 +82,8 @@ class Indexer : public QObject
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	void setFileSaverOptions(FileSaverOptions options);
 | 
						void setFileSaverOptions(FileSaverOptions options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void setProgressReportThreshold(int threshold);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void requestCancellation();
 | 
						void requestCancellation();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Indexer(SqliteDbService &db);
 | 
						Indexer(SqliteDbService &db);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -29,6 +29,11 @@ bool LooqsQuery::hasContentSearch() const
 | 
				
			|||||||
	return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
 | 
						return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool LooqsQuery::hasOutlineSearch() const
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						return (this->getTokensMask() & FILTER_OUTLINE_CONTAINS) == FILTER_OUTLINE_CONTAINS;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool LooqsQuery::hasPathSearch() const
 | 
					bool LooqsQuery::hasPathSearch() const
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH;
 | 
						return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH;
 | 
				
			||||||
@@ -285,6 +290,14 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
 | 
				
			|||||||
			{
 | 
								{
 | 
				
			||||||
				tokenType = FILTER_CONTENT_PAGE;
 | 
									tokenType = FILTER_CONTENT_PAGE;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								else if(filtername == "t" || filtername == "tag")
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									tokenType = FILTER_TAG_ASSIGNED;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								else if(filtername == "toc" || filtername == "outline")
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									tokenType = FILTER_OUTLINE_CONTAINS;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
			// TODO: given this is not really a "filter", this feels slightly misplaced here
 | 
								// TODO: given this is not really a "filter", this feels slightly misplaced here
 | 
				
			||||||
			else if(filtername == "sort")
 | 
								else if(filtername == "sort")
 | 
				
			||||||
			{
 | 
								{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -68,6 +68,7 @@ class LooqsQuery
 | 
				
			|||||||
		this->limit = limit;
 | 
							this->limit = limit;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	bool hasContentSearch() const;
 | 
						bool hasContentSearch() const;
 | 
				
			||||||
 | 
						bool hasOutlineSearch() const;
 | 
				
			||||||
	bool hasPathSearch() const;
 | 
						bool hasPathSearch() const;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void addSortCondition(SortCondition sc);
 | 
						void addSortCondition(SortCondition sc);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										6
									
								
								shared/migrations/5.sql
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										6
									
								
								shared/migrations/5.sql
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -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';
 | 
				
			||||||
							
								
								
									
										2
									
								
								shared/migrations/6.sql
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										2
									
								
								shared/migrations/6.sql
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					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,5 +4,7 @@
 | 
				
			|||||||
        <file>2.sql</file>
 | 
					        <file>2.sql</file>
 | 
				
			||||||
        <file>3.sql</file>
 | 
					        <file>3.sql</file>
 | 
				
			||||||
        <file>4.sql</file>
 | 
					        <file>4.sql</file>
 | 
				
			||||||
 | 
					        <file>5.sql</file>
 | 
				
			||||||
 | 
					        <file>6.sql</file>
 | 
				
			||||||
    </qresource>
 | 
					    </qresource>
 | 
				
			||||||
</RCC>
 | 
					</RCC>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ class NothingProcessor : public Processor
 | 
				
			|||||||
	NothingProcessor();
 | 
						NothingProcessor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
	QVector<PageData> process(const QByteArray & /*data*/) const override
 | 
						DocumentProcessResult process(const QByteArray & /*data*/) const override
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,12 +3,12 @@
 | 
				
			|||||||
#include "odtprocessor.h"
 | 
					#include "odtprocessor.h"
 | 
				
			||||||
#include "tagstripperprocessor.h"
 | 
					#include "tagstripperprocessor.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QVector<PageData> OdtProcessor::process(const QByteArray & /*data*/) const
 | 
					DocumentProcessResult OdtProcessor::process(const QByteArray & /*data*/) const
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	throw LooqsGeneralException("Not implemented yet");
 | 
						throw LooqsGeneralException("Not implemented yet");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QVector<PageData> OdtProcessor::process(QString path) const
 | 
					DocumentProcessResult OdtProcessor::process(QString path) const
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	QuaZipFile zipFile(path);
 | 
						QuaZipFile zipFile(path);
 | 
				
			||||||
	zipFile.setFileName("content.xml");
 | 
						zipFile.setFileName("content.xml");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,9 +8,9 @@ class OdtProcessor : public Processor
 | 
				
			|||||||
	{
 | 
						{
 | 
				
			||||||
		this->PREFERED_DATA_SOURCE = FILEPATH;
 | 
							this->PREFERED_DATA_SOURCE = FILEPATH;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
						DocumentProcessResult process(const QByteArray &data) const override;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QVector<PageData> process(QString path) const override;
 | 
						DocumentProcessResult process(QString path) const override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif // ODTPROCESSOR_H
 | 
					#endif // ODTPROCESSOR_H
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,9 +5,30 @@ PdfProcessor::PdfProcessor()
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QVector<PageData> PdfProcessor::process(const QByteArray &data) const
 | 
					QVector<DocumentOutlineEntry> PdfProcessor::createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	QVector<PageData> result;
 | 
						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;
 | 
				
			||||||
	QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data));
 | 
						QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data));
 | 
				
			||||||
	if(doc.isNull())
 | 
						if(doc.isNull())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
@@ -26,12 +47,13 @@ QVector<PageData> PdfProcessor::process(const QByteArray &data) const
 | 
				
			|||||||
	for(auto i = 0; i < pagecount; i++)
 | 
						for(auto i = 0; i < pagecount; i++)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		QString text = doc->page(i)->text(entirePage);
 | 
							QString text = doc->page(i)->text(entirePage);
 | 
				
			||||||
		result.append({static_cast<unsigned int>(i + 1), text});
 | 
							result.pages.append({static_cast<unsigned int>(i + 1), text});
 | 
				
			||||||
		/*TODO: hack, so we can fts search several words over the whole document, not just pages.
 | 
							/*TODO: hack, so we can fts search several words over the whole document, not just pages.
 | 
				
			||||||
		 * this of course uses more space and should be solved differently.
 | 
							 * this of course uses more space and should be solved differently.
 | 
				
			||||||
		 */
 | 
							 */
 | 
				
			||||||
		entire += text;
 | 
							entire += text;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	result.append({0, entire});
 | 
						result.pages.append({0, entire});
 | 
				
			||||||
 | 
						result.outlines = createOutline(doc->outline());
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
#ifndef PDFPROCESSOR_H
 | 
					#ifndef PDFPROCESSOR_H
 | 
				
			||||||
#define PDFPROCESSOR_H
 | 
					#define PDFPROCESSOR_H
 | 
				
			||||||
 | 
					#include <poppler-qt5.h>
 | 
				
			||||||
#include "processor.h"
 | 
					#include "processor.h"
 | 
				
			||||||
class PdfProcessor : public Processor
 | 
					class PdfProcessor : public Processor
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -7,7 +8,8 @@ class PdfProcessor : public Processor
 | 
				
			|||||||
	PdfProcessor();
 | 
						PdfProcessor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
						QVector<DocumentOutlineEntry> createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const;
 | 
				
			||||||
 | 
						DocumentProcessResult process(const QByteArray &data) const override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif // PDFPROCESSOR_H
 | 
					#endif // PDFPROCESSOR_H
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,8 @@
 | 
				
			|||||||
#define PROCESSOR_H
 | 
					#define PROCESSOR_H
 | 
				
			||||||
#include <QVector>
 | 
					#include <QVector>
 | 
				
			||||||
#include <QFile>
 | 
					#include <QFile>
 | 
				
			||||||
#include "pagedata.h"
 | 
					 | 
				
			||||||
#include "utils.h"
 | 
					#include "utils.h"
 | 
				
			||||||
 | 
					#include "documentprocessresult.h"
 | 
				
			||||||
enum DataSource
 | 
					enum DataSource
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	FILEPATH,
 | 
						FILEPATH,
 | 
				
			||||||
@@ -18,8 +18,8 @@ class Processor
 | 
				
			|||||||
	 * a single file */
 | 
						 * a single file */
 | 
				
			||||||
	DataSource PREFERED_DATA_SOURCE = ARRAY;
 | 
						DataSource PREFERED_DATA_SOURCE = ARRAY;
 | 
				
			||||||
	Processor();
 | 
						Processor();
 | 
				
			||||||
	virtual QVector<PageData> process(const QByteArray &data) const = 0;
 | 
						virtual DocumentProcessResult process(const QByteArray &data) const = 0;
 | 
				
			||||||
	virtual QVector<PageData> process(QString path) const
 | 
						virtual DocumentProcessResult process(QString path) const
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		return process(Utils::readFile(path));
 | 
							return process(Utils::readFile(path));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,18 +65,12 @@ void SandboxedProcessor::enableSandbox(QString readablePath)
 | 
				
			|||||||
	exile_free_policy(policy);
 | 
						exile_free_policy(policy);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void SandboxedProcessor::printResults(const QVector<PageData> &pageData)
 | 
					void SandboxedProcessor::printResults(const DocumentProcessResult &result)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	QFile fsstdout;
 | 
						QFile fsstdout;
 | 
				
			||||||
	fsstdout.open(stdout, QIODevice::WriteOnly);
 | 
						fsstdout.open(stdout, QIODevice::WriteOnly);
 | 
				
			||||||
	QDataStream stream(&fsstdout);
 | 
						QDataStream stream(&fsstdout);
 | 
				
			||||||
 | 
						stream << result;
 | 
				
			||||||
	for(const PageData &data : pageData)
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		stream << data;
 | 
					 | 
				
			||||||
		// fsstdout.flush();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	fsstdout.close();
 | 
						fsstdout.close();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -102,7 +96,7 @@ SaveFileResult SandboxedProcessor::process()
 | 
				
			|||||||
		return OK;
 | 
							return OK;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QVector<PageData> pageData;
 | 
						DocumentProcessResult processResult;
 | 
				
			||||||
	QString absPath = fileInfo.absoluteFilePath();
 | 
						QString absPath = fileInfo.absoluteFilePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	try
 | 
						try
 | 
				
			||||||
@@ -111,13 +105,13 @@ SaveFileResult SandboxedProcessor::process()
 | 
				
			|||||||
		{
 | 
							{
 | 
				
			||||||
			/* Read access to FS needed... doh..*/
 | 
								/* Read access to FS needed... doh..*/
 | 
				
			||||||
			enableSandbox(absPath);
 | 
								enableSandbox(absPath);
 | 
				
			||||||
			pageData = processor->process(absPath);
 | 
								processResult = processor->process(absPath);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			QByteArray data = Utils::readFile(absPath);
 | 
								QByteArray data = Utils::readFile(absPath);
 | 
				
			||||||
			enableSandbox();
 | 
								enableSandbox();
 | 
				
			||||||
			pageData = processor->process(data);
 | 
								processResult = processor->process(data);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	catch(LooqsGeneralException &e)
 | 
						catch(LooqsGeneralException &e)
 | 
				
			||||||
@@ -126,6 +120,6 @@ SaveFileResult SandboxedProcessor::process()
 | 
				
			|||||||
		return PROCESSFAIL;
 | 
							return PROCESSFAIL;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	printResults(pageData);
 | 
						printResults(processResult);
 | 
				
			||||||
	return pageData.isEmpty() ? OK_WASEMPTY : OK;
 | 
						return processResult.pages.isEmpty() ? OK_WASEMPTY : OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,7 @@
 | 
				
			|||||||
#define SANDBOXEDPROCESSOR_H
 | 
					#define SANDBOXEDPROCESSOR_H
 | 
				
			||||||
#include <QString>
 | 
					#include <QString>
 | 
				
			||||||
#include <QMimeDatabase>
 | 
					#include <QMimeDatabase>
 | 
				
			||||||
#include "pagedata.h"
 | 
					#include "documentprocessresult.h"
 | 
				
			||||||
#include "savefileresult.h"
 | 
					#include "savefileresult.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class SandboxedProcessor
 | 
					class SandboxedProcessor
 | 
				
			||||||
@@ -12,7 +12,7 @@ class SandboxedProcessor
 | 
				
			|||||||
	QMimeDatabase mimeDatabase;
 | 
						QMimeDatabase mimeDatabase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	void enableSandbox(QString readablePath = "");
 | 
						void enableSandbox(QString readablePath = "");
 | 
				
			||||||
	void printResults(const QVector<PageData> &pageData);
 | 
						void printResults(const DocumentProcessResult &pageData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
	SandboxedProcessor(QString filepath)
 | 
						SandboxedProcessor(QString filepath)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,8 @@ SOURCES += sqlitesearch.cpp \
 | 
				
			|||||||
    dbmigrator.cpp \
 | 
					    dbmigrator.cpp \
 | 
				
			||||||
    defaulttextprocessor.cpp \
 | 
					    defaulttextprocessor.cpp \
 | 
				
			||||||
    dirscanworker.cpp \
 | 
					    dirscanworker.cpp \
 | 
				
			||||||
 | 
					    documentoutlineentry.cpp \
 | 
				
			||||||
 | 
					    documentprocessresult.cpp \
 | 
				
			||||||
    encodingdetector.cpp \
 | 
					    encodingdetector.cpp \
 | 
				
			||||||
    filesaver.cpp \
 | 
					    filesaver.cpp \
 | 
				
			||||||
    filescanworker.cpp \
 | 
					    filescanworker.cpp \
 | 
				
			||||||
@@ -60,6 +62,7 @@ SOURCES += sqlitesearch.cpp \
 | 
				
			|||||||
    processor.cpp \
 | 
					    processor.cpp \
 | 
				
			||||||
    sandboxedprocessor.cpp \
 | 
					    sandboxedprocessor.cpp \
 | 
				
			||||||
    sqlitedbservice.cpp \
 | 
					    sqlitedbservice.cpp \
 | 
				
			||||||
 | 
					    tagmanager.cpp \
 | 
				
			||||||
    tagstripperprocessor.cpp \
 | 
					    tagstripperprocessor.cpp \
 | 
				
			||||||
    utils.cpp \
 | 
					    utils.cpp \
 | 
				
			||||||
    ../submodules/exile.h/exile.c \
 | 
					    ../submodules/exile.h/exile.c \
 | 
				
			||||||
@@ -71,6 +74,8 @@ HEADERS += sqlitesearch.h \
 | 
				
			|||||||
    dbmigrator.h \
 | 
					    dbmigrator.h \
 | 
				
			||||||
    defaulttextprocessor.h \
 | 
					    defaulttextprocessor.h \
 | 
				
			||||||
    dirscanworker.h \
 | 
					    dirscanworker.h \
 | 
				
			||||||
 | 
					    documentoutlineentry.h \
 | 
				
			||||||
 | 
					    documentprocessresult.h \
 | 
				
			||||||
    encodingdetector.h \
 | 
					    encodingdetector.h \
 | 
				
			||||||
    filedata.h \
 | 
					    filedata.h \
 | 
				
			||||||
    filesaver.h \
 | 
					    filesaver.h \
 | 
				
			||||||
@@ -93,6 +98,7 @@ HEADERS += sqlitesearch.h \
 | 
				
			|||||||
    savefileresult.h \
 | 
					    savefileresult.h \
 | 
				
			||||||
    searchresult.h \
 | 
					    searchresult.h \
 | 
				
			||||||
    sqlitedbservice.h \
 | 
					    sqlitedbservice.h \
 | 
				
			||||||
 | 
					    tagmanager.h \
 | 
				
			||||||
    tagstripperprocessor.h \
 | 
					    tagstripperprocessor.h \
 | 
				
			||||||
    token.h \
 | 
					    token.h \
 | 
				
			||||||
    common.h \
 | 
					    common.h \
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,25 +2,10 @@
 | 
				
			|||||||
#include <QFileInfo>
 | 
					#include <QFileInfo>
 | 
				
			||||||
#include <QDateTime>
 | 
					#include <QDateTime>
 | 
				
			||||||
#include <QSqlError>
 | 
					#include <QSqlError>
 | 
				
			||||||
 | 
					#include "looqsgeneralexception.h"
 | 
				
			||||||
#include "sqlitedbservice.h"
 | 
					#include "sqlitedbservice.h"
 | 
				
			||||||
#include "filedata.h"
 | 
					#include "filedata.h"
 | 
				
			||||||
#include "logger.h"
 | 
					#include "logger.h"
 | 
				
			||||||
bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
					 | 
				
			||||||
	query.prepare("SELECT 1 FROM file WHERE path = ? and mtime = ?");
 | 
					 | 
				
			||||||
	query.addBindValue(path);
 | 
					 | 
				
			||||||
	query.addBindValue(mtime);
 | 
					 | 
				
			||||||
	if(!query.exec())
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		throw LooqsGeneralException("Error while trying to query for file existance: " + query.lastError().text());
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if(!query.next())
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return query.value(0).toBool();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
 | 
					QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -31,13 +16,7 @@ QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
std::optional<QChar> SqliteDbService::queryFileType(QString absPath)
 | 
					std::optional<QChar> SqliteDbService::queryFileType(QString absPath)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
						auto query = exec("SELECT filetype FROM file WHERE path = ?", {absPath});
 | 
				
			||||||
	query.prepare("SELECT filetype FROM file WHERE path = ?");
 | 
					 | 
				
			||||||
	query.addBindValue(absPath);
 | 
					 | 
				
			||||||
	if(!query.exec())
 | 
					 | 
				
			||||||
	{
 | 
					 | 
				
			||||||
		throw LooqsGeneralException("Error while trying to query for file type: " + query.lastError().text());
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if(!query.next())
 | 
						if(!query.next())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		return {};
 | 
							return {};
 | 
				
			||||||
@@ -47,18 +26,17 @@ std::optional<QChar> SqliteDbService::queryFileType(QString absPath)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
bool SqliteDbService::fileExistsInDatabase(QString path)
 | 
					bool SqliteDbService::fileExistsInDatabase(QString path)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
						return execBool("SELECT 1 FROM file WHERE path = ?", {path});
 | 
				
			||||||
	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());
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	if(!query.next())
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
		return false;
 | 
						return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ?", {path, mtime});
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	return query.value(0).toBool();
 | 
					
 | 
				
			||||||
 | 
					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)
 | 
					SqliteDbService::SqliteDbService(DatabaseFactory &dbFactory)
 | 
				
			||||||
@@ -126,6 +104,117 @@ unsigned int SqliteDbService::getFiles(QVector<FileData> &results, QString wildC
 | 
				
			|||||||
	return processedRows;
 | 
						return processedRows;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QString> SqliteDbService::getTags()
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QVector<QString> result;
 | 
				
			||||||
 | 
						auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
				
			||||||
 | 
						query.prepare("SELECT name FROM tag ORDER by name ASC");
 | 
				
			||||||
 | 
						query.setForwardOnly(true);
 | 
				
			||||||
 | 
						if(!query.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while(query.next())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							QString tagname = query.value(0).toString();
 | 
				
			||||||
 | 
							result.append(tagname);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QString> SqliteDbService::getTagsForPath(QString path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QVector<QString> result;
 | 
				
			||||||
 | 
						auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
				
			||||||
 | 
						query.prepare("SELECT name FROM tag INNER JOIN filetag ON tag.id = filetag.tagid INNER JOIN file ON filetag.fileid "
 | 
				
			||||||
 | 
									  "= file.id WHERE file.path = ? ORDER BY name ASC");
 | 
				
			||||||
 | 
						query.addBindValue(path);
 | 
				
			||||||
 | 
						query.setForwardOnly(true);
 | 
				
			||||||
 | 
						if(!query.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while(query.next())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							QString tagname = query.value(0).toString();
 | 
				
			||||||
 | 
							result.append(tagname);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					QVector<QString> SqliteDbService::getPathsForTag(QString tag)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QVector<QString> result;
 | 
				
			||||||
 | 
						auto query = QSqlQuery(dbFactory->forCurrentThread());
 | 
				
			||||||
 | 
						query.prepare(
 | 
				
			||||||
 | 
							"SELECT file.path FROM tag INNER JOIN filetag ON tag.id = filetag.tagid INNER JOIN file ON filetag.fileid "
 | 
				
			||||||
 | 
							"= file.id WHERE tag.name = ?");
 | 
				
			||||||
 | 
						query.addBindValue(tag.toLower());
 | 
				
			||||||
 | 
						query.setForwardOnly(true);
 | 
				
			||||||
 | 
						if(!query.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							throw LooqsGeneralException("Error while trying to retrieve paths from database: " + query.lastError().text());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						while(query.next())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							QString path = query.value(0).toString();
 | 
				
			||||||
 | 
							result.append(path);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::setTags(QString path, const QSet<QString> &tags)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QSqlDatabase db = dbFactory->forCurrentThread();
 | 
				
			||||||
 | 
						if(!db.transaction())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Logger::error() << "Failed to open transaction for " << path << " : " << db.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QSqlQuery deletionQuery = QSqlQuery(db);
 | 
				
			||||||
 | 
						deletionQuery.prepare("DELETE FROM filetag WHERE fileid = (SELECT id FROM file WHERE path = ?)");
 | 
				
			||||||
 | 
						deletionQuery.addBindValue(path);
 | 
				
			||||||
 | 
						if(!deletionQuery.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Failed to delete existing tags " << deletionQuery.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for(const QString &tag : tags)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							QSqlQuery tagQuery = QSqlQuery(db);
 | 
				
			||||||
 | 
							tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
 | 
				
			||||||
 | 
							tagQuery.addBindValue(tag.toLower());
 | 
				
			||||||
 | 
							if(!tagQuery.exec())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								db.rollback();
 | 
				
			||||||
 | 
								Logger::error() << "Failed to insert tag " << tagQuery.lastError() << Qt::endl;
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							QSqlQuery fileTagQuery(db);
 | 
				
			||||||
 | 
							fileTagQuery.prepare(
 | 
				
			||||||
 | 
								"INSERT INTO filetag(fileid, tagid) VALUES((SELECT id FROM file WHERE path = ?), (SELECT id "
 | 
				
			||||||
 | 
								"FROM tag WHERE name = ?))");
 | 
				
			||||||
 | 
							fileTagQuery.bindValue(0, path);
 | 
				
			||||||
 | 
							fileTagQuery.bindValue(1, tag);
 | 
				
			||||||
 | 
							if(!fileTagQuery.exec())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								db.rollback();
 | 
				
			||||||
 | 
								Logger::error() << "Failed to assign tag to file" << Qt::endl;
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(!db.commit())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Failed to commit transaction when saving tags" << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData)
 | 
					bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	QString ftsInsertStatement;
 | 
						QString ftsInsertStatement;
 | 
				
			||||||
@@ -164,7 +253,55 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid
 | 
				
			|||||||
	return true;
 | 
						return true;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly)
 | 
					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());
 | 
				
			||||||
 | 
						query.prepare(querystr);
 | 
				
			||||||
 | 
						for(const QVariant &v : args)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							query.addBindValue(v);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(!query.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							throw LooqsGeneralException("Error while exec(): " + query.lastError().text() + " for query: " + querystr);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return query;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::execBool(QString querystr, std::initializer_list<QVariant> args)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						auto query = exec(querystr, args);
 | 
				
			||||||
 | 
						if(!query.next())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return query.value(0).toBool();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResult &processResult, bool pathsOnly)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	QString absPath = fileInfo.absoluteFilePath();
 | 
						QString absPath = fileInfo.absoluteFilePath();
 | 
				
			||||||
	auto mtime = fileInfo.lastModified().toSecsSinceEpoch();
 | 
						auto mtime = fileInfo.lastModified().toSecsSinceEpoch();
 | 
				
			||||||
@@ -209,18 +346,24 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
 | 
				
			|||||||
	if(!pathsOnly)
 | 
						if(!pathsOnly)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		int lastid = inserterQuery.lastInsertId().toInt();
 | 
							int lastid = inserterQuery.lastInsertId().toInt();
 | 
				
			||||||
		if(!insertToFTS(false, db, lastid, pageData))
 | 
							if(!insertToFTS(false, db, lastid, processResult.pages))
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			db.rollback();
 | 
								db.rollback();
 | 
				
			||||||
			Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
 | 
								Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
 | 
				
			||||||
			return DBFAIL;
 | 
								return DBFAIL;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if(!insertToFTS(true, db, lastid, pageData))
 | 
							if(!insertToFTS(true, db, lastid, processResult.pages))
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			db.rollback();
 | 
								db.rollback();
 | 
				
			||||||
			Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
 | 
								Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
 | 
				
			||||||
			return DBFAIL;
 | 
								return DBFAIL;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if(!insertOutline(db, lastid, processResult.outlines))
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								db.rollback();
 | 
				
			||||||
 | 
								Logger::error() << "Failed to insert outline data " << Qt::endl;
 | 
				
			||||||
 | 
								return DBFAIL;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if(!db.commit())
 | 
						if(!db.commit())
 | 
				
			||||||
@@ -231,3 +374,123 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return OK;
 | 
						return OK;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::addTag(QString tag, QString path)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QVector<QString> paths;
 | 
				
			||||||
 | 
						paths.append(path);
 | 
				
			||||||
 | 
						return addTag(tag, paths);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QSqlDatabase db = dbFactory->forCurrentThread();
 | 
				
			||||||
 | 
						QSqlQuery tagQuery(db);
 | 
				
			||||||
 | 
						QSqlQuery fileTagQuery(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tag = tag.toLower();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
 | 
				
			||||||
 | 
						tagQuery.addBindValue(tag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileTagQuery.prepare("INSERT INTO filetag(fileid, tagid) VALUES((SELECT id FROM file WHERE path = ?), (SELECT id "
 | 
				
			||||||
 | 
											 "FROM tag WHERE name = ?))");
 | 
				
			||||||
 | 
						fileTagQuery.bindValue(1, tag);
 | 
				
			||||||
 | 
						if(!db.transaction())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Logger::error() << "Failed to open transaction to add paths for tag " << tag << " : " << db.lastError()
 | 
				
			||||||
 | 
											<< Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(!tagQuery.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Failed INSERT query" << tagQuery.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for(const QString &path : paths)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							fileTagQuery.bindValue(0, path);
 | 
				
			||||||
 | 
							if(!fileTagQuery.exec())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								db.rollback();
 | 
				
			||||||
 | 
								Logger::error() << "Failed to add paths to tag" << Qt::endl;
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(!db.commit())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Failed to commit tag insertion transaction" << db.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::removePathsForTag(QString tag, const QVector<QString> &paths)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QSqlDatabase db = dbFactory->forCurrentThread();
 | 
				
			||||||
 | 
						QSqlQuery tagQuery(db);
 | 
				
			||||||
 | 
						QSqlQuery fileTagQuery(db);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tag = tag.toLower();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileTagQuery.prepare(
 | 
				
			||||||
 | 
							"DELETE FROM filetag WHERE fileid = (SELECT id FROM file WHERE path = ?) AND tagid = (SELECT id "
 | 
				
			||||||
 | 
							"FROM tag WHERE name = ?)");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fileTagQuery.bindValue(1, tag);
 | 
				
			||||||
 | 
						for(const QString &path : paths)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							fileTagQuery.bindValue(0, path);
 | 
				
			||||||
 | 
							if(!fileTagQuery.exec())
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								Logger::error() << "An error occured while trying to remove paths from tag assignment" << Qt::endl;
 | 
				
			||||||
 | 
								return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool SqliteDbService::deleteTag(QString tag)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						QSqlDatabase db = dbFactory->forCurrentThread();
 | 
				
			||||||
 | 
						if(!db.transaction())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							Logger::error() << "Failed to open transaction while trying to delete tag " << tag << " : " << db.lastError()
 | 
				
			||||||
 | 
											<< Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						tag = tag.toLower();
 | 
				
			||||||
 | 
						QSqlQuery assignmentDeleteQuery(db);
 | 
				
			||||||
 | 
						assignmentDeleteQuery.prepare("DELETE FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)");
 | 
				
			||||||
 | 
						assignmentDeleteQuery.addBindValue(tag);
 | 
				
			||||||
 | 
						if(!assignmentDeleteQuery.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QSqlQuery deleteTagQuery(db);
 | 
				
			||||||
 | 
						deleteTagQuery.prepare("DELETE FROM tag WHERE name = ?");
 | 
				
			||||||
 | 
						deleteTagQuery.addBindValue(tag);
 | 
				
			||||||
 | 
						if(!deleteTagQuery.exec())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if(!db.commit())
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							db.rollback();
 | 
				
			||||||
 | 
							Logger::error() << "Error while trying to delete tag: " << db.lastError() << Qt::endl;
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "databasefactory.h"
 | 
					#include "databasefactory.h"
 | 
				
			||||||
#include "utils.h"
 | 
					#include "utils.h"
 | 
				
			||||||
#include "pagedata.h"
 | 
					#include "documentprocessresult.h"
 | 
				
			||||||
#include "filedata.h"
 | 
					#include "filedata.h"
 | 
				
			||||||
#include "../shared/sqlitesearch.h"
 | 
					#include "../shared/sqlitesearch.h"
 | 
				
			||||||
#include "../shared/token.h"
 | 
					#include "../shared/token.h"
 | 
				
			||||||
@@ -17,16 +17,32 @@ class SqliteDbService
 | 
				
			|||||||
	DatabaseFactory *dbFactory = nullptr;
 | 
						DatabaseFactory *dbFactory = nullptr;
 | 
				
			||||||
	bool insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData);
 | 
						bool insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid, QVector<PageData> &pageData);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						QSqlQuery exec(QString query, std::initializer_list<QVariant> args);
 | 
				
			||||||
 | 
						bool execBool(QString querystr, std::initializer_list<QVariant> args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
	SqliteDbService(DatabaseFactory &dbFactory);
 | 
						SqliteDbService(DatabaseFactory &dbFactory);
 | 
				
			||||||
	SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
 | 
						SaveFileResult saveFile(QFileInfo fileInfo, DocumentProcessResult &pageData, bool pathsOnly);
 | 
				
			||||||
	unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
 | 
					
 | 
				
			||||||
	bool deleteFile(QString path);
 | 
						bool deleteFile(QString path);
 | 
				
			||||||
	bool fileExistsInDatabase(QString path);
 | 
						bool fileExistsInDatabase(QString path);
 | 
				
			||||||
	bool fileExistsInDatabase(QString path, qint64 mtime);
 | 
						bool fileExistsInDatabase(QString path, qint64 mtime);
 | 
				
			||||||
 | 
						bool fileExistsInDatabase(QString path, qint64 mtime, QChar filetype);
 | 
				
			||||||
 | 
						unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bool addTag(QString tag, QString path);
 | 
				
			||||||
 | 
						bool addTag(QString tag, const QVector<QString> &paths);
 | 
				
			||||||
 | 
						QVector<QString> getTags();
 | 
				
			||||||
 | 
						QVector<QString> getTagsForPath(QString path);
 | 
				
			||||||
 | 
						QVector<QString> getPathsForTag(QString path);
 | 
				
			||||||
 | 
						bool setTags(QString path, const QSet<QString> &tags);
 | 
				
			||||||
 | 
						bool removePathsForTag(QString tag, const QVector<QString> &paths);
 | 
				
			||||||
 | 
						bool deleteTag(QString tag);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	QVector<SearchResult> search(const LooqsQuery &query);
 | 
						QVector<SearchResult> search(const LooqsQuery &query);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	std::optional<QChar> queryFileType(QString absPath);
 | 
						std::optional<QChar> queryFileType(QString absPath);
 | 
				
			||||||
 | 
						bool insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines);
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif // SQLITEDBSERVICE_H
 | 
					#endif // SQLITEDBSERVICE_H
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -143,6 +143,16 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
 | 
				
			|||||||
	{
 | 
						{
 | 
				
			||||||
		return {" fts MATCH ? ", {escapeFtsArgument(value)}};
 | 
							return {" fts MATCH ? ", {escapeFtsArgument(value)}};
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if(token.type == FILTER_TAG_ASSIGNED)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ",
 | 
				
			||||||
 | 
									{value.toLower()}};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if(token.type == FILTER_OUTLINE_CONTAINS)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							return {" outline.text LIKE '%' || ? || '%'  ", {value.toLower()}};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	throw LooqsGeneralException("Unknown token passed (should not happen)");
 | 
						throw LooqsGeneralException("Unknown token passed (should not happen)");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -151,6 +161,7 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
 | 
				
			|||||||
	QString whereSql;
 | 
						QString whereSql;
 | 
				
			||||||
	QVector<QString> bindValues;
 | 
						QVector<QString> bindValues;
 | 
				
			||||||
	bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
 | 
						bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
 | 
				
			||||||
 | 
						bool isOutlineSearch = query.hasOutlineSearch();
 | 
				
			||||||
	if(query.getTokens().isEmpty())
 | 
						if(query.getTokens().isEmpty())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		throw LooqsGeneralException("Nothing to search for supplied");
 | 
							throw LooqsGeneralException("Nothing to search for supplied");
 | 
				
			||||||
@@ -179,31 +190,38 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		QString whereSqlTrigram = whereSql;
 | 
							QString whereSqlTrigram = whereSql;
 | 
				
			||||||
		whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
 | 
							whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
 | 
				
			||||||
		prepSql =
 | 
							prepSql = "SELECT DISTINCT path, page, mtime, size, filetype FROM ("
 | 
				
			||||||
			"SELECT DISTINCT path, page, mtime, size, filetype FROM ("
 | 
					 | 
				
			||||||
				  "SELECT file.path AS path,  content.page AS page, file.mtime AS mtime, file.size AS size, "
 | 
									  "SELECT file.path AS path,  content.page AS page, file.mtime AS mtime, file.size AS size, "
 | 
				
			||||||
				  "file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
 | 
									  "file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
 | 
				
			||||||
				  "content.fileid "
 | 
									  "content.fileid "
 | 
				
			||||||
				  "INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
 | 
									  "INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
 | 
				
			||||||
				  whereSql +
 | 
									  whereSql +
 | 
				
			||||||
				  "UNION ALL SELECT file.path AS path,  content.page AS page, file.mtime AS mtime, file.size AS size, "
 | 
									  "UNION ALL SELECT file.path AS path,  content.page AS page, file.mtime AS mtime, file.size AS size, "
 | 
				
			||||||
			"file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON file.id = "
 | 
									  "file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON "
 | 
				
			||||||
 | 
									  "file.id = "
 | 
				
			||||||
				  "content.fileid " +
 | 
									  "content.fileid " +
 | 
				
			||||||
			"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + whereSqlTrigram +
 | 
									  "INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " +
 | 
				
			||||||
			" ) " + sortSql;
 | 
									  whereSqlTrigram + " ) " + sortSql;
 | 
				
			||||||
		++bindIterations;
 | 
							++bindIterations;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	else
 | 
						else
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
 | 
							QString pageColumn = "'0' as page";
 | 
				
			||||||
 | 
							QString joiners = "";
 | 
				
			||||||
 | 
							if(isOutlineSearch)
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								pageColumn = "outline.page as page";
 | 
				
			||||||
 | 
								joiners = " INNER JOIN outline ON outline.fileid = file.id ";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		if(sortSql.isEmpty())
 | 
							if(sortSql.isEmpty())
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			sortSql = "ORDER BY file.mtime DESC";
 | 
								sortSql = "ORDER BY file.mtime DESC";
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		prepSql = "SELECT file.path AS path, '0' as page,  file.mtime AS mtime, file.size AS size, file.filetype AS "
 | 
							prepSql = "SELECT DISTINCT file.path AS path, " + pageColumn +
 | 
				
			||||||
				  "filetype FROM file WHERE  1=1 AND " +
 | 
									  ",file.mtime AS mtime, file.size AS size, "
 | 
				
			||||||
				  whereSql + " " + sortSql;
 | 
									  "file.filetype AS filetype FROM file" +
 | 
				
			||||||
 | 
									  joiners + " WHERE  1=1 AND " + whereSql + " " + sortSql;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	if(query.getLimit() > 0)
 | 
						if(query.getLimit() > 0)
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		prepSql += " LIMIT " + QString::number(query.getLimit());
 | 
							prepSql += " LIMIT " + QString::number(query.getLimit());
 | 
				
			||||||
@@ -237,7 +255,7 @@ QVector<SearchResult> SqliteSearch::search(const LooqsQuery &query)
 | 
				
			|||||||
		throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text());
 | 
							throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	bool contentSearch = query.hasContentSearch();
 | 
						bool contentSearch = query.hasContentSearch() || query.hasOutlineSearch();
 | 
				
			||||||
	while(dbQuery.next())
 | 
						while(dbQuery.next())
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		SearchResult result;
 | 
							SearchResult result;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										66
									
								
								shared/tagmanager.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										66
									
								
								shared/tagmanager.cpp
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -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
									
									
									
									
									
										Comhad gnáth
									
								
							
							
						
						
									
										28
									
								
								shared/tagmanager.h
									
									
									
									
									
										Comhad gnáth
									
								
							@@ -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
 | 
				
			||||||
@@ -4,11 +4,11 @@ TagStripperProcessor::TagStripperProcessor()
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
QVector<PageData> TagStripperProcessor::process(const QByteArray &data) const
 | 
					DocumentProcessResult TagStripperProcessor::process(const QByteArray &data) const
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	auto result = DefaultTextProcessor::process(data);
 | 
						auto result = DefaultTextProcessor::process(data);
 | 
				
			||||||
	// TODO: does not work properly with <br> and does not deal with entities...
 | 
						// TODO: does not work properly with <br> and does not deal with entities...
 | 
				
			||||||
 | 
						Q_ASSERT(result.pages.size() > 0);
 | 
				
			||||||
	result[0].content.remove(QRegExp("<[^>]*>"));
 | 
						result.pages[0].content.remove(QRegExp("<[^>]*>"));
 | 
				
			||||||
	return result;
 | 
						return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ class TagStripperProcessor : public DefaultTextProcessor
 | 
				
			|||||||
	TagStripperProcessor();
 | 
						TagStripperProcessor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public:
 | 
					  public:
 | 
				
			||||||
	QVector<PageData> process(const QByteArray &data) const override;
 | 
						DocumentProcessResult process(const QByteArray &data) const override;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#endif // XMLSTRIPPERPROCESSOR_H
 | 
					#endif // XMLSTRIPPERPROCESSOR_H
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,9 @@ enum TokenType
 | 
				
			|||||||
	FILTER_PATH_SIZE,
 | 
						FILTER_PATH_SIZE,
 | 
				
			||||||
	FILTER_PATH_ENDS,
 | 
						FILTER_PATH_ENDS,
 | 
				
			||||||
	FILTER_PATH_STARTS,
 | 
						FILTER_PATH_STARTS,
 | 
				
			||||||
	FILTER_CONTENT = 512,
 | 
						FILTER_TAG_ASSIGNED,
 | 
				
			||||||
 | 
						FILTER_OUTLINE_CONTAINS,
 | 
				
			||||||
 | 
						FILTER_CONTENT = 512, /* Everything below here is content search (except LIMIT) */
 | 
				
			||||||
	FILTER_CONTENT_CONTAINS,
 | 
						FILTER_CONTENT_CONTAINS,
 | 
				
			||||||
	FILTER_CONTENT_PAGE,
 | 
						FILTER_CONTENT_PAGE,
 | 
				
			||||||
	LIMIT = 1024
 | 
						LIMIT = 1024
 | 
				
			||||||
 
 | 
				
			|||||||
		Tagairt in Eagrán Nua
	
	Cuir bac ar úsáideoir