Commitok összehasonlítása
	
		
			45 Commit-ok
		
	
	
		
			v0.8
			...
			2b1dc72410
		
	
	| Szerző | SHA1 | Dátum | |
|---|---|---|---|
| 2b1dc72410 | |||
| 22fee1d064 | |||
| 50a5c399c4 | |||
| 4b3ebb08c2 | |||
| 4c5643e342 | |||
| e8d217e191 | |||
| 4604970f9d | |||
| 6cd7a92576 | |||
| 9540f27c95 | |||
| 244e6aa95e | |||
| 3e3a4d0cd4 | |||
| 94fbdb5a92 | |||
| abd1b94235 | |||
| d2dcc2f95b | |||
| f324da0369 | |||
| a3cfb7ade1 | |||
| 44b9986166 | |||
| 4fe745e858 | |||
| a0b95479e2 | |||
| 07630c3b36 | |||
| a7c4ad5e7c | |||
| 32c2653b0f | |||
| a869d677a3 | |||
| 2550af307f | |||
| 0b829215e5 | |||
| 566c4a8c58 | |||
| 3d0c236cb3 | |||
| 590a8888fc | |||
| ccc4d09b36 | |||
| 8298b675aa | |||
| 71789b5b56 | |||
| 363d207ccc | |||
| 4b1522b82a | |||
| efca45b88a | |||
| 0cd19b53e4 | |||
| 889725033a | |||
| 8485a25b21 | |||
| 57f0afaf91 | |||
| 20a1f8b2cd | |||
| a47af257f3 | |||
| 9686ef30c7 | |||
| abce4cfcd9 | |||
| d55187a71c | |||
| 9e1bc98f38 | |||
| 496aefaa09 | 
							
								
								
									
										25
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,5 +1,30 @@ | |||||||
| # 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 | ||||||
|  |  | ||||||
|  | CHANGES: | ||||||
|  |  - Fix regression causing previews in second (and higher) result page to not render | ||||||
|  |  - Minor improvements | ||||||
|  |  | ||||||
| ## 2022-10-22 - v0.8 | ## 2022-10-22 - v0.8 | ||||||
|  |  | ||||||
| CHANGES: | CHANGES: | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -1,4 +1,4 @@ | |||||||
| Copyright (c) 2018-2022: Albert Schwarzkopf <looqs at quitesimple period org> | Copyright (c) 2018-2023: Albert Schwarzkopf <looqs at quitesimple period org> | ||||||
|  |  | ||||||
| looqs is made available under the following license:  | looqs is made available under the following license:  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										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-10-22, v0.8 | 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 | ||||||
|   | |||||||
| @@ -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() | ||||||
| { | { | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ void CommandAdd::indexerFinished() | |||||||
| 	if(failedPathsCount > 0) | 	if(failedPathsCount > 0) | ||||||
| 	{ | 	{ | ||||||
| 		Logger::info() << "Failed paths: " << Qt::endl; | 		Logger::info() << "Failed paths: " << Qt::endl; | ||||||
| 		for(QString paths : result.failedPaths()) | 		for(const QString &paths : result.failedPaths()) | ||||||
| 		{ | 		{ | ||||||
| 			Logger::info() << paths << Qt::endl; | 			Logger::info() << paths << Qt::endl; | ||||||
| 		} | 		} | ||||||
| @@ -41,23 +41,36 @@ 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 " | ||||||
| 						"exit asap, but it's possible that a few files will still be processed. " | 						"given, looqs will " | ||||||
|  | 						"exit asap, but it's possible that a few files will still be processed." | ||||||
| 						"Set -t 1 to avoid this behavior, but processing will be slower. "}, | 						"Set -t 1 to avoid this behavior, but processing will be slower. "}, | ||||||
|  | 					   {{"n", "no-content"}, "Only add paths to database. Do not index content"}, | ||||||
|  | 					   {{"v", "verbose"}, "Print paths of files being processed"}, | ||||||
|  | 					   {{"f", "fill-content"}, "Index content for files previously indexed with -n"}, | ||||||
| 					   {{"t", "threads"}, "Number of threads to use.", "threads"}}); | 					   {{"t", "threads"}, "Number of threads to use.", "threads"}}); | ||||||
|  |  | ||||||
| 	parser.addHelpOption(); | 	parser.addHelpOption(); | ||||||
| 	parser.addPositionalArgument("add", "Add paths to the index", | 	parser.addPositionalArgument("add", "Add paths to the index", | ||||||
| 								 "add [paths...]. If no path is given, read from stdin, one path per line."); | 								 "add [paths...]. If no path is given, read from stdin, one path per line."); | ||||||
|  |  | ||||||
| 	parser.process(arguments); | 	parser.process(arguments); | ||||||
| 	this->keepGoing = parser.isSet("continue"); | 	this->keepGoing = parser.isSet("continue"); | ||||||
|  | 	bool pathsOnly = parser.isSet("no-content"); | ||||||
|  | 	bool fillContent = parser.isSet("fill-content"); | ||||||
|  | 	bool verbose = parser.isSet("verbose"); | ||||||
|  |  | ||||||
| 	if(parser.isSet("threads")) | 	if(parser.isSet("threads")) | ||||||
| 	{ | 	{ | ||||||
| 		QString threadsCount = parser.value("threads"); | 		QString threadsCount = parser.value("threads"); | ||||||
| 		QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt()); | 		QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if(pathsOnly && fillContent) | ||||||
|  | 	{ | ||||||
|  | 		Logger::error() << "Invalid options: -n and -f cannot both be set"; | ||||||
|  | 		return EXIT_FAILURE; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	QStringList files = parser.positionalArguments(); | 	QStringList files = parser.positionalArguments(); | ||||||
|  |  | ||||||
| 	if(files.length() == 0) | 	if(files.length() == 0) | ||||||
| @@ -71,15 +84,47 @@ int CommandAdd::handle(QStringList arguments) | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	FileSaverOptions fileSaverOptions; | ||||||
|  | 	fileSaverOptions.keepGoing = keepGoing; | ||||||
|  | 	fileSaverOptions.fillExistingContentless = fillContent; | ||||||
|  | 	fileSaverOptions.metadataOnly = pathsOnly; | ||||||
|  | 	fileSaverOptions.verbose = verbose; | ||||||
|  |  | ||||||
| 	indexer = new Indexer(*this->dbService); | 	indexer = new Indexer(*this->dbService); | ||||||
|  | 	indexer->setFileSaverOptions(fileSaverOptions); | ||||||
|  |  | ||||||
| 	indexer->setTargetPaths(files.toVector()); | 	indexer->setTargetPaths(files.toVector()); | ||||||
| 	indexer->setKeepGoing(keepGoing); |  | ||||||
|  | 	if(verbose) | ||||||
|  | 	{ | ||||||
|  | 		indexer->setProgressReportThreshold(1); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	connect(indexer, &Indexer::pathsCountChanged, this, | 	connect(indexer, &Indexer::pathsCountChanged, this, | ||||||
| 			[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; }); | 			[](int pathsCount) { Logger::info() << "Found paths: " << pathsCount << Qt::endl; }); | ||||||
| 	connect(indexer, &Indexer::indexProgress, this, | 	connect(indexer, &Indexer::indexProgress, this, | ||||||
| 			[](int pathsCount, unsigned int added, unsigned int skipped, unsigned int failed, unsigned int totalCount) | 			[verbose, this](int pathsCount, unsigned int /*added*/, unsigned int /*skipped*/, unsigned int /*failed*/, | ||||||
| 			{ 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 "commandlist.h" | #include "commandlist.h" | ||||||
| #include "databasefactory.h" |  | ||||||
| #include "logger.h" | #include "logger.h" | ||||||
|  |  | ||||||
| int CommandList::handle(QStringList arguments) | int CommandList::handle(QStringList arguments) | ||||||
|   | |||||||
| @@ -1,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
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								cli/commandtag.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | |||||||
|  | #include <QCommandLineParser> | ||||||
|  | #include "commandtag.h" | ||||||
|  | #include "logger.h" | ||||||
|  | #include "tagmanager.h" | ||||||
|  |  | ||||||
|  | bool CommandTag::ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths) | ||||||
|  | { | ||||||
|  | 	for(const QString &path : paths) | ||||||
|  | 	{ | ||||||
|  | 		QFileInfo info{path}; | ||||||
|  | 		if(!info.exists()) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Can't add tag for file  " + info.absoluteFilePath() + " because it does not exist" | ||||||
|  | 							<< Qt::endl; | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		QString absolutePath = info.absoluteFilePath(); | ||||||
|  | 		if(!this->dbService->fileExistsInDatabase(absolutePath)) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Only files that have been indexed can be tagged. File not in index: " + absolutePath | ||||||
|  | 							<< Qt::endl; | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  | 		absolutePaths.append(absolutePath); | ||||||
|  | 	} | ||||||
|  | 	return true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int CommandTag::handle(QStringList arguments) | ||||||
|  | { | ||||||
|  | 	QCommandLineParser parser; | ||||||
|  | 	parser.addPositionalArgument("add", "Adds a tag to a file", | ||||||
|  | 								 "add [tag] [paths...]. Adds the tag to the specified paths"); | ||||||
|  | 	parser.addPositionalArgument("remove", "Removes a path associated to a tag", "remove [tag] [path]"); | ||||||
|  | 	parser.addPositionalArgument("delete", "Deletes a tag", "delete [tag]"); | ||||||
|  | 	parser.addPositionalArgument("list", "Lists paths associated with a tag, or all tags", "list [tag]"); | ||||||
|  | 	parser.addPositionalArgument("show", "Lists tags associated with a path", "show [path]"); | ||||||
|  |  | ||||||
|  | 	parser.addHelpOption(); | ||||||
|  |  | ||||||
|  | 	parser.parse(arguments); | ||||||
|  |  | ||||||
|  | 	QStringList args = parser.positionalArguments(); | ||||||
|  | 	if(args.length() == 0) | ||||||
|  | 	{ | ||||||
|  | 		parser.showHelp(EXIT_FAILURE); | ||||||
|  | 		return EXIT_FAILURE; | ||||||
|  | 	} | ||||||
|  | 	TagManager tagManager{*this->dbService}; | ||||||
|  | 	QString cmd = args[0]; | ||||||
|  | 	if(cmd == "add") | ||||||
|  | 	{ | ||||||
|  | 		if(args.length() < 3) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Not enough arguments provided. 'add' requires a tag followed by at least one path" | ||||||
|  | 							<< Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		QString tag = args[1]; | ||||||
|  | 		QVector<QString> paths = args.mid(2).toVector(); | ||||||
|  |  | ||||||
|  | 		QVector<QString> absolutePaths; | ||||||
|  | 		if(!ensureAbsolutePaths(paths, absolutePaths)) | ||||||
|  | 		{ | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		bool result = tagManager.addPathsToTag(tag, absolutePaths); | ||||||
|  | 		if(!result) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Failed to assign tags" << Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		return EXIT_SUCCESS; | ||||||
|  | 	} | ||||||
|  | 	if(cmd == "list") | ||||||
|  | 	{ | ||||||
|  |  | ||||||
|  | 		QString tag; | ||||||
|  | 		if(args.length() >= 2) | ||||||
|  | 		{ | ||||||
|  | 			tag = args[1]; | ||||||
|  | 		} | ||||||
|  | 		QVector<QString> entries; | ||||||
|  | 		if(tag.isEmpty()) | ||||||
|  | 		{ | ||||||
|  | 			entries = tagManager.getTags(); | ||||||
|  | 		} | ||||||
|  | 		else | ||||||
|  | 		{ | ||||||
|  | 			entries = tagManager.getPaths(tag); | ||||||
|  | 		} | ||||||
|  | 		for(const QString &entry : entries) | ||||||
|  | 		{ | ||||||
|  | 			Logger::info() << entry << Qt::endl; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if(cmd == "remove") | ||||||
|  | 	{ | ||||||
|  | 		if(args.length() < 3) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Not enough arguments provided. 'remove' requires a tag followed by at least one path" | ||||||
|  | 							<< Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		QString tag = args[1]; | ||||||
|  | 		QVector<QString> paths = args.mid(2).toVector(); | ||||||
|  |  | ||||||
|  | 		QVector<QString> absolutePaths; | ||||||
|  | 		if(!ensureAbsolutePaths(paths, absolutePaths)) | ||||||
|  | 		{ | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(!tagManager.removePathsForTag(tag, absolutePaths)) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Failed to remove path assignments" << Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if(cmd == "delete") | ||||||
|  | 	{ | ||||||
|  | 		if(args.length() != 2) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "The 'delete' command requires the tag to delete" << Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		if(!tagManager.deleteTag(args[1])) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "Failed to delete tag" << Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	if(cmd == "show") | ||||||
|  | 	{ | ||||||
|  | 		if(args.length() != 2) | ||||||
|  | 		{ | ||||||
|  | 			Logger::error() << "The 'show' command requires a path to show the assigned tags" << Qt::endl; | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		QString path = args[1]; | ||||||
|  | 		QVector<QString> absolutePaths; | ||||||
|  | 		if(!ensureAbsolutePaths({path}, absolutePaths)) | ||||||
|  | 		{ | ||||||
|  | 			return EXIT_FAILURE; | ||||||
|  | 		} | ||||||
|  | 		QVector<QString> tags = tagManager.getTags(absolutePaths.at(0)); | ||||||
|  | 		for(const QString &entry : tags) | ||||||
|  | 		{ | ||||||
|  | 			Logger::info() << entry << Qt::endl; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return EXIT_SUCCESS; | ||||||
|  | } | ||||||
							
								
								
									
										16
									
								
								cli/commandtag.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								cli/commandtag.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | |||||||
|  | #ifndef COMMANDTAG_H | ||||||
|  | #define COMMANDTAG_H | ||||||
|  | #include "command.h" | ||||||
|  |  | ||||||
|  | class CommandTag : public Command | ||||||
|  | { | ||||||
|  |   protected: | ||||||
|  | 	bool ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths); | ||||||
|  |  | ||||||
|  |   public: | ||||||
|  | 	using Command::Command; | ||||||
|  |  | ||||||
|  | 	int handle(QStringList arguments) override; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // COMMANDTAG_H | ||||||
| @@ -38,10 +38,13 @@ int CommandUpdate::handle(QStringList arguments) | |||||||
| 		QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt()); | 		QThreadPool::globalInstance()->setMaxThreadCount(threadsCount.toInt()); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	bool hasErrors = false; |  | ||||||
| 	IndexSyncer *syncer = new IndexSyncer(*this->dbService); | 	IndexSyncer *syncer = new IndexSyncer(*this->dbService); | ||||||
| 	syncer->setKeepGoing(keepGoing); |  | ||||||
| 	syncer->setVerbose(verbose); | 	FileSaverOptions fileOptions; | ||||||
|  | 	fileOptions.keepGoing = keepGoing; | ||||||
|  | 	fileOptions.verbose = verbose; | ||||||
|  |  | ||||||
|  | 	syncer->setFileSaverOptions(fileOptions); | ||||||
| 	syncer->setPattern(pattern); | 	syncer->setPattern(pattern); | ||||||
| 	syncer->setDryRun(dryRun); | 	syncer->setDryRun(dryRun); | ||||||
| 	syncer->setRemoveDeletedFromIndex(deleteMissing); | 	syncer->setRemoveDeletedFromIndex(deleteMissing); | ||||||
| @@ -60,7 +63,7 @@ int CommandUpdate::handle(QStringList arguments) | |||||||
| 		/* TODO: updated not printed, handled be verbose in FileSaver, but this can be improved */ | 		/* TODO: updated not printed, handled be verbose in FileSaver, but this can be improved */ | ||||||
| 	} | 	} | ||||||
| 	connect(syncer, &IndexSyncer::finished, this, | 	connect(syncer, &IndexSyncer::finished, this, | ||||||
| 			[&](unsigned int totalUpdated, unsigned int totalRemoved, unsigned int totalErrors) | 			[this, dryRun, keepGoing](unsigned int totalUpdated, unsigned int totalRemoved, unsigned int totalErrors) | ||||||
| 			{ | 			{ | ||||||
| 				Logger::info() << "Syncing finished" << Qt::endl; | 				Logger::info() << "Syncing finished" << Qt::endl; | ||||||
|  |  | ||||||
| @@ -72,7 +75,7 @@ int CommandUpdate::handle(QStringList arguments) | |||||||
| 				} | 				} | ||||||
|  |  | ||||||
| 				int retval = 0; | 				int retval = 0; | ||||||
| 				if(hasErrors && !keepGoing) | 				if(this->hasErrors && !keepGoing) | ||||||
| 				{ | 				{ | ||||||
| 					retval = 1; | 					retval = 1; | ||||||
| 				} | 				} | ||||||
| @@ -82,7 +85,7 @@ int CommandUpdate::handle(QStringList arguments) | |||||||
| 			[&](QString error) | 			[&](QString error) | ||||||
| 			{ | 			{ | ||||||
| 				Logger::error() << error << Qt::endl; | 				Logger::error() << error << Qt::endl; | ||||||
| 				hasErrors = true; | 				this->hasErrors = true; | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 	this->autoFinish = false; | 	this->autoFinish = false; | ||||||
|   | |||||||
| @@ -4,6 +4,9 @@ | |||||||
| #include "filesaver.h" | #include "filesaver.h" | ||||||
| class CommandUpdate : public Command | class CommandUpdate : public Command | ||||||
| { | { | ||||||
|  |   protected: | ||||||
|  | 	bool hasErrors = false; | ||||||
|  |  | ||||||
|   public: |   public: | ||||||
| 	using Command::Command; | 	using Command::Command; | ||||||
| 	int handle(QStringList arguments) override; | 	int handle(QStringList arguments) override; | ||||||
|   | |||||||
| @@ -21,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; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -34,6 +34,7 @@ SOURCES += \ | |||||||
|         main.cpp \ |         main.cpp \ | ||||||
|         mainwindow.cpp \ |         mainwindow.cpp \ | ||||||
|       clicklabel.cpp \ |       clicklabel.cpp \ | ||||||
|  |     previewcoordinator.cpp \ | ||||||
|     previewgenerator.cpp \ |     previewgenerator.cpp \ | ||||||
|     previewgeneratormapfunctor.cpp \ |     previewgeneratormapfunctor.cpp \ | ||||||
|     previewgeneratorodt.cpp \ |     previewgeneratorodt.cpp \ | ||||||
| @@ -54,6 +55,7 @@ HEADERS += \ | |||||||
|     ipcserver.h \ |     ipcserver.h \ | ||||||
|         mainwindow.h \ |         mainwindow.h \ | ||||||
|     clicklabel.h \ |     clicklabel.h \ | ||||||
|  |     previewcoordinator.h \ | ||||||
|     previewgenerator.h \ |     previewgenerator.h \ | ||||||
|     previewgeneratormapfunctor.h \ |     previewgeneratormapfunctor.h \ | ||||||
|     previewgeneratorodt.h \ |     previewgeneratorodt.h \ | ||||||
|   | |||||||
| @@ -28,7 +28,7 @@ void enableIpcSandbox() | |||||||
| 	policy->namespace_options = EXILE_UNSHARE_USER | EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_NETWORK; | 	policy->namespace_options = EXILE_UNSHARE_USER | EXILE_UNSHARE_MOUNT | EXILE_UNSHARE_NETWORK; | ||||||
| 	policy->no_new_privs = 1; | 	policy->no_new_privs = 1; | ||||||
| 	policy->drop_caps = 1; | 	policy->drop_caps = 1; | ||||||
| 	policy->vow_promises = exile_vows_from_str("thread cpath rpath unix stdio proc error"); | 	policy->vow_promises = exile_vows_from_str("thread cpath rpath wpath unix stdio proc error"); | ||||||
| 	policy->mount_path_policies_to_chroot = 1; | 	policy->mount_path_policies_to_chroot = 1; | ||||||
|  |  | ||||||
| 	QString ipcSocketPath = Common::ipcSocketPath(); | 	QString ipcSocketPath = Common::ipcSocketPath(); | ||||||
| @@ -193,7 +193,7 @@ int main(int argc, char *argv[]) | |||||||
| 															   Logger::error() << error << Qt::endl; | 															   Logger::error() << error << Qt::endl; | ||||||
| 															   QMessageBox::critical(nullptr, "Error during upgrade", | 															   QMessageBox::critical(nullptr, "Error during upgrade", | ||||||
| 																					 error); | 																					 error); | ||||||
| 															   qApp->quit(); | 															   exit(EXIT_FAILURE); | ||||||
| 														   } | 														   } | ||||||
|  |  | ||||||
| 								 ); | 								 ); | ||||||
|   | |||||||
| @@ -16,14 +16,15 @@ | |||||||
| #include <QScreen> | #include <QScreen> | ||||||
| #include <QProgressDialog> | #include <QProgressDialog> | ||||||
| #include <QDesktopWidget> | #include <QDesktopWidget> | ||||||
|  | #include <QWidgetAction> | ||||||
|  | #include <QInputDialog> | ||||||
|  |  | ||||||
| #include "mainwindow.h" | #include "mainwindow.h" | ||||||
| #include "ui_mainwindow.h" | #include "ui_mainwindow.h" | ||||||
| #include "clicklabel.h" | #include "clicklabel.h" | ||||||
| #include "../shared/sqlitesearch.h" | #include "../shared/sqlitesearch.h" | ||||||
| #include "../shared/looqsgeneralexception.h" | #include "../shared/looqsgeneralexception.h" | ||||||
| #include "../shared/common.h" | #include "../shared/common.h" | ||||||
| #include "ipcpreviewclient.h" |  | ||||||
| #include "previewgenerator.h" |  | ||||||
| #include "aboutdialog.h" | #include "aboutdialog.h" | ||||||
|  |  | ||||||
| MainWindow::MainWindow(QWidget *parent, QString socketPath) | MainWindow::MainWindow(QWidget *parent, QString socketPath) | ||||||
| @@ -32,8 +33,7 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath) | |||||||
| 	this->progressDialog.cancel(); // because constructing it shows it, quite weird | 	this->progressDialog.cancel(); // because constructing it shows it, quite weird | ||||||
| 	ui->setupUi(this); | 	ui->setupUi(this); | ||||||
| 	setWindowTitle(QCoreApplication::applicationName()); | 	setWindowTitle(QCoreApplication::applicationName()); | ||||||
| 	this->ipcPreviewClient.moveToThread(&this->ipcClientThread); |  | ||||||
| 	this->ipcPreviewClient.setSocketPath(socketPath); |  | ||||||
| 	QSettings settings; | 	QSettings settings; | ||||||
|  |  | ||||||
| 	this->dbFactory = new DatabaseFactory(Common::databasePath()); | 	this->dbFactory = new DatabaseFactory(Common::databasePath()); | ||||||
| @@ -45,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); | ||||||
| @@ -78,7 +81,7 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath) | |||||||
| 	ui->txtSearch->installEventFilter(this); | 	ui->txtSearch->installEventFilter(this); | ||||||
| 	ui->scrollArea->viewport()->installEventFilter(this); | 	ui->scrollArea->viewport()->installEventFilter(this); | ||||||
|  |  | ||||||
| 	this->ipcClientThread.start(); | 	this->previewCoordinator.setSocketPath(socketPath); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::addPathToIndex() | void MainWindow::addPathToIndex() | ||||||
| @@ -150,7 +153,7 @@ void MainWindow::connectSignals() | |||||||
| 	connect(this->indexer, &Indexer::finished, this, &MainWindow::finishIndexing); | 	connect(this->indexer, &Indexer::finished, this, &MainWindow::finishIndexing); | ||||||
|  |  | ||||||
| 	connect(ui->lstPaths->selectionModel(), &QItemSelectionModel::selectionChanged, this, | 	connect(ui->lstPaths->selectionModel(), &QItemSelectionModel::selectionChanged, this, | ||||||
| 			[&](const QItemSelection &selected, const QItemSelection &deselected) | 			[&](const QItemSelection & /*selected*/, const QItemSelection & /*deselected*/) | ||||||
| 			{ ui->btnDeletePath->setEnabled(this->ui->lstPaths->selectedItems().count() > 0); }); | 			{ ui->btnDeletePath->setEnabled(this->ui->lstPaths->selectedItems().count() > 0); }); | ||||||
|  |  | ||||||
| 	connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); }); | 	connect(ui->btnDeletePath, &QPushButton::clicked, this, [&] { qDeleteAll(ui->lstPaths->selectedItems()); }); | ||||||
| @@ -170,30 +173,29 @@ void MainWindow::connectSignals() | |||||||
| 				} | 				} | ||||||
| 			}); | 			}); | ||||||
| 	connect(ui->menuAboutAction, &QAction::triggered, this, | 	connect(ui->menuAboutAction, &QAction::triggered, this, | ||||||
| 			[this](bool checked) | 			[this](bool /*checked*/) | ||||||
| 			{ | 			{ | ||||||
| 				AboutDialog aboutDialog(this); | 				AboutDialog aboutDialog(this); | ||||||
|  |  | ||||||
| 				aboutDialog.exec(); | 				aboutDialog.exec(); | ||||||
| 			}); | 			}); | ||||||
| 	connect(ui->menuAboutQtAction, &QAction::triggered, this, | 	connect(ui->menuAboutQtAction, &QAction::triggered, this, | ||||||
| 			[this](bool checked) { QMessageBox::aboutQt(this, "About Qt"); }); | 			[this](bool /*checked*/) { QMessageBox::aboutQt(this, "About Qt"); }); | ||||||
| 	connect(ui->menuSyncIndexAction, &QAction::triggered, this, &MainWindow::startIndexSync); | 	connect(ui->menuSyncIndexAction, &QAction::triggered, this, &MainWindow::startIndexSync); | ||||||
| 	connect(ui->menuOpenUserManualAction, &QAction::triggered, this, | 	connect(ui->menuOpenUserManualAction, &QAction::triggered, this, | ||||||
| 			[this]() { QDesktopServices::openUrl(Common::userManualUrl()); }); | 			[]() { QDesktopServices::openUrl(Common::userManualUrl()); }); | ||||||
|  |  | ||||||
| 	connect(indexSyncer, &IndexSyncer::finished, this, | 	connect( | ||||||
| 			[&](unsigned int totalUpdated, unsigned int totalDeleted, unsigned int totalErrored) | 		indexSyncer, &IndexSyncer::finished, this, | ||||||
| 			{ | 		[&](unsigned int totalUpdated, unsigned int totalDeleted, unsigned int totalErrored) | ||||||
| 				this->progressDialog.cancel(); | 		{ | ||||||
|  | 			this->progressDialog.cancel(); | ||||||
|  |  | ||||||
| 				QMessageBox::information( | 			QMessageBox::information( | ||||||
| 					this, "Syncing finished", | 				this, "Syncing finished", | ||||||
| 					QString("Syncing finished\n\nTotal updated: %1\nTotal deleted: %2\nTotal errors: %3\n") | 				QString("Syncing finished\n\nTotal updated: %1\nTotal deleted: %2\nTotal errors: %3\n") | ||||||
| 						.arg(QString::number(totalUpdated)) | 					.arg(QString::number(totalUpdated), QString::number(totalDeleted), QString::number(totalErrored))); | ||||||
| 						.arg(QString::number(totalDeleted)) | 		}); | ||||||
| 						.arg(QString::number(totalErrored))); |  | ||||||
| 			}); |  | ||||||
| 	connect(this, &MainWindow::beginIndexSync, indexSyncer, &IndexSyncer::sync); | 	connect(this, &MainWindow::beginIndexSync, indexSyncer, &IndexSyncer::sync); | ||||||
| 	connect(&this->progressDialog, &QProgressDialog::canceled, indexSyncer, &IndexSyncer::cancel); | 	connect(&this->progressDialog, &QProgressDialog::canceled, indexSyncer, &IndexSyncer::cancel); | ||||||
| 	connect(ui->btnSaveSettings, &QPushButton::clicked, this, &MainWindow::saveSettings); | 	connect(ui->btnSaveSettings, &QPushButton::clicked, this, &MainWindow::saveSettings); | ||||||
| @@ -208,9 +210,9 @@ void MainWindow::connectSignals() | |||||||
| 			} | 			} | ||||||
| 		}, | 		}, | ||||||
| 		Qt::QueuedConnection); | 		Qt::QueuedConnection); | ||||||
| 	connect(&ipcPreviewClient, &IPCPreviewClient::previewReceived, this, &MainWindow::previewReceived, | 	connect(&previewCoordinator, &PreviewCoordinator::previewReady, this, &MainWindow::previewReceived, | ||||||
| 			Qt::QueuedConnection); | 			Qt::QueuedConnection); | ||||||
| 	connect(&ipcPreviewClient, &IPCPreviewClient::finished, this, | 	connect(&previewCoordinator, &PreviewCoordinator::completedGeneration, this, | ||||||
| 			[&] | 			[&] | ||||||
| 			{ | 			{ | ||||||
| 				this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->maximum()); | 				this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->maximum()); | ||||||
| @@ -218,22 +220,24 @@ void MainWindow::connectSignals() | |||||||
| 				this->ui->comboPreviewFiles->setEnabled(true); | 				this->ui->comboPreviewFiles->setEnabled(true); | ||||||
| 				ui->txtSearch->setEnabled(true); | 				ui->txtSearch->setEnabled(true); | ||||||
| 			}); | 			}); | ||||||
| 	connect(&ipcPreviewClient, &IPCPreviewClient::error, this, | 	connect(&previewCoordinator, &PreviewCoordinator::error, this, | ||||||
| 			[this](QString msg) | 			[this](QString msg) | ||||||
| 			{ | 			{ | ||||||
| 				qCritical() << msg << Qt::endl; | 				qCritical() << msg << Qt::endl; | ||||||
| 				QMessageBox::critical(this, "IPC error", msg); | 				QMessageBox::critical(this, "IPC error", msg); | ||||||
| 			}); | 			}); | ||||||
|  | 	connect(ui->radioMetadataOnly, &QRadioButton::toggled, this, | ||||||
| 	connect(this, &MainWindow::startIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::startGeneration, | 			[this](bool toggled) | ||||||
| 			Qt::QueuedConnection); | 			{ | ||||||
| 	connect(this, &MainWindow::stopIpcPreviews, &ipcPreviewClient, &IPCPreviewClient::stopGeneration, | 				if(toggled) | ||||||
| 			Qt::QueuedConnection); | 				{ | ||||||
|  | 					this->ui->chkFillContentForContentless->setChecked(false); | ||||||
|  | 				}; | ||||||
|  | 			}); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::exportFailedPaths() | void MainWindow::exportFailedPaths() | ||||||
| { | { | ||||||
|  |  | ||||||
| 	QString filename = | 	QString filename = | ||||||
| 		QString("/tmp/looqs_indexresult_failed_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss")); | 		QString("/tmp/looqs_indexresult_failed_%1").arg(QDateTime::currentDateTime().toString("yyyy-MM-dd_hhmmss")); | ||||||
| 	QFile outFile(filename); | 	QFile outFile(filename); | ||||||
| @@ -266,8 +270,11 @@ void MainWindow::startIndexSync() | |||||||
| 	progressDialog.setValue(0); | 	progressDialog.setValue(0); | ||||||
| 	progressDialog.open(); | 	progressDialog.open(); | ||||||
|  |  | ||||||
| 	indexSyncer->setKeepGoing(true); | 	FileSaverOptions options; | ||||||
| 	indexSyncer->setVerbose(false); | 	options.keepGoing = true; | ||||||
|  | 	options.verbose = false; | ||||||
|  |  | ||||||
|  | 	indexSyncer->setFileSaverOptions(options); | ||||||
| 	indexSyncer->setDryRun(false); | 	indexSyncer->setDryRun(false); | ||||||
| 	indexSyncer->setRemoveDeletedFromIndex(true); | 	indexSyncer->setRemoveDeletedFromIndex(true); | ||||||
|  |  | ||||||
| @@ -286,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; | ||||||
| @@ -295,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); | ||||||
| @@ -311,6 +321,15 @@ void MainWindow::startIndexing() | |||||||
| 	this->indexer->setTargetPaths(paths); | 	this->indexer->setTargetPaths(paths); | ||||||
| 	QString ignorePatterns = ui->txtIgnorePatterns->text(); | 	QString ignorePatterns = ui->txtIgnorePatterns->text(); | ||||||
| 	this->indexer->setIgnorePattern(ignorePatterns.split(";")); | 	this->indexer->setIgnorePattern(ignorePatterns.split(";")); | ||||||
|  |  | ||||||
|  | 	FileSaverOptions options; | ||||||
|  | 	options.fillExistingContentless = | ||||||
|  | 		ui->chkFillContentForContentless->isEnabled() && ui->chkFillContentForContentless->isChecked(); | ||||||
|  | 	options.metadataOnly = ui->radioMetadataOnly->isChecked(); | ||||||
|  | 	options.verbose = false; | ||||||
|  | 	options.keepGoing = true; | ||||||
|  |  | ||||||
|  | 	this->indexer->setFileSaverOptions(options); | ||||||
| 	this->indexer->beginIndexing(); | 	this->indexer->beginIndexing(); | ||||||
| 	QSettings settings; | 	QSettings settings; | ||||||
| 	settings.setValue("indexPaths", pathSettingsValue); | 	settings.setValue("indexPaths", pathSettingsValue); | ||||||
| @@ -333,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) | ||||||
| 	{ | 	{ | ||||||
| @@ -340,7 +361,7 @@ void MainWindow::finishIndexing() | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::comboScaleChanged(int i) | void MainWindow::comboScaleChanged(int /*i*/) | ||||||
| { | { | ||||||
| 	QSettings scaleSetting; | 	QSettings scaleSetting; | ||||||
| 	scaleSetting.setValue("currentScale", ui->comboScale->currentText()); | 	scaleSetting.setValue("currentScale", ui->comboScale->currentText()); | ||||||
| @@ -386,7 +407,8 @@ void MainWindow::processShortcut(int key) | |||||||
| 	{ | 	{ | ||||||
| 		ui->txtSearch->setFocus(); | 		ui->txtSearch->setFocus(); | ||||||
| 		QString currentText = ui->txtSearch->text().trimmed(); | 		QString currentText = ui->txtSearch->text().trimmed(); | ||||||
| 		int index = currentText.lastIndexOf(QRegularExpression("[\\s\\)]")); | 		static QRegularExpression separatorRegex("[\\s\\)]"); | ||||||
|  | 		int index = currentText.lastIndexOf(separatorRegex); | ||||||
| 		if(index != -1) | 		if(index != -1) | ||||||
| 		{ | 		{ | ||||||
| 			bool isFilter = (index == currentText.length() - 1); | 			bool isFilter = (index == currentText.length() - 1); | ||||||
| @@ -632,13 +654,17 @@ void MainWindow::saveSettings() | |||||||
| 	qApp->quit(); | 	qApp->quit(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration) | void MainWindow::previewReceived() | ||||||
| { | { | ||||||
| 	if(previewGeneration < this->currentPreviewGeneration) |  | ||||||
| 	{ |  | ||||||
| 		return; |  | ||||||
| 	} |  | ||||||
| 	this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->value() + 1); | 	this->ui->previewProcessBar->setValue(this->ui->previewProcessBar->value() + 1); | ||||||
|  | 	QBoxLayout *layout = static_cast<QBoxLayout *>(ui->scrollAreaWidgetContents->layout()); | ||||||
|  | 	int index = layout->count(); | ||||||
|  | 	if(index > 0) | ||||||
|  | 	{ | ||||||
|  | 		--index; | ||||||
|  | 	} | ||||||
|  | 	QSharedPointer<PreviewResult> preview = this->previewCoordinator.resultAt(index); | ||||||
|  |  | ||||||
| 	if(!preview.isNull() && preview->hasPreview()) | 	if(!preview.isNull() && preview->hasPreview()) | ||||||
| 	{ | 	{ | ||||||
| 		QString docPath = preview->getDocumentPath(); | 		QString docPath = preview->getDocumentPath(); | ||||||
| @@ -661,8 +687,8 @@ void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned | |||||||
| 		{ | 		{ | ||||||
| 			QFileInfo fileInfo{docPath}; | 			QFileInfo fileInfo{docPath}; | ||||||
| 			QMenu menu("labeRightClick", this); | 			QMenu menu("labeRightClick", this); | ||||||
| 			createSearchResutlMenu(menu, fileInfo); | 			createSearchResultMenu(menu, fileInfo); | ||||||
| 			menu.addAction("Copy page number", | 			menu.addAction("Copy page number", this, | ||||||
| 						   [previewPage] { QGuiApplication::clipboard()->setText(QString::number(previewPage)); }); | 						   [previewPage] { QGuiApplication::clipboard()->setText(QString::number(previewPage)); }); | ||||||
| 			menu.exec(QCursor::pos()); | 			menu.exec(QCursor::pos()); | ||||||
| 		}; | 		}; | ||||||
| @@ -684,24 +710,7 @@ void MainWindow::previewReceived(QSharedPointer<PreviewResult> preview, unsigned | |||||||
|  |  | ||||||
| 		previewWidget->setLayout(previewLayout); | 		previewWidget->setLayout(previewLayout); | ||||||
|  |  | ||||||
| 		QBoxLayout *layout = static_cast<QBoxLayout *>(ui->scrollAreaWidgetContents->layout()); | 		layout->insertWidget(index, previewWidget); | ||||||
| 		int pos = previewOrder[docPath + QString::number(previewPage)]; |  | ||||||
| 		if(pos <= layout->count()) |  | ||||||
| 		{ |  | ||||||
| 			layout->insertWidget(pos, previewWidget); |  | ||||||
| 			for(auto it = previewWidgetOrderCache.constKeyValueBegin(); |  | ||||||
| 				it != previewWidgetOrderCache.constKeyValueEnd(); it++) |  | ||||||
| 			{ |  | ||||||
| 				if(it->first <= layout->count()) |  | ||||||
| 				{ |  | ||||||
| 					layout->insertWidget(it->first, it->second); |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		else |  | ||||||
| 		{ |  | ||||||
| 			previewWidgetOrderCache[pos] = previewWidget; |  | ||||||
| 		} |  | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -818,7 +827,6 @@ void MainWindow::lineEditReturnPressed() | |||||||
|  |  | ||||||
| void MainWindow::handleSearchResults(const QVector<SearchResult> &results) | void MainWindow::handleSearchResults(const QVector<SearchResult> &results) | ||||||
| { | { | ||||||
| 	this->previewableSearchResults.clear(); |  | ||||||
| 	qDeleteAll(ui->scrollAreaWidgetContents->children()); | 	qDeleteAll(ui->scrollAreaWidgetContents->children()); | ||||||
|  |  | ||||||
| 	ui->treeResultsList->clear(); | 	ui->treeResultsList->clear(); | ||||||
| @@ -827,6 +835,8 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results) | |||||||
| 	ui->comboPreviewFiles->setVisible(true); | 	ui->comboPreviewFiles->setVisible(true); | ||||||
| 	ui->lblTotalPreviewPagesCount->setText(""); | 	ui->lblTotalPreviewPagesCount->setText(""); | ||||||
|  |  | ||||||
|  | 	this->previewCoordinator.init(results); | ||||||
|  |  | ||||||
| 	bool hasDeleted = false; | 	bool hasDeleted = false; | ||||||
| 	QHash<QString, bool> seenMap; | 	QHash<QString, bool> seenMap; | ||||||
| 	for(const SearchResult &result : results) | 	for(const SearchResult &result : results) | ||||||
| @@ -847,34 +857,29 @@ void MainWindow::handleSearchResults(const QVector<SearchResult> &results) | |||||||
| 			item->setText(3, this->locale().formattedDataSize(result.fileData.size)); | 			item->setText(3, this->locale().formattedDataSize(result.fileData.size)); | ||||||
| 		} | 		} | ||||||
| 		bool exists = pathInfo.exists(); | 		bool exists = pathInfo.exists(); | ||||||
| 		if(exists) | 		if(!exists) | ||||||
| 		{ |  | ||||||
| 			if(result.wasContentSearch) |  | ||||||
| 			{ |  | ||||||
| 				if(!pathInfo.suffix().contains("htm")) // hack until we can preview them properly... |  | ||||||
| 				{ |  | ||||||
| 					if(PreviewGenerator::get(pathInfo) != nullptr) |  | ||||||
| 					{ |  | ||||||
| 						this->previewableSearchResults.append(result); |  | ||||||
| 						if(!seenMap.contains(result.fileData.absPath)) |  | ||||||
| 						{ |  | ||||||
| 							ui->comboPreviewFiles->addItem(result.fileData.absPath); |  | ||||||
| 						} |  | ||||||
| 					} |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		else |  | ||||||
| 		{ | 		{ | ||||||
| 			hasDeleted = true; | 			hasDeleted = true; | ||||||
| 		} | 		} | ||||||
| 		seenMap[absPath] = true; | 		seenMap[absPath] = true; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	seenMap.clear(); | ||||||
|  | 	for(const SearchResult &result : this->previewCoordinator.getPreviewableSearchResults()) | ||||||
|  | 	{ | ||||||
|  | 		const QString &absPath = result.fileData.absPath; | ||||||
|  | 		if(!seenMap.contains(absPath)) | ||||||
|  | 		{ | ||||||
|  | 			ui->comboPreviewFiles->addItem(absPath); | ||||||
|  | 		} | ||||||
|  | 		seenMap[absPath] = true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	ui->treeResultsList->resizeColumnToContents(0); | 	ui->treeResultsList->resizeColumnToContents(0); | ||||||
| 	ui->treeResultsList->resizeColumnToContents(1); | 	ui->treeResultsList->resizeColumnToContents(1); | ||||||
| 	ui->treeResultsList->resizeColumnToContents(2); | 	ui->treeResultsList->resizeColumnToContents(2); | ||||||
| 	previewDirty = !this->previewableSearchResults.empty(); |  | ||||||
|  | 	previewDirty = this->previewCoordinator.previewableCount() > 0; | ||||||
|  |  | ||||||
| 	ui->spinPreviewPage->setValue(1); | 	ui->spinPreviewPage->setValue(1); | ||||||
|  |  | ||||||
| @@ -883,8 +888,10 @@ 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->previewableSearchResults.count()); | 	statusText += ", previewable: " + QString::number(this->previewCoordinator.previewableCount()); | ||||||
| 	if(hasDeleted) | 	if(hasDeleted) | ||||||
| 	{ | 	{ | ||||||
| 		statusText += " WARNING: Some files are inaccessible. No preview available for those. Index may be out of sync"; | 		statusText += " WARNING: Some files are inaccessible. No preview available for those. Index may be out of sync"; | ||||||
| @@ -901,7 +908,7 @@ int MainWindow::currentSelectedScale() | |||||||
|  |  | ||||||
| void MainWindow::makePreviews(int page) | void MainWindow::makePreviews(int page) | ||||||
| { | { | ||||||
| 	if(this->previewableSearchResults.empty()) | 	if(this->previewCoordinator.previewableCount() == 0) | ||||||
| 	{ | 	{ | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
| @@ -918,11 +925,10 @@ void MainWindow::makePreviews(int page) | |||||||
| 		ui->scrollAreaWidgetContents->setLayout(new QVBoxLayout()); | 		ui->scrollAreaWidgetContents->setLayout(new QVBoxLayout()); | ||||||
| 		ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignCenter); | 		ui->scrollAreaWidgetContents->layout()->setAlignment(Qt::AlignCenter); | ||||||
| 	} | 	} | ||||||
| 	ui->previewProcessBar->setMaximum(this->previewableSearchResults.size()); | 	ui->previewProcessBar->setMaximum(this->previewCoordinator.previewableCount()); | ||||||
| 	processedPdfPreviews = 0; |  | ||||||
|  |  | ||||||
| 	QVector<QString> wordsToHighlight; | 	QVector<QString> wordsToHighlight; | ||||||
| 	QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#"); | 	static QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#"); | ||||||
| 	for(const Token &token : this->contentSearchQuery.getTokens()) | 	for(const Token &token : this->contentSearchQuery.getTokens()) | ||||||
| 	{ | 	{ | ||||||
| 		if(token.type == FILTER_CONTENT_CONTAINS) | 		if(token.type == FILTER_CONTENT_CONTAINS) | ||||||
| @@ -940,12 +946,12 @@ void MainWindow::makePreviews(int page) | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	int end = previewsPerPage; | 	int length = previewsPerPage; | ||||||
| 	int begin = page * previewsPerPage - previewsPerPage; | 	int beginOffset = page * previewsPerPage - previewsPerPage; | ||||||
| 	if(begin < 0) | 	if(beginOffset < 0) | ||||||
| 	{ | 	{ | ||||||
| 		// Should not happen actually | 		// Should not happen actually | ||||||
| 		begin = 0; | 		beginOffset = 0; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	int currentScale = currentSelectedScale(); | 	int currentScale = currentSelectedScale(); | ||||||
| @@ -954,12 +960,8 @@ void MainWindow::makePreviews(int page) | |||||||
| 	renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * (currentScale / 100.); | 	renderConfig.scaleY = QGuiApplication::primaryScreen()->physicalDotsPerInchY() * (currentScale / 100.); | ||||||
| 	renderConfig.wordsToHighlight = wordsToHighlight; | 	renderConfig.wordsToHighlight = wordsToHighlight; | ||||||
|  |  | ||||||
| 	this->previewOrder.clear(); |  | ||||||
| 	this->previewWidgetOrderCache.clear(); |  | ||||||
|  |  | ||||||
| 	int previewPos = 0; |  | ||||||
| 	QVector<RenderTarget> targets; | 	QVector<RenderTarget> targets; | ||||||
| 	for(SearchResult &sr : this->previewableSearchResults) | 	for(const SearchResult &sr : this->previewCoordinator.getPreviewableSearchResults()) | ||||||
| 	{ | 	{ | ||||||
| 		if(ui->comboPreviewFiles->currentIndex() != 0) | 		if(ui->comboPreviewFiles->currentIndex() != 0) | ||||||
| 		{ | 		{ | ||||||
| @@ -971,23 +973,23 @@ void MainWindow::makePreviews(int page) | |||||||
| 		RenderTarget renderTarget; | 		RenderTarget renderTarget; | ||||||
| 		renderTarget.path = sr.fileData.absPath; | 		renderTarget.path = sr.fileData.absPath; | ||||||
| 		renderTarget.page = (int)sr.page; | 		renderTarget.page = (int)sr.page; | ||||||
|  |  | ||||||
| 		targets.append(renderTarget); | 		targets.append(renderTarget); | ||||||
| 		this->previewOrder[renderTarget.path + QString::number(renderTarget.page)] = previewPos++; |  | ||||||
| 	} | 	} | ||||||
| 	int numpages = ceil(static_cast<double>(targets.size()) / previewsPerPage); | 	int numpages = ceil(static_cast<double>(targets.size()) / previewsPerPage); | ||||||
| 	ui->spinPreviewPage->setMaximum(numpages); | 	ui->spinPreviewPage->setMaximum(numpages); | ||||||
| 	targets = targets.mid(begin, end); | 	targets = targets.mid(beginOffset, length); | ||||||
|  |  | ||||||
| 	ui->lblTotalPreviewPagesCount->setText(QString::number(numpages)); | 	ui->lblTotalPreviewPagesCount->setText(QString::number(numpages)); | ||||||
| 	ui->previewProcessBar->setMaximum(targets.count()); | 	ui->previewProcessBar->setMaximum(targets.count()); | ||||||
| 	ui->previewProcessBar->setMinimum(0); | 	ui->previewProcessBar->setMinimum(0); | ||||||
| 	ui->previewProcessBar->setValue(0); | 	ui->previewProcessBar->setValue(0); | ||||||
| 	ui->previewProcessBar->setVisible(this->previewableSearchResults.size() > 0); | 	ui->previewProcessBar->setVisible(this->previewCoordinator.previewableCount() > 0); | ||||||
| 	++this->currentPreviewGeneration; |  | ||||||
| 	this->ui->spinPreviewPage->setEnabled(false); | 	this->ui->spinPreviewPage->setEnabled(false); | ||||||
| 	this->ui->comboPreviewFiles->setEnabled(false); | 	this->ui->comboPreviewFiles->setEnabled(false); | ||||||
| 	this->ui->txtSearch->setEnabled(false); | 	this->ui->txtSearch->setEnabled(false); | ||||||
| 	emit startIpcPreviews(renderConfig, targets); |  | ||||||
|  | 	this->previewCoordinator.startGeneration(renderConfig, targets); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::handleSearchError(QString error) | void MainWindow::handleSearchError(QString error) | ||||||
| @@ -995,27 +997,87 @@ void MainWindow::handleSearchError(QString error) | |||||||
| 	ui->lblSearchResults->setText("Error:" + error); | 	ui->lblSearchResults->setText("Error:" + error); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo) | void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo) | ||||||
| { | { | ||||||
| 	menu.addAction("Copy filename to clipboard", | 	menu.addAction("Copy filename to clipboard", this, | ||||||
| 				   [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); }); | 				   [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.fileName()); }); | ||||||
| 	menu.addAction("Copy full path to clipboard", | 	menu.addAction("Copy full path to clipboard", this, | ||||||
| 				   [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); }); | 				   [&fileInfo] { QGuiApplication::clipboard()->setText(fileInfo.absoluteFilePath()); }); | ||||||
| 	menu.addAction("Open containing folder", [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); }); | 	menu.addAction("Open containing folder", this, [this, &fileInfo] { this->openFile(fileInfo.absolutePath()); }); | ||||||
|  |  | ||||||
|  | 	auto previewables = this->previewCoordinator.getPreviewableSearchResults(); | ||||||
| 	auto result = | 	auto result = | ||||||
| 		std::find_if(this->previewableSearchResults.begin(), this->previewableSearchResults.end(), | 		std::find_if(previewables.begin(), previewables.end(), | ||||||
| 					 [this, &fileInfo](SearchResult &a) { return fileInfo.absoluteFilePath() == a.fileData.absPath; }); | 					 [&fileInfo](SearchResult &a) { return fileInfo.absoluteFilePath() == a.fileData.absPath; }); | ||||||
|  |  | ||||||
| 	if(result != this->previewableSearchResults.end()) | 	if(result != previewables.end()) | ||||||
| 	{ | 	{ | ||||||
| 		menu.addAction("Show previews for this file", | 		menu.addAction("Show previews for this file", this, | ||||||
| 					   [this, &fileInfo] | 					   [this, &fileInfo] | ||||||
| 					   { | 					   { | ||||||
| 						   ui->tabWidget->setCurrentIndex(1); | 						   ui->tabWidget->setCurrentIndex(1); | ||||||
| 						   this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath()); | 						   this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath()); | ||||||
| 					   }); | 					   }); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	QMenu *tagMenu = menu.addMenu("Tag file with: "); | ||||||
|  | 	QVector<QString> allTags = this->dbService->getTags(); | ||||||
|  | 	QHash<QString, bool> fileTags; | ||||||
|  |  | ||||||
|  | 	QString path = fileInfo.absoluteFilePath(); | ||||||
|  |  | ||||||
|  | 	for(const QString &fileTag : this->dbService->getTagsForPath(path)) | ||||||
|  | 	{ | ||||||
|  | 		fileTags[fileTag] = true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for(const QString &tag : allTags) | ||||||
|  | 	{ | ||||||
|  | 		QCheckBox *checkBox = new QCheckBox(tagMenu); | ||||||
|  | 		QWidgetAction *checkableAction = new QWidgetAction(tagMenu); | ||||||
|  | 		checkableAction->setDefaultWidget(checkBox); | ||||||
|  | 		checkBox->setText(tag); | ||||||
|  | 		checkBox->setChecked(fileTags.contains(tag)); | ||||||
|  | 		tagMenu->addAction(checkableAction); | ||||||
|  |  | ||||||
|  | 		connect(checkBox, &QCheckBox::stateChanged, this, | ||||||
|  | 				[this, checkBox, path] | ||||||
|  | 				{ | ||||||
|  | 					QVector<QString> currentTags = this->dbService->getTagsForPath(path); | ||||||
|  | 					QString checkBoxText = checkBox->text(); | ||||||
|  | 					if(checkBox->isChecked()) | ||||||
|  | 					{ | ||||||
|  | 						if(!this->tagManager->addTagsToPath(path, {checkBoxText})) | ||||||
|  | 						{ | ||||||
|  | 							QMessageBox::critical(this, "Error while adding tag", | ||||||
|  | 												  "An error occured while trying to add the tag"); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					else | ||||||
|  | 					{ | ||||||
|  | 						if(!this->tagManager->removeTagsForPath(path, {checkBoxText})) | ||||||
|  | 						{ | ||||||
|  | 							QMessageBox::critical(this, "Error while removing tag", | ||||||
|  | 												  "An error occured while trying to remove the tag"); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 				}); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	tagMenu->addAction("Add new tags", this, | ||||||
|  | 					   [this, path] | ||||||
|  | 					   { | ||||||
|  | 						   bool ok; | ||||||
|  | 						   QString text = | ||||||
|  | 							   QInputDialog::getText(this, tr("Enter new tags"), tr("New tags (comma separated):"), | ||||||
|  | 													 QLineEdit::Normal, "", &ok); | ||||||
|  |  | ||||||
|  | 						   if(ok && !this->tagManager->addTagsToPath(path, text, ',')) | ||||||
|  | 						   { | ||||||
|  | 							   QMessageBox::critical(this, "Error while trying to add tags", | ||||||
|  | 													 "An error occured while trying to add tags"); | ||||||
|  | 						   } | ||||||
|  | 					   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::openDocument(QString path, int num) | void MainWindow::openDocument(QString path, int num) | ||||||
| @@ -1045,7 +1107,7 @@ void MainWindow::openFile(QString path) | |||||||
| 	QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | 	QDesktopServices::openUrl(QUrl::fromLocalFile(path)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int i) | void MainWindow::treeSearchItemActivated(QTreeWidgetItem *item, int /*i*/) | ||||||
| { | { | ||||||
| 	openFile(item->text(1)); | 	openFile(item->text(1)); | ||||||
| } | } | ||||||
| @@ -1059,22 +1121,22 @@ void MainWindow::showSearchResultsContextMenu(const QPoint &point) | |||||||
| 	} | 	} | ||||||
| 	QFileInfo pathinfo(item->text(1)); | 	QFileInfo pathinfo(item->text(1)); | ||||||
| 	QMenu menu("SearchResults", this); | 	QMenu menu("SearchResults", this); | ||||||
| 	createSearchResutlMenu(menu, pathinfo); | 	createSearchResultMenu(menu, pathinfo); | ||||||
| 	menu.exec(QCursor::pos()); | 	menu.exec(QCursor::pos()); | ||||||
| } | } | ||||||
|  |  | ||||||
| MainWindow::~MainWindow() | MainWindow::~MainWindow() | ||||||
| { | { | ||||||
| 	syncerThread.terminate(); | 	syncerThread.terminate(); | ||||||
| 	ipcClientThread.terminate(); |  | ||||||
| 	delete this->indexSyncer; | 	delete this->indexSyncer; | ||||||
| 	delete this->dbService; | 	delete this->dbService; | ||||||
| 	delete this->dbFactory; | 	delete this->dbFactory; | ||||||
| 	delete this->indexer; | 	delete this->indexer; | ||||||
|  | 	delete this->tagManager; | ||||||
| 	delete ui; | 	delete ui; | ||||||
| } | } | ||||||
|  |  | ||||||
| void MainWindow::closeEvent(QCloseEvent *event) | void MainWindow::closeEvent(QCloseEvent * /*event*/) | ||||||
| { | { | ||||||
| 	QStringList list = this->searchHistory.toList(); | 	QStringList list = this->searchHistory.toList(); | ||||||
| 	QSettings settings; | 	QSettings settings; | ||||||
|   | |||||||
| @@ -12,8 +12,9 @@ | |||||||
| #include <QProgressDialog> | #include <QProgressDialog> | ||||||
| #include "../shared/looqsquery.h" | #include "../shared/looqsquery.h" | ||||||
| #include "../shared/indexsyncer.h" | #include "../shared/indexsyncer.h" | ||||||
| #include "ipcpreviewclient.h" | #include "previewcoordinator.h" | ||||||
| #include "indexer.h" | #include "indexer.h" | ||||||
|  | #include "tagmanager.h" | ||||||
| namespace Ui | namespace Ui | ||||||
| { | { | ||||||
| class MainWindow; | class MainWindow; | ||||||
| @@ -27,8 +28,9 @@ class MainWindow : public QMainWindow | |||||||
| 	DatabaseFactory *dbFactory; | 	DatabaseFactory *dbFactory; | ||||||
| 	SqliteDbService *dbService; | 	SqliteDbService *dbService; | ||||||
| 	Ui::MainWindow *ui; | 	Ui::MainWindow *ui; | ||||||
| 	IPCPreviewClient ipcPreviewClient; |  | ||||||
| 	QThread ipcClientThread; | 	PreviewCoordinator previewCoordinator; | ||||||
|  |  | ||||||
| 	QThread syncerThread; | 	QThread syncerThread; | ||||||
| 	Indexer *indexer; | 	Indexer *indexer; | ||||||
| 	IndexSyncer *indexSyncer; | 	IndexSyncer *indexSyncer; | ||||||
| @@ -36,18 +38,15 @@ class MainWindow : public QMainWindow | |||||||
| 	QFileIconProvider iconProvider; | 	QFileIconProvider iconProvider; | ||||||
| 	QSqlDatabase db; | 	QSqlDatabase db; | ||||||
| 	QFutureWatcher<QVector<SearchResult>> searchWatcher; | 	QFutureWatcher<QVector<SearchResult>> searchWatcher; | ||||||
| 	QVector<SearchResult> previewableSearchResults; |  | ||||||
| 	LooqsQuery contentSearchQuery; | 	LooqsQuery contentSearchQuery; | ||||||
| 	QVector<QString> searchHistory; | 	QVector<QString> searchHistory; | ||||||
|  |  | ||||||
|  | 	TagManager *tagManager; | ||||||
|  |  | ||||||
| 	int currentSearchHistoryIndex = 0; | 	int currentSearchHistoryIndex = 0; | ||||||
| 	QString currentSavedSearchText; | 	QString currentSavedSearchText; | ||||||
| 	QHash<QString, int> previewOrder; /* Quick lookup for the order a preview should have */ | 	bool previewDirty = false; | ||||||
| 	QMap<int, QWidget *> | 	int previewsPerPage = 20; | ||||||
| 		previewWidgetOrderCache /* Saves those that arrived out of order to be inserted later at the correct pos */; |  | ||||||
| 	bool previewDirty; |  | ||||||
| 	int previewsPerPage; |  | ||||||
| 	unsigned int processedPdfPreviews; |  | ||||||
| 	unsigned int currentPreviewGeneration = 1; |  | ||||||
|  |  | ||||||
| 	void connectSignals(); | 	void connectSignals(); | ||||||
| 	void makePreviews(int page); | 	void makePreviews(int page); | ||||||
| @@ -56,20 +55,20 @@ class MainWindow : public QMainWindow | |||||||
| 	void keyPressEvent(QKeyEvent *event) override; | 	void keyPressEvent(QKeyEvent *event) override; | ||||||
| 	void handleSearchResults(const QVector<SearchResult> &results); | 	void handleSearchResults(const QVector<SearchResult> &results); | ||||||
| 	void handleSearchError(QString error); | 	void handleSearchError(QString error); | ||||||
| 	void createSearchResutlMenu(QMenu &menu, const QFileInfo &fileInfo); | 	void createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo); | ||||||
| 	void openDocument(QString path, int num); | 	void openDocument(QString path, int num); | ||||||
| 	void openFile(QString path); | 	void openFile(QString path); | ||||||
| 	void initSettingsTabs(); | 	void initSettingsTabs(); | ||||||
| 	int currentSelectedScale(); | 	int currentSelectedScale(); | ||||||
| 	void processShortcut(int key); | 	void processShortcut(int key); | ||||||
| 	bool eventFilter(QObject *object, QEvent *event); | 	bool eventFilter(QObject *object, QEvent *event) override; | ||||||
|  |  | ||||||
|   private slots: |   private slots: | ||||||
| 	void lineEditReturnPressed(); | 	void lineEditReturnPressed(); | ||||||
| 	void treeSearchItemActivated(QTreeWidgetItem *item, int i); | 	void treeSearchItemActivated(QTreeWidgetItem *item, int i); | ||||||
| 	void showSearchResultsContextMenu(const QPoint &point); | 	void showSearchResultsContextMenu(const QPoint &point); | ||||||
| 	void tabChanged(); | 	void tabChanged(); | ||||||
| 	void previewReceived(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration); | 	void previewReceived(); | ||||||
| 	void comboScaleChanged(int i); | 	void comboScaleChanged(int i); | ||||||
| 	void spinPreviewPageValueChanged(int val); | 	void spinPreviewPageValueChanged(int val); | ||||||
| 	void startIndexing(); | 	void startIndexing(); | ||||||
|   | |||||||
| @@ -18,16 +18,13 @@ | |||||||
|     <item> |     <item> | ||||||
|      <widget class="QLineEdit" name="txtSearch"/> |      <widget class="QLineEdit" name="txtSearch"/> | ||||||
|     </item> |     </item> | ||||||
|     <item> |  | ||||||
|      <layout class="QHBoxLayout" name="horizontalLayout_3"/> |  | ||||||
|     </item> |  | ||||||
|     <item> |     <item> | ||||||
|      <widget class="QTabWidget" name="tabWidget"> |      <widget class="QTabWidget" name="tabWidget"> | ||||||
|       <property name="tabPosition"> |       <property name="tabPosition"> | ||||||
|        <enum>QTabWidget::South</enum> |        <enum>QTabWidget::South</enum> | ||||||
|       </property> |       </property> | ||||||
|       <property name="currentIndex"> |       <property name="currentIndex"> | ||||||
|        <number>1</number> |        <number>2</number> | ||||||
|       </property> |       </property> | ||||||
|       <widget class="QWidget" name="resultsTab"> |       <widget class="QWidget" name="resultsTab"> | ||||||
|        <attribute name="title"> |        <attribute name="title"> | ||||||
| @@ -82,7 +79,7 @@ | |||||||
|              <x>0</x> |              <x>0</x> | ||||||
|              <y>0</y> |              <y>0</y> | ||||||
|              <width>1244</width> |              <width>1244</width> | ||||||
|              <height>633</height> |              <height>641</height> | ||||||
|             </rect> |             </rect> | ||||||
|            </property> |            </property> | ||||||
|            <layout class="QHBoxLayout" name="horizontalLayout"/> |            <layout class="QHBoxLayout" name="horizontalLayout"/> | ||||||
| @@ -195,62 +192,6 @@ | |||||||
|        </attribute> |        </attribute> | ||||||
|        <layout class="QGridLayout" name="gridLayout"> |        <layout class="QGridLayout" name="gridLayout"> | ||||||
|         <item row="6" column="0"> |         <item row="6" column="0"> | ||||||
|          <widget class="QLineEdit" name="txtIgnorePatterns"/> |  | ||||||
|         </item> |  | ||||||
|         <item row="11" column="0"> |  | ||||||
|          <widget class="QPushButton" name="btnStartIndexing"> |  | ||||||
|           <property name="text"> |  | ||||||
|            <string>Start indexing</string> |  | ||||||
|           </property> |  | ||||||
|          </widget> |  | ||||||
|         </item> |  | ||||||
|         <item row="1" column="0"> |  | ||||||
|          <widget class="QGroupBox" name="groupBoxPaths"> |  | ||||||
|           <property name="title"> |  | ||||||
|            <string>Add paths to scan</string> |  | ||||||
|           </property> |  | ||||||
|           <layout class="QGridLayout" name="gridLayout_2"> |  | ||||||
|            <item row="1" column="0"> |  | ||||||
|             <widget class="QLineEdit" name="txtPathScanAdd"/> |  | ||||||
|            </item> |  | ||||||
|            <item row="3" column="0" colspan="5"> |  | ||||||
|             <widget class="QListWidget" name="lstPaths"/> |  | ||||||
|            </item> |  | ||||||
|            <item row="1" column="3"> |  | ||||||
|             <widget class="QToolButton" name="btnDeletePath"> |  | ||||||
|              <property name="enabled"> |  | ||||||
|               <bool>false</bool> |  | ||||||
|              </property> |  | ||||||
|              <property name="text"> |  | ||||||
|               <string>Delete</string> |  | ||||||
|              </property> |  | ||||||
|             </widget> |  | ||||||
|            </item> |  | ||||||
|            <item row="1" column="1"> |  | ||||||
|             <widget class="QPushButton" name="btnChoosePath"> |  | ||||||
|              <property name="text"> |  | ||||||
|               <string>...</string> |  | ||||||
|              </property> |  | ||||||
|             </widget> |  | ||||||
|            </item> |  | ||||||
|            <item row="1" column="2"> |  | ||||||
|             <widget class="QPushButton" name="btnAddPath"> |  | ||||||
|              <property name="text"> |  | ||||||
|               <string>Add</string> |  | ||||||
|              </property> |  | ||||||
|             </widget> |  | ||||||
|            </item> |  | ||||||
|           </layout> |  | ||||||
|          </widget> |  | ||||||
|         </item> |  | ||||||
|         <item row="5" column="0"> |  | ||||||
|          <widget class="QLabel" name="label"> |  | ||||||
|           <property name="text"> |  | ||||||
|            <string>Ignore patterns, separated by ';'. Example: *.js;*Downloads*</string> |  | ||||||
|           </property> |  | ||||||
|          </widget> |  | ||||||
|         </item> |  | ||||||
|         <item row="9" column="0"> |  | ||||||
|          <widget class="QGroupBox" name="groupBoxIndexProgress"> |          <widget class="QGroupBox" name="groupBoxIndexProgress"> | ||||||
|           <property name="contextMenuPolicy"> |           <property name="contextMenuPolicy"> | ||||||
|            <enum>Qt::PreventContextMenu</enum> |            <enum>Qt::PreventContextMenu</enum> | ||||||
| @@ -452,6 +393,108 @@ | |||||||
|           </layout> |           </layout> | ||||||
|          </widget> |          </widget> | ||||||
|         </item> |         </item> | ||||||
|  |         <item row="2" column="0"> | ||||||
|  |          <widget class="QGroupBox" name="groupBoxIndexOptions"> | ||||||
|  |           <property name="title"> | ||||||
|  |            <string>Index options</string> | ||||||
|  |           </property> | ||||||
|  |           <layout class="QVBoxLayout" name="verticalLayout_11"> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QLabel" name="label"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Ignore patterns, separated by ';'. Example: *.js;*Downloads*:</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QLineEdit" name="txtIgnorePatterns"/> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="Line" name="line"> | ||||||
|  |              <property name="orientation"> | ||||||
|  |               <enum>Qt::Horizontal</enum> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QRadioButton" name="radioIndexEverything"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Index everything (metadata + file content)</string> | ||||||
|  |              </property> | ||||||
|  |              <property name="checked"> | ||||||
|  |               <bool>true</bool> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QCheckBox" name="chkFillContentForContentless"> | ||||||
|  |              <property name="enabled"> | ||||||
|  |               <bool>true</bool> | ||||||
|  |              </property> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Index content for files previously indexed without content</string> | ||||||
|  |              </property> | ||||||
|  |              <property name="checked"> | ||||||
|  |               <bool>false</bool> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item> | ||||||
|  |             <widget class="QRadioButton" name="radioMetadataOnly"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Index metadata only, don't process content of files</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |           </layout> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item row="8" column="0"> | ||||||
|  |          <widget class="QPushButton" name="btnStartIndexing"> | ||||||
|  |           <property name="text"> | ||||||
|  |            <string>Start indexing</string> | ||||||
|  |           </property> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|  |         <item row="1" column="0"> | ||||||
|  |          <widget class="QGroupBox" name="groupBoxPaths"> | ||||||
|  |           <property name="title"> | ||||||
|  |            <string>Add paths to scan</string> | ||||||
|  |           </property> | ||||||
|  |           <layout class="QGridLayout" name="gridLayout_2"> | ||||||
|  |            <item row="1" column="0"> | ||||||
|  |             <widget class="QLineEdit" name="txtPathScanAdd"/> | ||||||
|  |            </item> | ||||||
|  |            <item row="3" column="0" colspan="5"> | ||||||
|  |             <widget class="QListWidget" name="lstPaths"/> | ||||||
|  |            </item> | ||||||
|  |            <item row="1" column="3"> | ||||||
|  |             <widget class="QToolButton" name="btnDeletePath"> | ||||||
|  |              <property name="enabled"> | ||||||
|  |               <bool>false</bool> | ||||||
|  |              </property> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Delete</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item row="1" column="1"> | ||||||
|  |             <widget class="QPushButton" name="btnChoosePath"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>...</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |            <item row="1" column="2"> | ||||||
|  |             <widget class="QPushButton" name="btnAddPath"> | ||||||
|  |              <property name="text"> | ||||||
|  |               <string>Add</string> | ||||||
|  |              </property> | ||||||
|  |             </widget> | ||||||
|  |            </item> | ||||||
|  |           </layout> | ||||||
|  |          </widget> | ||||||
|  |         </item> | ||||||
|        </layout> |        </layout> | ||||||
|       </widget> |       </widget> | ||||||
|       <widget class="QWidget" name="settingsTab"> |       <widget class="QWidget" name="settingsTab"> | ||||||
| @@ -701,5 +744,22 @@ | |||||||
|  </widget> |  </widget> | ||||||
|  <layoutdefault spacing="6" margin="11"/> |  <layoutdefault spacing="6" margin="11"/> | ||||||
|  <resources/> |  <resources/> | ||||||
|  <connections/> |  <connections> | ||||||
|  |   <connection> | ||||||
|  |    <sender>radioIndexEverything</sender> | ||||||
|  |    <signal>toggled(bool)</signal> | ||||||
|  |    <receiver>chkFillContentForContentless</receiver> | ||||||
|  |    <slot>setEnabled(bool)</slot> | ||||||
|  |    <hints> | ||||||
|  |     <hint type="sourcelabel"> | ||||||
|  |      <x>639</x> | ||||||
|  |      <y>464</y> | ||||||
|  |     </hint> | ||||||
|  |     <hint type="destinationlabel"> | ||||||
|  |      <x>639</x> | ||||||
|  |      <y>497</y> | ||||||
|  |     </hint> | ||||||
|  |    </hints> | ||||||
|  |   </connection> | ||||||
|  |  </connections> | ||||||
| </ui> | </ui> | ||||||
|   | |||||||
							
								
								
									
										97
									
								
								gui/previewcoordinator.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								gui/previewcoordinator.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | |||||||
|  | #include "previewcoordinator.h" | ||||||
|  | #include <QFileInfo> | ||||||
|  |  | ||||||
|  | PreviewCoordinator::PreviewCoordinator() | ||||||
|  | { | ||||||
|  | 	this->ipcPreviewClient.moveToThread(&this->ipcClientThread); | ||||||
|  |  | ||||||
|  | 	connect(&ipcPreviewClient, &IPCPreviewClient::previewReceived, this, &PreviewCoordinator::handleReceivedPreview, | ||||||
|  | 			Qt::QueuedConnection); | ||||||
|  | 	connect(&ipcPreviewClient, &IPCPreviewClient::finished, this, [&] { emit completedGeneration(); }); | ||||||
|  | 	connect(this, &PreviewCoordinator::ipcStartGeneration, &ipcPreviewClient, &IPCPreviewClient::startGeneration, | ||||||
|  | 			Qt::QueuedConnection); | ||||||
|  |  | ||||||
|  | 	this->ipcClientThread.start(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PreviewCoordinator::init(const QVector<SearchResult> &searchResults) | ||||||
|  | { | ||||||
|  | 	this->previewableSearchResults.clear(); | ||||||
|  | 	for(const SearchResult &result : searchResults) | ||||||
|  | 	{ | ||||||
|  | 		if(result.wasContentSearch) | ||||||
|  | 		{ | ||||||
|  | 			QString path = result.fileData.absPath; | ||||||
|  | 			// HACK until we can preview them properly | ||||||
|  | 			if(path.endsWith(".html") || path.endsWith(".htm")) | ||||||
|  | 			{ | ||||||
|  | 				continue; | ||||||
|  | 			} | ||||||
|  | 			QFileInfo info{path}; | ||||||
|  | 			if(info.exists()) | ||||||
|  | 			{ | ||||||
|  | 				this->previewableSearchResults.append(result); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PreviewCoordinator::setSocketPath(QString socketPath) | ||||||
|  | { | ||||||
|  | 	this->socketPath = socketPath; | ||||||
|  | 	this->ipcPreviewClient.setSocketPath(socketPath); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | int PreviewCoordinator::previewableCount() const | ||||||
|  | { | ||||||
|  | 	return this->previewableSearchResults.count(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QSharedPointer<PreviewResult> PreviewCoordinator::resultAt(int index) | ||||||
|  | { | ||||||
|  | 	if(this->previewResults.size() > index) | ||||||
|  | 	{ | ||||||
|  | 		return {this->previewResults[index]}; | ||||||
|  | 	} | ||||||
|  | 	return {nullptr}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const QVector<SearchResult> &PreviewCoordinator::getPreviewableSearchResults() const | ||||||
|  | { | ||||||
|  | 	return this->previewableSearchResults; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PreviewCoordinator::handleReceivedPreview(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration) | ||||||
|  | { | ||||||
|  | 	if(previewGeneration < this->currentPreviewGeneration) | ||||||
|  | 	{ | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	if(!preview.isNull() && preview->hasPreview()) | ||||||
|  | 	{ | ||||||
|  | 		QString docPath = preview->getDocumentPath(); | ||||||
|  | 		auto previewPage = preview->getPage(); | ||||||
|  | 		int pos = previewOrder[docPath + QString::number(previewPage)]; | ||||||
|  | 		this->previewResults[pos] = preview; | ||||||
|  | 		emit previewReady(); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void PreviewCoordinator::startGeneration(RenderConfig config, const QVector<RenderTarget> &targets) | ||||||
|  | { | ||||||
|  | 	++this->currentPreviewGeneration; | ||||||
|  |  | ||||||
|  | 	this->previewOrder.clear(); | ||||||
|  | 	this->previewResults.clear(); | ||||||
|  |  | ||||||
|  | 	this->previewResults.resize(targets.size()); | ||||||
|  | 	this->previewResults.fill(nullptr); | ||||||
|  |  | ||||||
|  | 	int i = 0; | ||||||
|  | 	for(const RenderTarget &target : targets) | ||||||
|  | 	{ | ||||||
|  | 		this->previewOrder[target.path + QString::number(target.page)] = i++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	emit ipcStartGeneration(config, targets); | ||||||
|  | } | ||||||
							
								
								
									
										48
									
								
								gui/previewcoordinator.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								gui/previewcoordinator.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | |||||||
|  | #ifndef PREVIEWCOORDINATOR_H | ||||||
|  | #define PREVIEWCOORDINATOR_H | ||||||
|  | #include <QVector> | ||||||
|  | #include <QObject> | ||||||
|  | #include <QThread> | ||||||
|  | #include "searchresult.h" | ||||||
|  | #include "previewresult.h" | ||||||
|  | #include "ipcpreviewclient.h" | ||||||
|  | #include "rendertarget.h" | ||||||
|  | class PreviewCoordinator : public QObject | ||||||
|  | { | ||||||
|  | 	Q_OBJECT | ||||||
|  |   private: | ||||||
|  | 	QThread ipcClientThread; | ||||||
|  | 	IPCPreviewClient ipcPreviewClient; | ||||||
|  | 	QString socketPath; | ||||||
|  |  | ||||||
|  | 	QVector<QSharedPointer<PreviewResult>> previewResults; | ||||||
|  | 	QVector<SearchResult> previewableSearchResults; | ||||||
|  |  | ||||||
|  | 	unsigned int currentPreviewGeneration = 1; | ||||||
|  |  | ||||||
|  | 	/* Quick lookup table for the order a preview should have */ | ||||||
|  | 	QHash<QString, int> previewOrder; | ||||||
|  |  | ||||||
|  |   public: | ||||||
|  | 	PreviewCoordinator(); | ||||||
|  |  | ||||||
|  | 	void init(const QVector<SearchResult> &searchResults); | ||||||
|  |  | ||||||
|  | 	int previewableCount() const; | ||||||
|  | 	const QVector<SearchResult> &getPreviewableSearchResults() const; | ||||||
|  |  | ||||||
|  | 	QSharedPointer<PreviewResult> resultAt(int index); | ||||||
|  |  | ||||||
|  | 	void setSocketPath(QString socketPath); | ||||||
|  |   public slots: | ||||||
|  | 	void startGeneration(RenderConfig config, const QVector<RenderTarget> &targets); | ||||||
|  | 	void handleReceivedPreview(QSharedPointer<PreviewResult> preview, unsigned int previewGeneration); | ||||||
|  |  | ||||||
|  |   signals: | ||||||
|  | 	void previewReady(); | ||||||
|  | 	void completedGeneration(); | ||||||
|  | 	void error(QString); | ||||||
|  | 	void ipcStartGeneration(RenderConfig config, const QVector<RenderTarget> &targets); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // PREVIEWCOORDINATOR_H | ||||||
| @@ -24,7 +24,7 @@ QSharedPointer<PreviewResult> PreviewGeneratorOdt::generate(RenderConfig config, | |||||||
| 		throw LooqsGeneralException("Error while reading content.xml of " + documentPath); | 		throw LooqsGeneralException("Error while reading content.xml of " + documentPath); | ||||||
| 	} | 	} | ||||||
| 	TagStripperProcessor tsp; | 	TagStripperProcessor tsp; | ||||||
| 	QString content = tsp.process(entireContent).first().content; | 	QString content = tsp.process(entireContent).constFirst().content; | ||||||
|  |  | ||||||
| 	PreviewGeneratorPlainText plainTextGenerator; | 	PreviewGeneratorPlainText plainTextGenerator; | ||||||
| 	result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName())); | 	result->setText(plainTextGenerator.generatePreviewText(content, config, info.fileName())); | ||||||
|   | |||||||
| @@ -20,6 +20,8 @@ Poppler::Document *PreviewGeneratorPdf::document(QString path) | |||||||
| 		return nullptr; | 		return nullptr; | ||||||
| 	} | 	} | ||||||
| 	result->setRenderHint(Poppler::Document::TextAntialiasing); | 	result->setRenderHint(Poppler::Document::TextAntialiasing); | ||||||
|  | 	result->setRenderHint(Poppler::Document::TextHinting); | ||||||
|  | 	result->setRenderHint(Poppler::Document::TextSlightHinting); | ||||||
|  |  | ||||||
| 	locker.relock(); | 	locker.relock(); | ||||||
| 	documentcache.insert(path, result); | 	documentcache.insert(path, result); | ||||||
|   | |||||||
| @@ -103,7 +103,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in, | |||||||
| 		int foundWordsCount = 0; | 		int foundWordsCount = 0; | ||||||
| 		for(QString &word : config.wordsToHighlight) | 		for(QString &word : config.wordsToHighlight) | ||||||
| 		{ | 		{ | ||||||
| 			QRegularExpression searchRegex("\\b" + word + "\\b"); | 			QRegularExpression searchRegex("\\b" + QRegularExpression::escape(word) + "\\b"); | ||||||
| 			bool containsRegex = line.contains(searchRegex); | 			bool containsRegex = line.contains(searchRegex); | ||||||
| 			bool contains = false; | 			bool contains = false; | ||||||
| 			if(!containsRegex) | 			if(!containsRegex) | ||||||
| @@ -195,7 +195,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in, | |||||||
| 				  int totalWordsA = 0; | 				  int totalWordsA = 0; | ||||||
| 				  int differentWordsB = 0; | 				  int differentWordsB = 0; | ||||||
| 				  int totalWordsB = 0; | 				  int totalWordsB = 0; | ||||||
| 				  for(int count : a.wordCountMap.values()) | 				  for(int count : qAsConst(a.wordCountMap)) | ||||||
| 				  { | 				  { | ||||||
| 					  if(count > 0) | 					  if(count > 0) | ||||||
| 					  { | 					  { | ||||||
| @@ -203,7 +203,7 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in, | |||||||
| 					  } | 					  } | ||||||
| 					  totalWordsA += count; | 					  totalWordsA += count; | ||||||
| 				  } | 				  } | ||||||
| 				  for(int count : b.wordCountMap.values()) | 				  for(int count : qAsConst(b.wordCountMap)) | ||||||
| 				  { | 				  { | ||||||
| 					  if(count > 0) | 					  if(count > 0) | ||||||
| 					  { | 					  { | ||||||
| @@ -246,17 +246,21 @@ 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(isTruncated) | 	if(!resultText.isEmpty()) | ||||||
| 	{ | 	{ | ||||||
| 		header += "(truncated) "; | 		if(isTruncated) | ||||||
| 	} | 		{ | ||||||
| 	for(QString &word : config.wordsToHighlight) | 			header += "(truncated) "; | ||||||
| 	{ | 		} | ||||||
| 		header += word + ": " + QString::number(totalWordCountMap[word]) + " "; | 		for(QString &word : config.wordsToHighlight) | ||||||
| 	} | 		{ | ||||||
| 	header += "<hr>"; | 			header += word + ": " + QString::number(totalWordCountMap[word]) + " "; | ||||||
|  | 		} | ||||||
|  | 		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, | ||||||
|   | |||||||
| @@ -30,7 +30,7 @@ QByteArray PreviewResultPdf::serialize() const | |||||||
|  |  | ||||||
| QSharedPointer<PreviewResultPdf> PreviewResultPdf::deserialize(QByteArray &ba) | QSharedPointer<PreviewResultPdf> PreviewResultPdf::deserialize(QByteArray &ba) | ||||||
| { | { | ||||||
| 	PreviewResultPdf *result = new PreviewResultPdf(); | 	QSharedPointer<PreviewResultPdf> result(new PreviewResultPdf()); | ||||||
| 	PreviewResultType type; | 	PreviewResultType type; | ||||||
|  |  | ||||||
| 	QDataStream stream{&ba, QIODevice::ReadOnly}; | 	QDataStream stream{&ba, QIODevice::ReadOnly}; | ||||||
| @@ -40,5 +40,5 @@ QSharedPointer<PreviewResultPdf> PreviewResultPdf::deserialize(QByteArray &ba) | |||||||
| 		throw std::runtime_error("Invalid byte array: Not a pdf preview"); | 		throw std::runtime_error("Invalid byte array: Not a pdf preview"); | ||||||
| 	} | 	} | ||||||
| 	stream >> result->documentPath >> result->page >> result->previewImage; | 	stream >> result->documentPath >> result->page >> result->previewImage; | ||||||
| 	return QSharedPointer<PreviewResultPdf>(result); | 	return result; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -40,7 +40,8 @@ QByteArray PreviewResultPlainText::serialize() const | |||||||
|  |  | ||||||
| QSharedPointer<PreviewResultPlainText> PreviewResultPlainText::deserialize(QByteArray &ba) | QSharedPointer<PreviewResultPlainText> PreviewResultPlainText::deserialize(QByteArray &ba) | ||||||
| { | { | ||||||
| 	PreviewResultPlainText *result = new PreviewResultPlainText(); | 	QSharedPointer<PreviewResultPlainText> result(new PreviewResultPlainText()); | ||||||
|  |  | ||||||
| 	PreviewResultType type; | 	PreviewResultType type; | ||||||
|  |  | ||||||
| 	QDataStream stream{&ba, QIODevice::ReadOnly}; | 	QDataStream stream{&ba, QIODevice::ReadOnly}; | ||||||
| @@ -50,5 +51,5 @@ QSharedPointer<PreviewResultPlainText> PreviewResultPlainText::deserialize(QByte | |||||||
| 		throw std::runtime_error("Invalid byte array: Not a pdf preview"); | 		throw std::runtime_error("Invalid byte array: Not a pdf preview"); | ||||||
| 	} | 	} | ||||||
| 	stream >> result->documentPath >> result->page >> result->text; | 	stream >> result->documentPath >> result->page >> result->text; | ||||||
| 	return QSharedPointer<PreviewResultPlainText>(result); | 	return result; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -38,18 +50,17 @@ SaveFileResult FileSaver::updateFile(QString path) | |||||||
| 	return saveFile(info); | 	return saveFile(info); | ||||||
| } | } | ||||||
|  |  | ||||||
| int FileSaver::addFiles(const QVector<QString> paths, bool keepGoing, bool verbose) | int FileSaver::addFiles(const QVector<QString> paths) | ||||||
| { | { | ||||||
| 	return processFiles(paths, std::bind(&FileSaver::addFile, this, std::placeholders::_1), keepGoing, verbose); | 	return processFiles(paths, std::bind(&FileSaver::addFile, this, std::placeholders::_1)); | ||||||
| } | } | ||||||
|  |  | ||||||
| int FileSaver::updateFiles(const QVector<QString> paths, bool keepGoing, bool verbose) | int FileSaver::updateFiles(const QVector<QString> paths) | ||||||
| { | { | ||||||
| 	return processFiles(paths, std::bind(&FileSaver::updateFile, this, std::placeholders::_1), keepGoing, verbose); | 	return processFiles(paths, std::bind(&FileSaver::updateFile, this, std::placeholders::_1)); | ||||||
| } | } | ||||||
|  |  | ||||||
| int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc, | int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc) | ||||||
| 							bool keepGoing, bool verbose) |  | ||||||
| { | { | ||||||
| 	std::atomic<bool> terminate{false}; | 	std::atomic<bool> terminate{false}; | ||||||
| 	std::atomic<int> processedCount{0}; | 	std::atomic<int> processedCount{0}; | ||||||
| @@ -60,7 +71,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile | |||||||
| 								  { | 								  { | ||||||
| 									  return; | 									  return; | ||||||
| 								  } | 								  } | ||||||
| 								  if(verbose) | 								  if(this->fileSaverOptions.verbose) | ||||||
| 								  { | 								  { | ||||||
| 									  Logger::info() << "Processing " << path << Qt::endl; | 									  Logger::info() << "Processing " << path << Qt::endl; | ||||||
| 								  } | 								  } | ||||||
| @@ -68,7 +79,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile | |||||||
| 								  if(result == DBFAIL || result == PROCESSFAIL) | 								  if(result == DBFAIL || result == PROCESSFAIL) | ||||||
| 								  { | 								  { | ||||||
| 									  Logger::error() << "Failed to process " << path << Qt::endl; | 									  Logger::error() << "Failed to process " << path << Qt::endl; | ||||||
| 									  if(!keepGoing) | 									  if(!this->fileSaverOptions.keepGoing) | ||||||
| 									  { | 									  { | ||||||
| 										  terminate = true; | 										  terminate = true; | ||||||
| 									  } | 									  } | ||||||
| @@ -76,7 +87,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile | |||||||
| 								  else | 								  else | ||||||
| 								  { | 								  { | ||||||
| 									  ++processedCount; | 									  ++processedCount; | ||||||
| 									  if(verbose) | 									  if(this->fileSaverOptions.verbose) | ||||||
| 									  { | 									  { | ||||||
| 										  if(result == SKIPPED) | 										  if(result == SKIPPED) | ||||||
| 										  { | 										  { | ||||||
| @@ -120,11 +131,26 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo) | |||||||
| 		{ | 		{ | ||||||
| 			if(canonicalPath.startsWith(excludedPath)) | 			if(canonicalPath.startsWith(excludedPath)) | ||||||
| 			{ | 			{ | ||||||
|  | 				if(this->fileSaverOptions.verbose) | ||||||
|  | 				{ | ||||||
|  | 					Logger::info() << "Skipped due to excluded path"; | ||||||
|  | 				} | ||||||
| 				return SKIPPED; | 				return SKIPPED; | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if(fileInfo.size() > 0) | 		bool mustFillContent = this->fileSaverOptions.fillExistingContentless; | ||||||
|  | 		if(!mustFillContent) | ||||||
|  | 		{ | ||||||
|  | 			mustFillContent = !this->fileSaverOptions.metadataOnly; | ||||||
|  | 			if(mustFillContent) | ||||||
|  | 			{ | ||||||
|  | 				auto filetype = this->dbService->queryFileType(fileInfo.absolutePath()); | ||||||
|  | 				mustFillContent = !filetype.has_value() || filetype.value() == 'c'; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if(fileInfo.size() > 0 && mustFillContent) | ||||||
| 		{ | 		{ | ||||||
| 			QProcess process; | 			QProcess process; | ||||||
| 			QStringList args; | 			QStringList args; | ||||||
| @@ -159,7 +185,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo) | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	SaveFileResult result = this->dbService->saveFile(fileInfo, pageData); | 	SaveFileResult result = this->dbService->saveFile(fileInfo, pageData, this->fileSaverOptions.metadataOnly); | ||||||
| 	if(result == OK && processorReturnCode == OK_WASEMPTY) | 	if(result == OK && processorReturnCode == OK_WASEMPTY) | ||||||
| 	{ | 	{ | ||||||
| 		return OK_WASEMPTY; | 		return OK_WASEMPTY; | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| #define FILESAVER_H | #define FILESAVER_H | ||||||
| #include <QSqlDatabase> | #include <QSqlDatabase> | ||||||
| #include <QFileInfo> | #include <QFileInfo> | ||||||
|  | #include "filesaveroptions.h" | ||||||
| #include "pagedata.h" | #include "pagedata.h" | ||||||
| #include "filedata.h" | #include "filedata.h" | ||||||
| #include "sqlitedbservice.h" | #include "sqlitedbservice.h" | ||||||
| @@ -11,16 +12,21 @@ class FileSaver | |||||||
|   private: |   private: | ||||||
| 	SqliteDbService *dbService; | 	SqliteDbService *dbService; | ||||||
| 	QStringList excludedPaths = Common::excludedPaths(); | 	QStringList excludedPaths = Common::excludedPaths(); | ||||||
|  | 	FileSaverOptions fileSaverOptions; | ||||||
|  |  | ||||||
|   public: |   public: | ||||||
| 	FileSaver(SqliteDbService &dbService); | 	FileSaver(SqliteDbService &dbService); | ||||||
| 	SaveFileResult addFile(QString path); | 	SaveFileResult addFile(QString path); | ||||||
| 	SaveFileResult updateFile(QString path); | 	SaveFileResult updateFile(QString path); | ||||||
| 	SaveFileResult saveFile(const QFileInfo &fileInfo); | 	SaveFileResult saveFile(const QFileInfo &fileInfo); | ||||||
| 	int processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc, | 	int processFiles(const QVector<QString> paths, std::function<SaveFileResult(QString path)> saverFunc); | ||||||
| 					 bool keepGoing, bool verbose); | 	int addFiles(const QVector<QString> paths); | ||||||
| 	int addFiles(const QVector<QString> paths, bool keepGoing, bool verbose); | 	int updateFiles(const QVector<QString> paths); | ||||||
| 	int updateFiles(const QVector<QString> paths, bool keepGoing, bool verbose); |  | ||||||
|  | 	void setFileSaverOptions(FileSaverOptions options) | ||||||
|  | 	{ | ||||||
|  | 		this->fileSaverOptions = options; | ||||||
|  | 	} | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // FILESAVER_H | #endif // FILESAVER_H | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								shared/filesaveroptions.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								shared/filesaveroptions.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | #ifndef FILESAVEROPTIONS_H | ||||||
|  | #define FILESAVEROPTIONS_H | ||||||
|  |  | ||||||
|  | class FileSaverOptions | ||||||
|  | { | ||||||
|  |   public: | ||||||
|  | 	bool verbose = false; | ||||||
|  | 	bool keepGoing = false; | ||||||
|  | 	bool metadataOnly = false; | ||||||
|  | 	/* Whether those previously explicitly without content should be filled */ | ||||||
|  | 	bool fillExistingContentless = false; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // FILESAVEROPTIONS_H | ||||||
| @@ -12,6 +12,7 @@ FileScanWorker::FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &qu | |||||||
| void FileScanWorker::run() | void FileScanWorker::run() | ||||||
| { | { | ||||||
| 	FileSaver saver{*this->dbService}; | 	FileSaver saver{*this->dbService}; | ||||||
|  | 	saver.setFileSaverOptions(this->fileSaverOptions); | ||||||
| 	auto paths = queue->dequeue(batchsize); | 	auto paths = queue->dequeue(batchsize); | ||||||
| 	for(QString &path : paths) | 	for(QString &path : paths) | ||||||
| 	{ | 	{ | ||||||
| @@ -20,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 | ||||||
| 		{ | 		{ | ||||||
| @@ -34,3 +42,8 @@ void FileScanWorker::run() | |||||||
| 	} | 	} | ||||||
| 	emit finished(); | 	emit finished(); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void FileScanWorker::setFileSaverOptions(FileSaverOptions options) | ||||||
|  | { | ||||||
|  | 	this->fileSaverOptions = options; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -15,12 +15,14 @@ class FileScanWorker : public QObject, public QRunnable | |||||||
|   protected: |   protected: | ||||||
| 	SqliteDbService *dbService; | 	SqliteDbService *dbService; | ||||||
| 	ConcurrentQueue<QString> *queue; | 	ConcurrentQueue<QString> *queue; | ||||||
|  | 	FileSaverOptions fileSaverOptions; | ||||||
| 	int batchsize; | 	int batchsize; | ||||||
| 	std::atomic<bool> *stopToken; | 	std::atomic<bool> *stopToken; | ||||||
|  |  | ||||||
|   public: |   public: | ||||||
| 	FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize, std::atomic<bool> &stopToken); | 	FileScanWorker(SqliteDbService &db, ConcurrentQueue<QString> &queue, int batchsize, std::atomic<bool> &stopToken); | ||||||
| 	void run() override; | 	void run() override; | ||||||
|  | 	void setFileSaverOptions(FileSaverOptions options); | ||||||
|   signals: |   signals: | ||||||
| 	void result(FileScanResult); | 	void result(FileScanResult); | ||||||
| 	void finished(); | 	void finished(); | ||||||
|   | |||||||
| @@ -73,16 +73,6 @@ void Indexer::setTargetPaths(QVector<QString> pathsToScan) | |||||||
| 	this->pathsToScan = pathsToScan; | 	this->pathsToScan = pathsToScan; | ||||||
| } | } | ||||||
|  |  | ||||||
| void Indexer::setVerbose(bool verbose) |  | ||||||
| { |  | ||||||
| 	this->verbose = verbose; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Indexer::setKeepGoing(bool keepGoing) |  | ||||||
| { |  | ||||||
| 	this->keepGoing = keepGoing; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void Indexer::requestCancellation() | void Indexer::requestCancellation() | ||||||
| { | { | ||||||
| 	this->dirScanner->cancel(); | 	this->dirScanner->cancel(); | ||||||
| @@ -108,6 +98,7 @@ void Indexer::launchWorker(ConcurrentQueue<QString> &queue, int batchsize) | |||||||
| 	FileScanWorker *runnable = new FileScanWorker(*this->db, queue, batchsize, this->workerCancellationToken); | 	FileScanWorker *runnable = new FileScanWorker(*this->db, queue, batchsize, this->workerCancellationToken); | ||||||
| 	connect(runnable, &FileScanWorker::result, this, &Indexer::processFileScanResult); | 	connect(runnable, &FileScanWorker::result, this, &Indexer::processFileScanResult); | ||||||
| 	connect(runnable, &FileScanWorker::finished, this, &Indexer::processFinishedWorker); | 	connect(runnable, &FileScanWorker::finished, this, &Indexer::processFinishedWorker); | ||||||
|  | 	runnable->setFileSaverOptions(this->fileSaverOptions); | ||||||
| 	++this->runningWorkers; | 	++this->runningWorkers; | ||||||
| 	QThreadPool::globalInstance()->start(runnable); | 	QThreadPool::globalInstance()->start(runnable); | ||||||
| } | } | ||||||
| @@ -120,24 +111,6 @@ void Indexer::dirScanProgress(int current, int total) | |||||||
|  |  | ||||||
| void Indexer::processFileScanResult(FileScanResult result) | void Indexer::processFileScanResult(FileScanResult result) | ||||||
| { | { | ||||||
| 	if(isErrorSaveFileResult(result.second)) |  | ||||||
| 	{ |  | ||||||
| 		this->currentIndexResult.results.append(result); |  | ||||||
| 		if(!keepGoing) |  | ||||||
| 		{ |  | ||||||
| 			this->requestCancellation(); |  | ||||||
| 			emit finished(); |  | ||||||
| 			return; |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	else |  | ||||||
| 	{ |  | ||||||
| 		if(verbose) |  | ||||||
| 		{ |  | ||||||
| 			this->currentIndexResult.results.append(result); |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	/* TODO: OK_WASEMPTY might need a special list */ | 	/* TODO: OK_WASEMPTY might need a special list */ | ||||||
| 	if(result.second == OK || result.second == OK_WASEMPTY) | 	if(result.second == OK || result.second == OK_WASEMPTY) | ||||||
| 	{ | 	{ | ||||||
| @@ -152,6 +125,24 @@ void Indexer::processFileScanResult(FileScanResult result) | |||||||
| 		++this->currentIndexResult.erroredPaths; | 		++this->currentIndexResult.erroredPaths; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if(isErrorSaveFileResult(result.second)) | ||||||
|  | 	{ | ||||||
|  | 		this->currentIndexResult.results.append(result); | ||||||
|  | 		if(!this->fileSaverOptions.keepGoing) | ||||||
|  | 		{ | ||||||
|  | 			this->requestCancellation(); | ||||||
|  | 			emit finished(); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	else | ||||||
|  | 	{ | ||||||
|  | 		if(this->fileSaverOptions.verbose) | ||||||
|  | 		{ | ||||||
|  | 			this->currentIndexResult.results.append(result); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	QTime currentTime = QTime::currentTime(); | 	QTime currentTime = QTime::currentTime(); | ||||||
| 	if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10) | 	if(currentScanProcessedCount++ == progressReportThreshold || this->lastProgressReportTime.secsTo(currentTime) >= 10) | ||||||
| 	{ | 	{ | ||||||
| @@ -175,3 +166,13 @@ void Indexer::processFinishedWorker() | |||||||
| 		emit finished(); | 		emit finished(); | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void Indexer::setFileSaverOptions(FileSaverOptions options) | ||||||
|  | { | ||||||
|  | 	this->fileSaverOptions = options; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Indexer::setProgressReportThreshold(int threshold) | ||||||
|  | { | ||||||
|  | 	this->progressReportThreshold = threshold; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -52,8 +52,7 @@ class Indexer : public QObject | |||||||
| { | { | ||||||
| 	Q_OBJECT | 	Q_OBJECT | ||||||
|   protected: |   protected: | ||||||
| 	bool verbose = false; | 	FileSaverOptions fileSaverOptions; | ||||||
| 	bool keepGoing = true; |  | ||||||
| 	SqliteDbService *db; | 	SqliteDbService *db; | ||||||
|  |  | ||||||
| 	int progressReportThreshold = 50; | 	int progressReportThreshold = 50; | ||||||
| @@ -80,8 +79,10 @@ class Indexer : public QObject | |||||||
| 	void beginIndexing(); | 	void beginIndexing(); | ||||||
| 	void setIgnorePattern(QStringList ignorePattern); | 	void setIgnorePattern(QStringList ignorePattern); | ||||||
| 	void setTargetPaths(QVector<QString> pathsToScan); | 	void setTargetPaths(QVector<QString> pathsToScan); | ||||||
| 	void setVerbose(bool verbose); |  | ||||||
| 	void setKeepGoing(bool keepGoing); | 	void setFileSaverOptions(FileSaverOptions options); | ||||||
|  |  | ||||||
|  | 	void setProgressReportThreshold(int threshold); | ||||||
|  |  | ||||||
| 	void requestCancellation(); | 	void requestCancellation(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,21 +7,16 @@ IndexSyncer::IndexSyncer(SqliteDbService &dbService) | |||||||
| 	this->dbService = &dbService; | 	this->dbService = &dbService; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | void IndexSyncer::setFileSaverOptions(FileSaverOptions options) | ||||||
|  | { | ||||||
|  | 	fileSaverOptions = options; | ||||||
|  | } | ||||||
|  |  | ||||||
| void IndexSyncer::setDryRun(bool dryRun) | void IndexSyncer::setDryRun(bool dryRun) | ||||||
| { | { | ||||||
| 	this->dryRun = dryRun; | 	this->dryRun = dryRun; | ||||||
| } | } | ||||||
|  |  | ||||||
| void IndexSyncer::setVerbose(bool verbose) |  | ||||||
| { |  | ||||||
| 	this->verbose = verbose; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void IndexSyncer::setKeepGoing(bool keepGoing) |  | ||||||
| { |  | ||||||
| 	this->keepGoing = keepGoing; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void IndexSyncer::setRemoveDeletedFromIndex(bool removeDeletedFromIndex) | void IndexSyncer::setRemoveDeletedFromIndex(bool removeDeletedFromIndex) | ||||||
| { | { | ||||||
| 	this->removeDeletedFromIndex = removeDeletedFromIndex; | 	this->removeDeletedFromIndex = removeDeletedFromIndex; | ||||||
| @@ -35,7 +30,7 @@ void IndexSyncer::setPattern(QString pattern) | |||||||
| void IndexSyncer::sync() | void IndexSyncer::sync() | ||||||
| { | { | ||||||
| 	this->stopToken.store(false, std::memory_order_relaxed); | 	this->stopToken.store(false, std::memory_order_relaxed); | ||||||
| 	FileSaver saver(*this->dbService); |  | ||||||
| 	QVector<FileData> files; | 	QVector<FileData> files; | ||||||
| 	int offset = 0; | 	int offset = 0; | ||||||
| 	int limit = 10000; | 	int limit = 10000; | ||||||
| @@ -87,7 +82,7 @@ void IndexSyncer::sync() | |||||||
| 						if(!this->dbService->deleteFile(fileData.absPath)) | 						if(!this->dbService->deleteFile(fileData.absPath)) | ||||||
| 						{ | 						{ | ||||||
| 							emit error("Error: Failed to delete " + fileData.absPath + " from the index"); | 							emit error("Error: Failed to delete " + fileData.absPath + " from the index"); | ||||||
| 							if(!this->keepGoing) | 							if(!this->fileSaverOptions.keepGoing) | ||||||
| 							{ | 							{ | ||||||
| 								emit finished(totalUpdatesFilesCount, totalDeletedFilesCount, totalErroredFilesCount); | 								emit finished(totalUpdatesFilesCount, totalDeletedFilesCount, totalErroredFilesCount); | ||||||
| 								return; | 								return; | ||||||
| @@ -104,13 +99,15 @@ void IndexSyncer::sync() | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		unsigned int updatedFilesCount = saver.updateFiles(filePathsToUpdate, keepGoing, verbose); | 		FileSaver saver(*this->dbService); | ||||||
|  | 		saver.setFileSaverOptions(this->fileSaverOptions); | ||||||
|  | 		unsigned int updatedFilesCount = saver.updateFiles(filePathsToUpdate); | ||||||
| 		unsigned int shouldHaveUpdatedCount = static_cast<unsigned int>(filePathsToUpdate.size()); | 		unsigned int shouldHaveUpdatedCount = static_cast<unsigned int>(filePathsToUpdate.size()); | ||||||
| 		if(updatedFilesCount != shouldHaveUpdatedCount) | 		if(updatedFilesCount != shouldHaveUpdatedCount) | ||||||
| 		{ | 		{ | ||||||
|  |  | ||||||
| 			totalErroredFilesCount += (shouldHaveUpdatedCount - updatedFilesCount); | 			totalErroredFilesCount += (shouldHaveUpdatedCount - updatedFilesCount); | ||||||
| 			if(!keepGoing) | 			if(!this->fileSaverOptions.keepGoing) | ||||||
| 			{ | 			{ | ||||||
| 				QString errorMsg = QString("Failed to update all files selected for updating in this batch. Updated") + | 				QString errorMsg = QString("Failed to update all files selected for updating in this batch. Updated") + | ||||||
| 								   updatedFilesCount + "out of" + shouldHaveUpdatedCount + "selected for updating"; | 								   updatedFilesCount + "out of" + shouldHaveUpdatedCount + "selected for updating"; | ||||||
|   | |||||||
| @@ -1,16 +1,15 @@ | |||||||
| #ifndef INDEXSYNCER_H | #ifndef INDEXSYNCER_H | ||||||
| #define INDEXSYNCER_H | #define INDEXSYNCER_H | ||||||
| #include "sqlitedbservice.h" | #include "sqlitedbservice.h" | ||||||
|  | #include "filesaveroptions.h" | ||||||
| class IndexSyncer : public QObject | class IndexSyncer : public QObject | ||||||
| { | { | ||||||
| 	Q_OBJECT | 	Q_OBJECT | ||||||
|   private: |   private: | ||||||
| 	SqliteDbService *dbService = nullptr; | 	SqliteDbService *dbService = nullptr; | ||||||
| 	bool keepGoing = true; | 	FileSaverOptions fileSaverOptions; | ||||||
| 	bool removeDeletedFromIndex = true; | 	bool removeDeletedFromIndex = true; | ||||||
| 	bool dryRun = false; | 	bool dryRun = false; | ||||||
| 	bool verbose = false; |  | ||||||
| 	QString pattern; | 	QString pattern; | ||||||
|  |  | ||||||
| 	std::atomic<bool> stopToken{false}; | 	std::atomic<bool> stopToken{false}; | ||||||
| @@ -18,12 +17,12 @@ class IndexSyncer : public QObject | |||||||
|   public: |   public: | ||||||
| 	IndexSyncer(SqliteDbService &dbService); | 	IndexSyncer(SqliteDbService &dbService); | ||||||
|  |  | ||||||
|  | 	void setFileSaverOptions(FileSaverOptions options); | ||||||
|  |  | ||||||
|   public slots: |   public slots: | ||||||
| 	void sync(); | 	void sync(); | ||||||
| 	void cancel(); | 	void cancel(); | ||||||
| 	void setDryRun(bool dryRun); | 	void setDryRun(bool dryRun); | ||||||
| 	void setVerbose(bool verbose); |  | ||||||
| 	void setKeepGoing(bool keepGoing); |  | ||||||
| 	void setRemoveDeletedFromIndex(bool removeDeletedFromIndex); | 	void setRemoveDeletedFromIndex(bool removeDeletedFromIndex); | ||||||
| 	void setPattern(QString pattern); | 	void setPattern(QString pattern); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ | |||||||
| #include <optional> | #include <optional> | ||||||
| #include <algorithm> | #include <algorithm> | ||||||
| #include "looqsquery.h" | #include "looqsquery.h" | ||||||
|  | #include "looqsgeneralexception.h" | ||||||
|  |  | ||||||
| const QVector<Token> &LooqsQuery::getTokens() const | const QVector<Token> &LooqsQuery::getTokens() const | ||||||
| { | { | ||||||
| @@ -180,8 +181,9 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b | |||||||
|  |  | ||||||
| 	QStringList loneWords; | 	QStringList loneWords; | ||||||
| 	LooqsQuery result; | 	LooqsQuery result; | ||||||
| 	QRegularExpression rx("((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([^\\s])+)|(?<boolean>AND|OR)" | 	static QRegularExpression rx( | ||||||
| 						  "|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>[^\\s]+))"); | 		"((?<filtername>(\\.|\\w)+):(?<args>\\((?<innerargs>[^\\)]+)\\)|([^\\s])+)|(?<boolean>AND|OR)" | ||||||
|  | 		"|(?<negation>!)|(?<bracket>\\(|\\))|(?<loneword>[^\\s]+))"); | ||||||
| 	QRegularExpressionMatchIterator i = rx.globalMatch(expression); | 	QRegularExpressionMatchIterator i = rx.globalMatch(expression); | ||||||
| 	auto previousWasBool = [&result] { return !result.tokens.empty() && ((result.tokens.last().type & BOOL) == BOOL); }; | 	auto previousWasBool = [&result] { return !result.tokens.empty() && ((result.tokens.last().type & BOOL) == BOOL); }; | ||||||
| 	auto previousWas = [&result](TokenType t) { return !result.tokens.empty() && (result.tokens.last().type == t); }; | 	auto previousWas = [&result](TokenType t) { return !result.tokens.empty() && (result.tokens.last().type == t); }; | ||||||
| @@ -283,6 +285,10 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b | |||||||
| 			{ | 			{ | ||||||
| 				tokenType = FILTER_CONTENT_PAGE; | 				tokenType = FILTER_CONTENT_PAGE; | ||||||
| 			} | 			} | ||||||
|  | 			else if(filtername == "t" || filtername == "tag") | ||||||
|  | 			{ | ||||||
|  | 				tokenType = FILTER_TAG_ASSIGNED; | ||||||
|  | 			} | ||||||
| 			// TODO: given this is not really a "filter", this feels slightly misplaced here | 			// TODO: given this is not really a "filter", this feels slightly misplaced here | ||||||
| 			else if(filtername == "sort") | 			else if(filtername == "sort") | ||||||
| 			{ | 			{ | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ | |||||||
| #define LOOQSQUERY_H | #define LOOQSQUERY_H | ||||||
| #include <QString> | #include <QString> | ||||||
| #include <QVector> | #include <QVector> | ||||||
| #include "looqsgeneralexception.h" |  | ||||||
| #include "token.h" | #include "token.h" | ||||||
| /* Fields that can be queried or sorted */ | /* Fields that can be queried or sorted */ | ||||||
| enum QueryField | enum QueryField | ||||||
| @@ -46,7 +45,7 @@ class LooqsQuery | |||||||
| 	void addToken(Token t); | 	void addToken(Token t); | ||||||
| 	void updateTokensMask() | 	void updateTokensMask() | ||||||
| 	{ | 	{ | ||||||
| 		for(const Token &t : tokens) | 		for(const Token &t : qAsConst(tokens)) | ||||||
| 		{ | 		{ | ||||||
| 			this->tokensMask |= t.type; | 			this->tokensMask |= t.type; | ||||||
| 		} | 		} | ||||||
| @@ -92,14 +91,6 @@ class LooqsQuery | |||||||
| 		this->sortConditions = sortConditions; | 		this->sortConditions = sortConditions; | ||||||
| 		updateTokensMask(); | 		updateTokensMask(); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	LooqsQuery(const LooqsQuery &o) |  | ||||||
| 	{ |  | ||||||
| 		this->tokens = o.tokens; |  | ||||||
| 		this->sortConditions = o.sortConditions; |  | ||||||
| 		this->tokensMask = o.tokensMask; |  | ||||||
| 		this->limit = o.limit; |  | ||||||
| 	} |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // LOOQSQUERY_H | #endif // LOOQSQUERY_H | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								shared/migrations/5.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								shared/migrations/5.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | CREATE TABLE tag(id integer PRIMARY KEY, name varchar(128) UNIQUE); | ||||||
|  | CREATE TABLE filetag(fileid integer, tagid integer); | ||||||
|  | CREATE INDEX filetag_fileid ON filetag(fileid); | ||||||
|  | CREATE INDEX tag_id ON tag(id); | ||||||
|  | CREATE INDEX file_path ON file ( path ); | ||||||
|  | UPDATE file SET filetype='c' WHERE filetype='f'; | ||||||
| @@ -4,5 +4,6 @@ | |||||||
|         <file>2.sql</file> |         <file>2.sql</file> | ||||||
|         <file>3.sql</file> |         <file>3.sql</file> | ||||||
|         <file>4.sql</file> |         <file>4.sql</file> | ||||||
|  |         <file>5.sql</file> | ||||||
|     </qresource> |     </qresource> | ||||||
| </RCC> | </RCC> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ class NothingProcessor : public Processor | |||||||
| 	NothingProcessor(); | 	NothingProcessor(); | ||||||
|  |  | ||||||
|   public: |   public: | ||||||
| 	QVector<PageData> process(const QByteArray &data) const override | 	QVector<PageData> process(const QByteArray & /*data*/) const override | ||||||
| 	{ | 	{ | ||||||
| 		return {}; | 		return {}; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| #include "odtprocessor.h" | #include "odtprocessor.h" | ||||||
| #include "tagstripperprocessor.h" | #include "tagstripperprocessor.h" | ||||||
|  |  | ||||||
| QVector<PageData> OdtProcessor::process(const QByteArray &data) const | QVector<PageData> OdtProcessor::process(const QByteArray & /*data*/) const | ||||||
| { | { | ||||||
| 	throw LooqsGeneralException("Not implemented yet"); | 	throw LooqsGeneralException("Not implemented yet"); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,3 @@ | |||||||
| #include "paralleldirscanner.h" |  | ||||||
|  |  | ||||||
| #include <QRunnable> | #include <QRunnable> | ||||||
| #include <QMutex> | #include <QMutex> | ||||||
| #include <QDirIterator> | #include <QDirIterator> | ||||||
| @@ -7,7 +5,7 @@ | |||||||
| #include <QThreadPool> | #include <QThreadPool> | ||||||
| #include <functional> | #include <functional> | ||||||
| #include "dirscanworker.h" | #include "dirscanworker.h" | ||||||
| #include "logger.h" | #include "paralleldirscanner.h" | ||||||
|  |  | ||||||
| ParallelDirScanner::ParallelDirScanner() | ParallelDirScanner::ParallelDirScanner() | ||||||
| { | { | ||||||
|   | |||||||
| @@ -60,6 +60,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 \ | ||||||
| @@ -74,6 +75,7 @@ HEADERS += sqlitesearch.h \ | |||||||
|     encodingdetector.h \ |     encodingdetector.h \ | ||||||
|     filedata.h \ |     filedata.h \ | ||||||
|     filesaver.h \ |     filesaver.h \ | ||||||
|  |     filesaveroptions.h \ | ||||||
|     filescanworker.h \ |     filescanworker.h \ | ||||||
|     indexer.h \ |     indexer.h \ | ||||||
|     indexsyncer.h \ |     indexsyncer.h \ | ||||||
| @@ -92,6 +94,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) | ||||||
| { | { | ||||||
| @@ -29,20 +14,29 @@ QVector<SearchResult> SqliteDbService::search(const LooqsQuery &query) | |||||||
| 	return searcher.search(query); | 	return searcher.search(query); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool SqliteDbService::fileExistsInDatabase(QString path) | 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 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()) | 	if(!query.next()) | ||||||
| 	{ | 	{ | ||||||
| 		return false; | 		return {}; | ||||||
| 	} | 	} | ||||||
| 	return query.value(0).toBool(); | 	return query.value(0).toChar(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SqliteDbService::fileExistsInDatabase(QString path) | ||||||
|  | { | ||||||
|  | 	return execBool("SELECT 1 FROM file WHERE path = ?", {path}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime) | ||||||
|  | { | ||||||
|  | 	return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ?", {path, mtime}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SqliteDbService::fileExistsInDatabase(QString path, qint64 mtime, QChar fileType) | ||||||
|  | { | ||||||
|  | 	return execBool("SELECT 1 FROM file WHERE path = ? AND mtime = ? AND filetype = ?", {path, mtime, fileType}); | ||||||
| } | } | ||||||
|  |  | ||||||
| SqliteDbService::SqliteDbService(DatabaseFactory &dbFactory) | SqliteDbService::SqliteDbService(DatabaseFactory &dbFactory) | ||||||
| @@ -110,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; | ||||||
| @@ -148,11 +253,40 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid | |||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData) | QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args) | ||||||
|  | { | ||||||
|  | 	auto query = QSqlQuery(dbFactory->forCurrentThread()); | ||||||
|  | 	query.prepare(querystr); | ||||||
|  | 	for(const QVariant &v : args) | ||||||
|  | 	{ | ||||||
|  | 		query.addBindValue(v); | ||||||
|  | 	} | ||||||
|  | 	if(!query.exec()) | ||||||
|  | 	{ | ||||||
|  | 		throw LooqsGeneralException("Error while exec(): " + query.lastError().text() + " for query: " + querystr); | ||||||
|  | 	} | ||||||
|  | 	return query; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool SqliteDbService::execBool(QString querystr, std::initializer_list<QVariant> args) | ||||||
|  | { | ||||||
|  | 	auto query = exec(querystr, args); | ||||||
|  | 	if(!query.next()) | ||||||
|  | 	{ | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | 	return query.value(0).toBool(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly) | ||||||
| { | { | ||||||
| 	QString absPath = fileInfo.absoluteFilePath(); | 	QString absPath = fileInfo.absoluteFilePath(); | ||||||
| 	auto mtime = fileInfo.lastModified().toSecsSinceEpoch(); | 	auto mtime = fileInfo.lastModified().toSecsSinceEpoch(); | ||||||
| 	QChar fileType = fileInfo.isDir() ? 'd' : 'f'; | 	QChar fileType = fileInfo.isDir() ? 'd' : 'c'; | ||||||
|  | 	if(pathsOnly) | ||||||
|  | 	{ | ||||||
|  | 		fileType = 'f'; | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	QSqlDatabase db = dbFactory->forCurrentThread(); | 	QSqlDatabase db = dbFactory->forCurrentThread(); | ||||||
| 	QSqlQuery delQuery(db); | 	QSqlQuery delQuery(db); | ||||||
| @@ -186,19 +320,23 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> & | |||||||
| 		return DBFAIL; | 		return DBFAIL; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	int lastid = inserterQuery.lastInsertId().toInt(); | 	if(!pathsOnly) | ||||||
| 	if(!insertToFTS(false, db, lastid, pageData)) |  | ||||||
| 	{ | 	{ | ||||||
| 		db.rollback(); | 		int lastid = inserterQuery.lastInsertId().toInt(); | ||||||
| 		Logger::error() << "Failed to insert data to FTS index " << Qt::endl; | 		if(!insertToFTS(false, db, lastid, pageData)) | ||||||
| 		return DBFAIL; | 		{ | ||||||
| 	} | 			db.rollback(); | ||||||
| 	if(!insertToFTS(true, db, lastid, pageData)) | 			Logger::error() << "Failed to insert data to FTS index " << Qt::endl; | ||||||
| 	{ | 			return DBFAIL; | ||||||
| 		db.rollback(); | 		} | ||||||
| 		Logger::error() << "Failed to insert data to FTS index " << Qt::endl; | 		if(!insertToFTS(true, db, lastid, pageData)) | ||||||
| 		return DBFAIL; | 		{ | ||||||
|  | 			db.rollback(); | ||||||
|  | 			Logger::error() << "Failed to insert data to FTS index " << Qt::endl; | ||||||
|  | 			return DBFAIL; | ||||||
|  | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if(!db.commit()) | 	if(!db.commit()) | ||||||
| 	{ | 	{ | ||||||
| 		db.rollback(); | 		db.rollback(); | ||||||
| @@ -207,3 +345,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; | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,6 +1,8 @@ | |||||||
| #ifndef SQLITEDBSERVICE_H | #ifndef SQLITEDBSERVICE_H | ||||||
| #define SQLITEDBSERVICE_H | #define SQLITEDBSERVICE_H | ||||||
| #include <QFileInfo> | #include <QFileInfo> | ||||||
|  | #include <optional> | ||||||
|  |  | ||||||
| #include "databasefactory.h" | #include "databasefactory.h" | ||||||
| #include "utils.h" | #include "utils.h" | ||||||
| #include "pagedata.h" | #include "pagedata.h" | ||||||
| @@ -15,14 +17,31 @@ 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); | 	SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly); | ||||||
| 	unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit); |  | ||||||
| 	bool deleteFile(QString path); | 	bool deleteFile(QString path); | ||||||
| 	bool fileExistsInDatabase(QString path); | 	bool fileExistsInDatabase(QString path); | ||||||
| 	bool fileExistsInDatabase(QString path, qint64 mtime); | 	bool fileExistsInDatabase(QString path, qint64 mtime); | ||||||
|  | 	bool fileExistsInDatabase(QString path, qint64 mtime, QChar filetype); | ||||||
|  | 	unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit); | ||||||
|  |  | ||||||
|  | 	bool addTag(QString tag, QString path); | ||||||
|  | 	bool addTag(QString tag, const QVector<QString> &paths); | ||||||
|  | 	QVector<QString> getTags(); | ||||||
|  | 	QVector<QString> getTagsForPath(QString path); | ||||||
|  | 	QVector<QString> getPathsForTag(QString path); | ||||||
|  | 	bool setTags(QString path, const QSet<QString> &tags); | ||||||
|  | 	bool removePathsForTag(QString tag, const QVector<QString> &paths); | ||||||
|  | 	bool deleteTag(QString tag); | ||||||
|  |  | ||||||
| 	QVector<SearchResult> search(const LooqsQuery &query); | 	QVector<SearchResult> search(const LooqsQuery &query); | ||||||
|  |  | ||||||
|  | 	std::optional<QChar> queryFileType(QString absPath); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #endif // SQLITEDBSERVICE_H | #endif // SQLITEDBSERVICE_H | ||||||
|   | |||||||
| @@ -69,7 +69,7 @@ QString SqliteSearch::createSortSql(const QVector<SortCondition> sortConditions) | |||||||
| QString SqliteSearch::escapeFtsArgument(QString ftsArg) | QString SqliteSearch::escapeFtsArgument(QString ftsArg) | ||||||
| { | { | ||||||
| 	QString result; | 	QString result; | ||||||
| 	QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#"); | 	static QRegularExpression extractor(R"#("([^"]*)"|([^\s]+))#"); | ||||||
| 	QRegularExpressionMatchIterator i = extractor.globalMatch(ftsArg); | 	QRegularExpressionMatchIterator i = extractor.globalMatch(ftsArg); | ||||||
| 	while(i.hasNext()) | 	while(i.hasNext()) | ||||||
| 	{ | 	{ | ||||||
| @@ -143,13 +143,17 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token) | |||||||
| 	{ | 	{ | ||||||
| 		return {" fts MATCH ? ", {escapeFtsArgument(value)}}; | 		return {" fts MATCH ? ", {escapeFtsArgument(value)}}; | ||||||
| 	} | 	} | ||||||
|  | 	if(token.type == FILTER_TAG_ASSIGNED) | ||||||
|  | 	{ | ||||||
|  | 		return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ", | ||||||
|  | 				{value.toLower()}}; | ||||||
|  | 	} | ||||||
| 	throw LooqsGeneralException("Unknown token passed (should not happen)"); | 	throw LooqsGeneralException("Unknown token passed (should not happen)"); | ||||||
| } | } | ||||||
|  |  | ||||||
| QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query) | QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query) | ||||||
| { | { | ||||||
| 	QString whereSql; | 	QString whereSql; | ||||||
| 	QString joinSql; |  | ||||||
| 	QVector<QString> bindValues; | 	QVector<QString> bindValues; | ||||||
| 	bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; | 	bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; | ||||||
| 	if(query.getTokens().isEmpty()) | 	if(query.getTokens().isEmpty()) | ||||||
| @@ -157,7 +161,6 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query) | |||||||
| 		throw LooqsGeneralException("Nothing to search for supplied"); | 		throw LooqsGeneralException("Nothing to search for supplied"); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	bool ftsAlreadyJoined = false; |  | ||||||
| 	auto tokens = query.getTokens(); | 	auto tokens = query.getTokens(); | ||||||
| 	for(const Token &token : tokens) | 	for(const Token &token : tokens) | ||||||
| 	{ | 	{ | ||||||
| @@ -181,18 +184,18 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query) | |||||||
| 		} | 		} | ||||||
| 		QString whereSqlTrigram = whereSql; | 		QString whereSqlTrigram = whereSql; | ||||||
| 		whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty... | 		whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty... | ||||||
| 		prepSql = | 		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.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON file.id = " | 				  "file.id = " | ||||||
| 			"content.fileid " + | 				  "content.fileid " + | ||||||
| 			"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + whereSqlTrigram + | 				  "INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + | ||||||
| 			" ) " + sortSql; | 				  whereSqlTrigram + " ) " + sortSql; | ||||||
| 		++bindIterations; | 		++bindIterations; | ||||||
| 	} | 	} | ||||||
| 	else | 	else | ||||||
|   | |||||||
							
								
								
									
										66
									
								
								shared/tagmanager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								shared/tagmanager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | |||||||
|  | #include "tagmanager.h" | ||||||
|  |  | ||||||
|  | TagManager::TagManager(SqliteDbService &dbService) | ||||||
|  | { | ||||||
|  | 	this->dbService = &dbService; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TagManager::addTagsToPath(QString path, const QSet<QString> &tags) | ||||||
|  | { | ||||||
|  | 	QVector<QString> currentTags = this->dbService->getTagsForPath(path); | ||||||
|  | 	for(const QString &tag : tags) | ||||||
|  | 	{ | ||||||
|  | 		currentTags.append(tag.toLower()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	QSet<QString> newTags{currentTags.begin(), currentTags.end()}; | ||||||
|  | 	return this->dbService->setTags(path, newTags); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TagManager::removeTagsForPath(QString path, const QSet<QString> &tags) | ||||||
|  | { | ||||||
|  | 	QVector<QString> currentTags = this->dbService->getTagsForPath(path); | ||||||
|  | 	for(const QString &tag : tags) | ||||||
|  | 	{ | ||||||
|  | 		currentTags.removeAll(tag); | ||||||
|  | 	} | ||||||
|  | 	QSet<QString> newTags{currentTags.begin(), currentTags.end()}; | ||||||
|  | 	return this->dbService->setTags(path, newTags); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TagManager::removePathsForTag(QString tag, const QVector<QString> &paths) | ||||||
|  | { | ||||||
|  | 	return this->dbService->removePathsForTag(tag, paths); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TagManager::deleteTag(QString tag) | ||||||
|  | { | ||||||
|  | 	return this->dbService->deleteTag(tag); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QVector<QString> TagManager::getTags(QString path) | ||||||
|  | { | ||||||
|  | 	return this->dbService->getTagsForPath(path); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QVector<QString> TagManager::getTags() | ||||||
|  | { | ||||||
|  | 	return this->dbService->getTags(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | QVector<QString> TagManager::getPaths(QString tag) | ||||||
|  | { | ||||||
|  | 	return this->dbService->getPathsForTag(tag); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TagManager::addTagsToPath(QString path, QString tagstring, QChar delim) | ||||||
|  | { | ||||||
|  | 	auto splitted = tagstring.split(delim); | ||||||
|  |  | ||||||
|  | 	return addTagsToPath(path, QSet<QString>{splitted.begin(), splitted.end()}); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | bool TagManager::addPathsToTag(QString tag, const QVector<QString> &paths) | ||||||
|  | { | ||||||
|  | 	return this->dbService->addTag(tag, paths); | ||||||
|  | } | ||||||
							
								
								
									
										28
									
								
								shared/tagmanager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								shared/tagmanager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | |||||||
|  | #ifndef TAGMANAGER_H | ||||||
|  | #define TAGMANAGER_H | ||||||
|  | #include "sqlitedbservice.h" | ||||||
|  |  | ||||||
|  | class TagManager | ||||||
|  | { | ||||||
|  |   private: | ||||||
|  | 	SqliteDbService *dbService = nullptr; | ||||||
|  | 	bool ensurePathOkay(QString inpath); | ||||||
|  |  | ||||||
|  |   public: | ||||||
|  | 	TagManager(SqliteDbService &dbService); | ||||||
|  |  | ||||||
|  | 	bool addTagsToPath(QString path, const QSet<QString> &tags); | ||||||
|  | 	bool addTagsToPath(QString path, QString tagstring, QChar delim); | ||||||
|  |  | ||||||
|  | 	bool addPathsToTag(QString tag, const QVector<QString> &paths); | ||||||
|  | 	bool removeTagsForPath(QString path, const QSet<QString> &tags); | ||||||
|  |  | ||||||
|  | 	bool removePathsForTag(QString tag, const QVector<QString> &paths); | ||||||
|  | 	bool deleteTag(QString tag); | ||||||
|  |  | ||||||
|  | 	QVector<QString> getTags(QString path); | ||||||
|  | 	QVector<QString> getTags(); | ||||||
|  | 	QVector<QString> getPaths(QString tag); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // TAGMANAGER_H | ||||||
| @@ -19,7 +19,8 @@ enum TokenType | |||||||
| 	FILTER_PATH_SIZE, | 	FILTER_PATH_SIZE, | ||||||
| 	FILTER_PATH_ENDS, | 	FILTER_PATH_ENDS, | ||||||
| 	FILTER_PATH_STARTS, | 	FILTER_PATH_STARTS, | ||||||
| 	FILTER_CONTENT = 512, | 	FILTER_TAG_ASSIGNED, | ||||||
|  | 	FILTER_CONTENT = 512, /* Everything below here is content search (except LIMIT) */ | ||||||
| 	FILTER_CONTENT_CONTAINS, | 	FILTER_CONTENT_CONTAINS, | ||||||
| 	FILTER_CONTENT_PAGE, | 	FILTER_CONTENT_PAGE, | ||||||
| 	LIMIT = 1024 | 	LIMIT = 1024 | ||||||
|   | |||||||
 Submodule submodules/exile.h updated: e711a1d53a...44b9a17bec
									
								
							
		Reference in New Issue
	
	Block a user