1 Revīzijas

Autors SHA1 Ziņojums Datums
d23dadbe49 cli: CommandAdd: Add verbose (-v) 2023-05-01 23:56:25 +02:00
37 mainīti faili ar 104 papildinājumiem un 529 dzēšanām

Parādīt failu

@ -1,24 +1,5 @@
# looqs: Release notes # looqs: Release notes
## 2023-05-07 - v0.9
Highlights: Tag support. Also begin new index mode to only index metadata (currently only path + file size, more to come).
Note: Upgrading can take some time as new column indexes will be added
CHANGES:
- gui: Improve font rendering in previews
- gui: Allow indexing only metadata
- gui: Allow adding content for files which only had metadata indexed before
- gui: Allow assigning tags by right clicking on paths
- cli: "add" command: Implement --verbose (-v)
- cli: "add" command: Implement --no-content and --fill-content
- cli: Add "tag" command which allows managing tags for paths.
- search: Add "tag:()", "t:()" filters
- Minor improvements and refactorings under the hood
- Add packages: Ubuntu 23.04.
## 2022-11-19 - v0.8.1 ## 2022-11-19 - v0.8.1
CHANGES: CHANGES:

Parādīt failu

@ -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: 2023-05-07, v0.9 Latest version: 2022-11-19, v0.8.1
Please keep in mind: looqs is still at an early stage and may exhibit some weirdness and contain bugs. 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 libqt5sql5-sqlite libpoppler-qt5-dev libuchardet-dev libquazip5-dev sudo apt install build-essential qtbase5-dev libpoppler-qt5-dev libuchardet-dev libquazip5-dev
qmake qmake
make make
``` ```
@ -97,9 +97,7 @@ The GUI is located in `gui/looqs-gui`, the binary for the CLI is in `cli/looqs`
## Packages ## Packages
At this point, looqs is not in any official distro package repo, but I maintain some packages. At this point, looqs is not in any official distro package repo, but I maintain some packages.
### Ubuntu 22.04, 22.10
### Ubuntu 23.04, 22.10, 22.04
Latest release can be installed using apt from the repo. Latest release can be installed using apt from the repo.
``` ```
# First, obtain key, assume it's trusted. # First, obtain key, assume it's trusted.
@ -110,8 +108,6 @@ echo "deb [arch=amd64 signed-by=/usr/share/keyrings/repo.quitesimple.org.gpg] ht
sudo apt-get update sudo apt-get update
sudo apt-get install looqs sudo apt-get install looqs
``` ```
### Gentoo (EXPERIMENTAL)
Available in this overlay: https://github.com/quitesimpleorg/quitesimple-overlay
### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL) ### Prebuilt tarball (distro-agnostic) (EXPERIMENTAL)
looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is looqs is also distributed as a tarball containing prebuilt binaries and its library dependencies. The tarball is
@ -138,7 +134,7 @@ An AppImage may accompany the tarball in the future.
### Other distros ### Other distros
I appreciate help for others distros. If you create a package, let me know! I'll probably add a package for voidlinux at some point and maybe will provide a Gentoo ebuild. However, I would appreciate help for others distros. If you create a package, let me know!
### Signature verification ### Signature verification

Parādīt failu

@ -165,8 +165,6 @@ A number of search filters are available.
| path.begins:(term) | pb:(term) | Filters path beginning with the specified term | | path.begins:(term) | pb:(term) | Filters path beginning with the specified term |
| contains:(terms) | c:(terms) | Full-text search, also understands quotes | | contains:(terms) | c:(terms) | Full-text search, also understands quotes |
| limit:(integer) | - | Limits the number of results. The default is 1000. Say "limit:0" to see all results | | limit:(integer) | - | Limits the number of results. The default is 1000. Say "limit:0" to see all results |
| tag:(tagname) | t:(tagname) | Filter for files that have been tagged with the corresponding tag |
Filters can be combined. The booleans AND and OR are supported. Negations can be applied too, except for c:(). Negations are specified with "!". Filters can be combined. The booleans AND and OR are supported. Negations can be applied too, except for c:(). Negations are specified with "!".
The AND boolean is implicit and thus entering it strictly optional. The AND boolean is implicit and thus entering it strictly optional.
@ -179,5 +177,11 @@ Examples:
|p:(notes) (pe:(odt) OR pe:(docx))          |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|Equivalent to c:("invoice") p:("Downloads")| |c:("invoice") Downloads|This query is equivalent to c:("invoice") p:("Downloads")|
|p:(Downloads) invoice|Equivalent to c:("invoice") p:("Downloads")|

Parādīt failu

@ -46,8 +46,6 @@ packagesExist(quazip1-qt5) {
} }
INCLUDEPATH += $$PWD/../shared INCLUDEPATH += $$PWD/../shared
INCLUDEPATH += /usr/include/poppler/qt5/
DEPENDPATH += $$PWD/../shared DEPENDPATH += $$PWD/../shared
win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a win32-g++:CONFIG(release, debug|release): PRE_TARGETDEPS += $$OUT_PWD/../shared/release/libshared.a

Parādīt failu

@ -41,13 +41,13 @@ int CommandAdd::handle(QStringList arguments)
{ {
QCommandLineParser parser; QCommandLineParser parser;
parser.addOptions({{{"c", "continue"}, parser.addOptions({{{"c", "continue"},
"Continue adding files, don't exit on first error. Exit code will be 0. If this option is not " "Continue adding files, don't exit on first error. If this option is not given, looqs will "
"given, looqs will " "exit asap, but it's possible that a few files will still be processed. "
"exit asap, but it's possible that a few files will still be processed."
"Set -t 1 to avoid this behavior, but processing will be slower. "}, "Set -t 1 to avoid this behavior, but processing will be slower. "},
{{"n", "no-content"}, "Only add paths to database. Do not index content"}, {{"n", "no-content"}, "Only add paths to database. Do not index content"},
{{"v", "verbose"}, "Print paths of files being processed"}, {{"v", "verbose"}, "Print paths of files being processed"},
{{"f", "fill-content"}, "Index content for files previously indexed with -n"}, {{"f", "fill-content"}, "Index content for files previously indexed with -n"},
{"tags", "Comma-separated list of tags to assign"},
{{"t", "threads"}, "Number of threads to use.", "threads"}}); {{"t", "threads"}, "Number of threads to use.", "threads"}});
parser.addHelpOption(); parser.addHelpOption();
parser.addPositionalArgument("add", "Add paths to the index", parser.addPositionalArgument("add", "Add paths to the index",
@ -111,9 +111,10 @@ int CommandAdd::handle(QStringList arguments)
{ {
IndexResult indexResult = indexer->getResult(); IndexResult indexResult = indexer->getResult();
int newlyAdded = indexResult.results.count() - currentResult.results.count(); int newlyAdded = indexResult.results.count() - currentResult.results.count();
int newOffset = 0;
if(newlyAdded > 0) if(newlyAdded > 0)
{ {
int newOffset = indexResult.results.count() - newlyAdded; newOffset = indexResult.results.count() - newlyAdded;
for(int i = newOffset; i < indexResult.results.count(); i++) for(int i = newOffset; i < indexResult.results.count(); i++)
{ {
auto result = indexResult.results.at(i); auto result = indexResult.results.at(i);

Parādīt failu

@ -3,39 +3,14 @@
#include "logger.h" #include "logger.h"
#include "tagmanager.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) int CommandTag::handle(QStringList arguments)
{ {
QCommandLineParser parser; QCommandLineParser parser;
parser.addPositionalArgument("add", "Adds a tag to a file", parser.addPositionalArgument("add", "Adds a tag to a file",
"add [tag] [paths...]. Adds the tag to the specified paths"); "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("remove", "Removes a file associated to tag", "remove [tag] [file]");
parser.addPositionalArgument("delete", "Deletes a tag", "delete [tag]"); parser.addPositionalArgument("delete", "Deletes a tag", "delete [tag]");
parser.addPositionalArgument("list", "Lists paths associated with a tag, or all tags", "list [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.addHelpOption();
parser.parse(arguments); parser.parse(arguments);
@ -46,8 +21,9 @@ int CommandTag::handle(QStringList arguments)
parser.showHelp(EXIT_FAILURE); parser.showHelp(EXIT_FAILURE);
return EXIT_FAILURE; return EXIT_FAILURE;
} }
TagManager tagManager{*this->dbService};
QString cmd = args[0]; QString cmd = args[0];
qDebug() << cmd;
if(cmd == "add") if(cmd == "add")
{ {
if(args.length() < 3) if(args.length() < 3)
@ -57,14 +33,28 @@ int CommandTag::handle(QStringList arguments)
return EXIT_FAILURE; return EXIT_FAILURE;
} }
QString tag = args[1]; QString tag = args[1];
QVector<QString> paths = args.mid(2).toVector(); auto paths = args.mid(2).toVector();
for(int i = 0; i < paths.size(); i++)
QVector<QString> absolutePaths;
if(!ensureAbsolutePaths(paths, absolutePaths))
{ {
return EXIT_FAILURE; QFileInfo info{paths[i]};
if(!info.exists())
{
Logger::error() << "Can't add tag for file " + info.absoluteFilePath() + " because it does not exist"
<< Qt::endl;
return EXIT_FAILURE;
}
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 EXIT_FAILURE;
}
paths[i] = absolutePath;
} }
bool result = tagManager.addPathsToTag(tag, absolutePaths);
TagManager tagManager{*this->dbService};
bool result = tagManager.addPathsToTag(tag, paths);
if(!result) if(!result)
{ {
Logger::error() << "Failed to assign tags" << Qt::endl; Logger::error() << "Failed to assign tags" << Qt::endl;
@ -72,82 +62,6 @@ int CommandTag::handle(QStringList arguments)
} }
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
if(cmd == "list")
{
QString tag; return 0;
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;
} }

Parādīt failu

@ -4,9 +4,6 @@
class CommandTag : public Command class CommandTag : public Command
{ {
protected:
bool ensureAbsolutePaths(const QVector<QString> &paths, QVector<QString> &absolutePaths);
public: public:
using Command::Command; using Command::Command;

Parādīt failu

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

Parādīt failu

@ -246,21 +246,17 @@ QString PreviewGeneratorPlainText::generateLineBasedPreviewText(QTextStream &in,
totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second; totalWordCountMap[it->first] = totalWordCountMap.value(it->first, 0) + it->second;
} }
} }
if(!resultText.isEmpty()) if(isTruncated)
{ {
if(isTruncated) header += "(truncated) ";
{
header += "(truncated) ";
}
for(QString &word : config.wordsToHighlight)
{
header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
}
header += "<hr>";
resultText = header + resultText;
} }
return resultText; for(QString &word : config.wordsToHighlight)
{
header += word + ": " + QString::number(totalWordCountMap[word]) + " ";
}
header += "<hr>";
return header + resultText;
} }
QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath, QSharedPointer<PreviewResult> PreviewGeneratorPlainText::generate(RenderConfig config, QString documentPath,

Parādīt failu

@ -24,9 +24,7 @@ QString DefaultTextProcessor::processText(const QByteArray &data) const
return {}; return {};
} }
DocumentProcessResult DefaultTextProcessor::process(const QByteArray &data) const QVector<PageData> DefaultTextProcessor::process(const QByteArray &data) const
{ {
DocumentProcessResult result; return {{0, processText(data)}};
result.pages.append({0, processText(data)});
return result;
} }

Parādīt failu

@ -11,7 +11,7 @@ class DefaultTextProcessor : public Processor
public: public:
DefaultTextProcessor(); DefaultTextProcessor();
QString processText(const QByteArray &data) const; QString processText(const QByteArray &data) const;
DocumentProcessResult process(const QByteArray &data) const override; QVector<PageData> process(const QByteArray &data) const override;
}; };
#endif // DEFAULTTEXTPROCESSOR_H #endif // DEFAULTTEXTPROCESSOR_H

Parādīt failu

@ -1,31 +0,0 @@
#include "documentoutlineentry.h"
DocumentOutlineEntry::DocumentOutlineEntry()
{
}
QDataStream &operator<<(QDataStream &out, const DocumentOutlineEntry &pd)
{
out << pd.text << pd.type << pd.destinationPage;
out << pd.children.size();
for(const DocumentOutlineEntry &entry : pd.children)
{
out << entry;
}
return out;
}
QDataStream &operator>>(QDataStream &in, DocumentOutlineEntry &pd)
{
in >> pd.text >> pd.type >> pd.destinationPage;
int numChildren;
in >> numChildren;
for(int i = 0; i < numChildren; i++)
{
DocumentOutlineEntry entry;
in >> entry;
pd.children.append(entry);
}
return in;
}

Parādīt failu

@ -1,29 +0,0 @@
#ifndef DOCUMENTOUTLINEENTRY_H
#define DOCUMENTOUTLINEENTRY_H
#include <QMetaType>
#include <QDataStream>
#include <QString>
enum OutlineDestinationType
{
OUTLINE_DESTINATION_TYPE_NONE,
OUTLINE_DESTINATION_TYPE_PAGE
/* In the future, links, or #anchors are possible */
};
class DocumentOutlineEntry
{
public:
DocumentOutlineEntry();
QVector<DocumentOutlineEntry> children;
OutlineDestinationType type;
QString text;
unsigned int destinationPage;
};
Q_DECLARE_METATYPE(DocumentOutlineEntry);
QDataStream &operator<<(QDataStream &out, const DocumentOutlineEntry &pd);
QDataStream &operator>>(QDataStream &in, DocumentOutlineEntry &pd);
#endif // DOCUMENTOUTLINEENTRY_H

Parādīt failu

@ -1,39 +0,0 @@
#include "documentprocessresult.h"
QDataStream &operator<<(QDataStream &out, const DocumentProcessResult &pd)
{
out << pd.pages.size();
out << pd.outlines.size();
for(const PageData &pd : pd.pages)
{
out << pd;
}
for(const DocumentOutlineEntry &outline : pd.outlines)
{
out << outline;
}
return out;
}
QDataStream &operator>>(QDataStream &in, DocumentProcessResult &pd)
{
int numPages, numOutlines;
in >> numPages;
in >> numOutlines;
for(int i = 0; i < numPages; i++)
{
PageData data;
in >> data;
pd.pages.append(data);
}
for(int i = 0; i < numOutlines; i++)
{
DocumentOutlineEntry outline;
in >> outline;
pd.outlines.append(outline);
}
return in;
}

Parādīt failu

@ -1,17 +0,0 @@
#ifndef DOCUMENTPROCESSRESULT_H
#define DOCUMENTPROCESSRESULT_H
#include <pagedata.h>
#include <documentoutlineentry.h>
class DocumentProcessResult
{
public:
QVector<PageData> pages;
QVector<DocumentOutlineEntry> outlines;
};
Q_DECLARE_METATYPE(DocumentProcessResult);
QDataStream &operator<<(QDataStream &out, const DocumentProcessResult &pd);
QDataStream &operator>>(QDataStream &in, DocumentProcessResult &pd);
#endif // DOCUMENTPROCESSRESULT_H

Parādīt failu

@ -110,7 +110,7 @@ int FileSaver::processFiles(const QVector<QString> paths, std::function<SaveFile
SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo) SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
{ {
DocumentProcessResult processResult; QVector<PageData> pageData;
QString canonicalPath = fileInfo.canonicalFilePath(); QString canonicalPath = fileInfo.canonicalFilePath();
int processorReturnCode = -1; int processorReturnCode = -1;
@ -169,10 +169,11 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
* finishes. * finishes.
*/ */
QDataStream in(process.readAllStandardOutput()); QDataStream in(process.readAllStandardOutput());
while(!in.atEnd())
if(!in.atEnd())
{ {
in >> processResult; PageData pd;
in >> pd;
pageData.append(pd);
} }
processorReturnCode = process.exitCode(); processorReturnCode = process.exitCode();
if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY) if(processorReturnCode != OK && processorReturnCode != OK_WASEMPTY)
@ -184,7 +185,7 @@ SaveFileResult FileSaver::saveFile(const QFileInfo &fileInfo)
} }
} }
} }
SaveFileResult result = this->dbService->saveFile(fileInfo, processResult, this->fileSaverOptions.metadataOnly); SaveFileResult result = this->dbService->saveFile(fileInfo, pageData, this->fileSaverOptions.metadataOnly);
if(result == OK && processorReturnCode == OK_WASEMPTY) if(result == OK && processorReturnCode == OK_WASEMPTY)
{ {
return OK_WASEMPTY; return OK_WASEMPTY;

Parādīt failu

@ -29,11 +29,6 @@ bool LooqsQuery::hasContentSearch() const
return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; return (this->getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
} }
bool LooqsQuery::hasOutlineSearch() const
{
return (this->getTokensMask() & FILTER_OUTLINE_CONTAINS) == FILTER_OUTLINE_CONTAINS;
}
bool LooqsQuery::hasPathSearch() const bool LooqsQuery::hasPathSearch() const
{ {
return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH; return (this->getTokensMask() & FILTER_PATH) == FILTER_PATH;
@ -294,10 +289,6 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
{ {
tokenType = FILTER_TAG_ASSIGNED; tokenType = FILTER_TAG_ASSIGNED;
} }
else if(filtername == "toc" || filtername == "outline")
{
tokenType = FILTER_OUTLINE_CONTAINS;
}
// TODO: given this is not really a "filter", this feels slightly misplaced here // TODO: given this is not really a "filter", this feels slightly misplaced here
else if(filtername == "sort") else if(filtername == "sort")
{ {

Parādīt failu

@ -68,7 +68,6 @@ class LooqsQuery
this->limit = limit; this->limit = limit;
} }
bool hasContentSearch() const; bool hasContentSearch() const;
bool hasOutlineSearch() const;
bool hasPathSearch() const; bool hasPathSearch() const;
void addSortCondition(SortCondition sc); void addSortCondition(SortCondition sc);

Parādīt failu

@ -1,2 +0,0 @@
CREATE TABLE outline(id INTEGER PRIMARY KEY, fileid INTEGER REFERENCES file (id) ON DELETE CASCADE, text varchar(1024), page integer);
CREATE INDEX outline_fileid ON outline (fileid);

Parādīt failu

@ -5,6 +5,5 @@
<file>3.sql</file> <file>3.sql</file>
<file>4.sql</file> <file>4.sql</file>
<file>5.sql</file> <file>5.sql</file>
<file>6.sql</file>
</qresource> </qresource>
</RCC> </RCC>

Parādīt failu

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

Parādīt failu

@ -3,12 +3,12 @@
#include "odtprocessor.h" #include "odtprocessor.h"
#include "tagstripperprocessor.h" #include "tagstripperprocessor.h"
DocumentProcessResult OdtProcessor::process(const QByteArray & /*data*/) const QVector<PageData> OdtProcessor::process(const QByteArray & /*data*/) const
{ {
throw LooqsGeneralException("Not implemented yet"); throw LooqsGeneralException("Not implemented yet");
} }
DocumentProcessResult OdtProcessor::process(QString path) const QVector<PageData> OdtProcessor::process(QString path) const
{ {
QuaZipFile zipFile(path); QuaZipFile zipFile(path);
zipFile.setFileName("content.xml"); zipFile.setFileName("content.xml");

Parādīt failu

@ -8,9 +8,9 @@ class OdtProcessor : public Processor
{ {
this->PREFERED_DATA_SOURCE = FILEPATH; this->PREFERED_DATA_SOURCE = FILEPATH;
} }
DocumentProcessResult process(const QByteArray &data) const override; QVector<PageData> process(const QByteArray &data) const override;
DocumentProcessResult process(QString path) const override; QVector<PageData> process(QString path) const override;
}; };
#endif // ODTPROCESSOR_H #endif // ODTPROCESSOR_H

Parādīt failu

@ -5,30 +5,9 @@ PdfProcessor::PdfProcessor()
{ {
} }
QVector<DocumentOutlineEntry> PdfProcessor::createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const QVector<PageData> PdfProcessor::process(const QByteArray &data) const
{ {
QVector<DocumentOutlineEntry> result; QVector<PageData> result;
for(const Poppler::OutlineItem &outlineItem : outlineItems)
{
DocumentOutlineEntry documentOutlineEntry;
documentOutlineEntry.text = outlineItem.name();
documentOutlineEntry.type = OUTLINE_DESTINATION_TYPE_PAGE;
if(!outlineItem.destination().isNull())
{
documentOutlineEntry.destinationPage = outlineItem.destination()->pageNumber();
}
if(outlineItem.hasChildren())
{
documentOutlineEntry.children = createOutline(outlineItem.children());
}
result.append(documentOutlineEntry);
}
return result;
}
DocumentProcessResult PdfProcessor::process(const QByteArray &data) const
{
DocumentProcessResult result;
QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data)); QScopedPointer<Poppler::Document> doc(Poppler::Document::loadFromData(data));
if(doc.isNull()) if(doc.isNull())
{ {
@ -47,13 +26,12 @@ DocumentProcessResult PdfProcessor::process(const QByteArray &data) const
for(auto i = 0; i < pagecount; i++) for(auto i = 0; i < pagecount; i++)
{ {
QString text = doc->page(i)->text(entirePage); QString text = doc->page(i)->text(entirePage);
result.pages.append({static_cast<unsigned int>(i + 1), text}); result.append({static_cast<unsigned int>(i + 1), text});
/*TODO: hack, so we can fts search several words over the whole document, not just pages. /*TODO: hack, so we can fts search several words over the whole document, not just pages.
* this of course uses more space and should be solved differently. * this of course uses more space and should be solved differently.
*/ */
entire += text; entire += text;
} }
result.pages.append({0, entire}); result.append({0, entire});
result.outlines = createOutline(doc->outline());
return result; return result;
} }

Parādīt failu

@ -1,6 +1,5 @@
#ifndef PDFPROCESSOR_H #ifndef PDFPROCESSOR_H
#define PDFPROCESSOR_H #define PDFPROCESSOR_H
#include <poppler-qt5.h>
#include "processor.h" #include "processor.h"
class PdfProcessor : public Processor class PdfProcessor : public Processor
{ {
@ -8,8 +7,7 @@ class PdfProcessor : public Processor
PdfProcessor(); PdfProcessor();
public: public:
QVector<DocumentOutlineEntry> createOutline(const QVector<Poppler::OutlineItem> &outlineItems) const; QVector<PageData> process(const QByteArray &data) const override;
DocumentProcessResult process(const QByteArray &data) const override;
}; };
#endif // PDFPROCESSOR_H #endif // PDFPROCESSOR_H

Parādīt failu

@ -2,8 +2,8 @@
#define PROCESSOR_H #define PROCESSOR_H
#include <QVector> #include <QVector>
#include <QFile> #include <QFile>
#include "pagedata.h"
#include "utils.h" #include "utils.h"
#include "documentprocessresult.h"
enum DataSource enum DataSource
{ {
FILEPATH, FILEPATH,
@ -18,8 +18,8 @@ class Processor
* a single file */ * a single file */
DataSource PREFERED_DATA_SOURCE = ARRAY; DataSource PREFERED_DATA_SOURCE = ARRAY;
Processor(); Processor();
virtual DocumentProcessResult process(const QByteArray &data) const = 0; virtual QVector<PageData> process(const QByteArray &data) const = 0;
virtual DocumentProcessResult process(QString path) const virtual QVector<PageData> process(QString path) const
{ {
return process(Utils::readFile(path)); return process(Utils::readFile(path));
} }

Parādīt failu

@ -65,12 +65,18 @@ void SandboxedProcessor::enableSandbox(QString readablePath)
exile_free_policy(policy); exile_free_policy(policy);
} }
void SandboxedProcessor::printResults(const DocumentProcessResult &result) void SandboxedProcessor::printResults(const QVector<PageData> &pageData)
{ {
QFile fsstdout; QFile fsstdout;
fsstdout.open(stdout, QIODevice::WriteOnly); fsstdout.open(stdout, QIODevice::WriteOnly);
QDataStream stream(&fsstdout); QDataStream stream(&fsstdout);
stream << result;
for(const PageData &data : pageData)
{
stream << data;
// fsstdout.flush();
}
fsstdout.close(); fsstdout.close();
} }
@ -96,7 +102,7 @@ SaveFileResult SandboxedProcessor::process()
return OK; return OK;
} }
DocumentProcessResult processResult; QVector<PageData> pageData;
QString absPath = fileInfo.absoluteFilePath(); QString absPath = fileInfo.absoluteFilePath();
try try
@ -105,13 +111,13 @@ SaveFileResult SandboxedProcessor::process()
{ {
/* Read access to FS needed... doh..*/ /* Read access to FS needed... doh..*/
enableSandbox(absPath); enableSandbox(absPath);
processResult = processor->process(absPath); pageData = processor->process(absPath);
} }
else else
{ {
QByteArray data = Utils::readFile(absPath); QByteArray data = Utils::readFile(absPath);
enableSandbox(); enableSandbox();
processResult = processor->process(data); pageData = processor->process(data);
} }
} }
catch(LooqsGeneralException &e) catch(LooqsGeneralException &e)
@ -120,6 +126,6 @@ SaveFileResult SandboxedProcessor::process()
return PROCESSFAIL; return PROCESSFAIL;
} }
printResults(processResult); printResults(pageData);
return processResult.pages.isEmpty() ? OK_WASEMPTY : OK; return pageData.isEmpty() ? OK_WASEMPTY : OK;
} }

Parādīt failu

@ -2,7 +2,7 @@
#define SANDBOXEDPROCESSOR_H #define SANDBOXEDPROCESSOR_H
#include <QString> #include <QString>
#include <QMimeDatabase> #include <QMimeDatabase>
#include "documentprocessresult.h" #include "pagedata.h"
#include "savefileresult.h" #include "savefileresult.h"
class SandboxedProcessor class SandboxedProcessor
@ -12,7 +12,7 @@ class SandboxedProcessor
QMimeDatabase mimeDatabase; QMimeDatabase mimeDatabase;
void enableSandbox(QString readablePath = ""); void enableSandbox(QString readablePath = "");
void printResults(const DocumentProcessResult &pageData); void printResults(const QVector<PageData> &pageData);
public: public:
SandboxedProcessor(QString filepath) SandboxedProcessor(QString filepath)

Parādīt failu

@ -42,8 +42,6 @@ SOURCES += sqlitesearch.cpp \
dbmigrator.cpp \ dbmigrator.cpp \
defaulttextprocessor.cpp \ defaulttextprocessor.cpp \
dirscanworker.cpp \ dirscanworker.cpp \
documentoutlineentry.cpp \
documentprocessresult.cpp \
encodingdetector.cpp \ encodingdetector.cpp \
filesaver.cpp \ filesaver.cpp \
filescanworker.cpp \ filescanworker.cpp \
@ -74,8 +72,6 @@ HEADERS += sqlitesearch.h \
dbmigrator.h \ dbmigrator.h \
defaulttextprocessor.h \ defaulttextprocessor.h \
dirscanworker.h \ dirscanworker.h \
documentoutlineentry.h \
documentprocessresult.h \
encodingdetector.h \ encodingdetector.h \
filedata.h \ filedata.h \
filesaver.h \ filesaver.h \

Parādīt failu

@ -142,27 +142,6 @@ QVector<QString> SqliteDbService::getTagsForPath(QString path)
return result; 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) bool SqliteDbService::setTags(QString path, const QSet<QString> &tags)
{ {
QSqlDatabase db = dbFactory->forCurrentThread(); QSqlDatabase db = dbFactory->forCurrentThread();
@ -253,29 +232,6 @@ bool SqliteDbService::insertToFTS(bool useTrigrams, QSqlDatabase &db, int fileid
return true; return true;
} }
bool SqliteDbService::insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines)
{
QSqlQuery outlineQuery(db);
outlineQuery.prepare("INSERT INTO outline(fileid, text, page) VALUES(?,?,?)");
outlineQuery.addBindValue(fileid);
for(const DocumentOutlineEntry &outline : outlines)
{
outlineQuery.bindValue(1, outline.text.toLower());
outlineQuery.bindValue(2, outline.destinationPage);
if(!outlineQuery.exec())
{
Logger::error() << "Failed outline insertion " << outlineQuery.lastError() << Qt::endl;
return false;
}
if(!insertOutline(db, fileid, outline.children))
{
Logger::error() << "Failed outline insertion (children)) " << outlineQuery.lastError() << Qt::endl;
return false;
}
}
return true;
}
QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args) QSqlQuery SqliteDbService::exec(QString querystr, std::initializer_list<QVariant> args)
{ {
auto query = QSqlQuery(dbFactory->forCurrentThread()); auto query = QSqlQuery(dbFactory->forCurrentThread());
@ -301,7 +257,7 @@ bool SqliteDbService::execBool(QString querystr, std::initializer_list<QVariant>
return query.value(0).toBool(); return query.value(0).toBool();
} }
SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResult &processResult, bool pathsOnly) SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly)
{ {
QString absPath = fileInfo.absoluteFilePath(); QString absPath = fileInfo.absoluteFilePath();
auto mtime = fileInfo.lastModified().toSecsSinceEpoch(); auto mtime = fileInfo.lastModified().toSecsSinceEpoch();
@ -346,24 +302,18 @@ SaveFileResult SqliteDbService::saveFile(QFileInfo fileInfo, DocumentProcessResu
if(!pathsOnly) if(!pathsOnly)
{ {
int lastid = inserterQuery.lastInsertId().toInt(); int lastid = inserterQuery.lastInsertId().toInt();
if(!insertToFTS(false, db, lastid, processResult.pages)) if(!insertToFTS(false, db, lastid, pageData))
{ {
db.rollback(); db.rollback();
Logger::error() << "Failed to insert data to FTS index " << Qt::endl; Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
return DBFAIL; return DBFAIL;
} }
if(!insertToFTS(true, db, lastid, processResult.pages)) if(!insertToFTS(true, db, lastid, pageData))
{ {
db.rollback(); db.rollback();
Logger::error() << "Failed to insert data to FTS index " << Qt::endl; Logger::error() << "Failed to insert data to FTS index " << Qt::endl;
return DBFAIL; return DBFAIL;
} }
if(!insertOutline(db, lastid, processResult.outlines))
{
db.rollback();
Logger::error() << "Failed to insert outline data " << Qt::endl;
return DBFAIL;
}
} }
if(!db.commit()) if(!db.commit())
@ -429,68 +379,3 @@ bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
return true; 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;
}

Parādīt failu

@ -5,7 +5,7 @@
#include "databasefactory.h" #include "databasefactory.h"
#include "utils.h" #include "utils.h"
#include "documentprocessresult.h" #include "pagedata.h"
#include "filedata.h" #include "filedata.h"
#include "../shared/sqlitesearch.h" #include "../shared/sqlitesearch.h"
#include "../shared/token.h" #include "../shared/token.h"
@ -22,7 +22,7 @@ class SqliteDbService
public: public:
SqliteDbService(DatabaseFactory &dbFactory); SqliteDbService(DatabaseFactory &dbFactory);
SaveFileResult saveFile(QFileInfo fileInfo, DocumentProcessResult &pageData, bool pathsOnly); SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
bool deleteFile(QString path); bool deleteFile(QString path);
bool fileExistsInDatabase(QString path); bool fileExistsInDatabase(QString path);
@ -34,15 +34,11 @@ class SqliteDbService
bool addTag(QString tag, const QVector<QString> &paths); bool addTag(QString tag, const QVector<QString> &paths);
QVector<QString> getTags(); QVector<QString> getTags();
QVector<QString> getTagsForPath(QString path); QVector<QString> getTagsForPath(QString path);
QVector<QString> getPathsForTag(QString path);
bool setTags(QString path, const QSet<QString> &tags); bool setTags(QString path, const QSet<QString> &tags);
bool removePathsForTag(QString tag, const QVector<QString> &paths);
bool deleteTag(QString tag);
QVector<SearchResult> search(const LooqsQuery &query); QVector<SearchResult> search(const LooqsQuery &query);
std::optional<QChar> queryFileType(QString absPath); std::optional<QChar> queryFileType(QString absPath);
bool insertOutline(QSqlDatabase &db, int fileid, const QVector<DocumentOutlineEntry> &outlines);
}; };
#endif // SQLITEDBSERVICE_H #endif // SQLITEDBSERVICE_H

Parādīt failu

@ -148,11 +148,6 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ", return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ",
{value.toLower()}}; {value.toLower()}};
} }
if(token.type == FILTER_OUTLINE_CONTAINS)
{
return {" outline.text LIKE '%' || ? || '%' ", {value.toLower()}};
}
throw LooqsGeneralException("Unknown token passed (should not happen)"); throw LooqsGeneralException("Unknown token passed (should not happen)");
} }
@ -161,7 +156,6 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
QString whereSql; QString whereSql;
QVector<QString> bindValues; QVector<QString> bindValues;
bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT; bool isContentSearch = (query.getTokensMask() & FILTER_CONTENT) == FILTER_CONTENT;
bool isOutlineSearch = query.hasOutlineSearch();
if(query.getTokens().isEmpty()) if(query.getTokens().isEmpty())
{ {
throw LooqsGeneralException("Nothing to search for supplied"); throw LooqsGeneralException("Nothing to search for supplied");
@ -206,22 +200,15 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
} }
else else
{ {
QString pageColumn = "'0' as page";
QString joiners = "";
if(isOutlineSearch)
{
pageColumn = "outline.page as page";
joiners = " INNER JOIN outline ON outline.fileid = file.id ";
}
if(sortSql.isEmpty()) if(sortSql.isEmpty())
{ {
sortSql = "ORDER BY file.mtime DESC"; sortSql = "ORDER BY file.mtime DESC";
} }
prepSql = "SELECT DISTINCT file.path AS path, " + pageColumn + prepSql = "SELECT file.path AS path, '0' as page, file.mtime AS mtime, file.size AS size, file.filetype AS "
",file.mtime AS mtime, file.size AS size, " "filetype FROM file WHERE 1=1 AND " +
"file.filetype AS filetype FROM file" + whereSql + " " + sortSql;
joiners + " WHERE 1=1 AND " + whereSql + " " + sortSql;
} }
if(query.getLimit() > 0) if(query.getLimit() > 0)
{ {
prepSql += " LIMIT " + QString::number(query.getLimit()); prepSql += " LIMIT " + QString::number(query.getLimit());
@ -255,7 +242,7 @@ QVector<SearchResult> SqliteSearch::search(const LooqsQuery &query)
throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text()); throw LooqsGeneralException("SQL Error: " + dbQuery.lastError().text());
} }
bool contentSearch = query.hasContentSearch() || query.hasOutlineSearch(); bool contentSearch = query.hasContentSearch();
while(dbQuery.next()) while(dbQuery.next())
{ {
SearchResult result; SearchResult result;

Parādīt failu

@ -28,31 +28,6 @@ bool TagManager::removeTagsForPath(QString path, const QSet<QString> &tags)
return this->dbService->setTags(path, newTags); 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) bool TagManager::addTagsToPath(QString path, QString tagstring, QChar delim)
{ {
auto splitted = tagstring.split(delim); auto splitted = tagstring.split(delim);

Parādīt failu

@ -17,11 +17,9 @@ class TagManager
bool addPathsToTag(QString tag, const QVector<QString> &paths); bool addPathsToTag(QString tag, const QVector<QString> &paths);
bool removeTagsForPath(QString path, const QSet<QString> &tags); bool removeTagsForPath(QString path, const QSet<QString> &tags);
bool removePathsForTag(QString tag, const QVector<QString> &paths);
bool deleteTag(QString tag); bool deleteTag(QString tag);
QVector<QString> getTags(QString path); QVector<QString> getTags(QString path);
QVector<QString> getTags();
QVector<QString> getPaths(QString tag); QVector<QString> getPaths(QString tag);
}; };

Parādīt failu

@ -4,11 +4,11 @@ TagStripperProcessor::TagStripperProcessor()
{ {
} }
DocumentProcessResult TagStripperProcessor::process(const QByteArray &data) const QVector<PageData> TagStripperProcessor::process(const QByteArray &data) const
{ {
auto result = DefaultTextProcessor::process(data); auto result = DefaultTextProcessor::process(data);
// TODO: does not work properly with <br> and does not deal with entities... // TODO: does not work properly with <br> and does not deal with entities...
Q_ASSERT(result.pages.size() > 0);
result.pages[0].content.remove(QRegExp("<[^>]*>")); result[0].content.remove(QRegExp("<[^>]*>"));
return result; return result;
} }

Parādīt failu

@ -8,7 +8,7 @@ class TagStripperProcessor : public DefaultTextProcessor
TagStripperProcessor(); TagStripperProcessor();
public: public:
DocumentProcessResult process(const QByteArray &data) const override; QVector<PageData> process(const QByteArray &data) const override;
}; };
#endif // XMLSTRIPPERPROCESSOR_H #endif // XMLSTRIPPERPROCESSOR_H

Parādīt failu

@ -19,11 +19,10 @@ enum TokenType
FILTER_PATH_SIZE, FILTER_PATH_SIZE,
FILTER_PATH_ENDS, FILTER_PATH_ENDS,
FILTER_PATH_STARTS, FILTER_PATH_STARTS,
FILTER_TAG_ASSIGNED, FILTER_CONTENT = 512,
FILTER_OUTLINE_CONTAINS,
FILTER_CONTENT = 512, /* Everything below here is content search (except LIMIT) */
FILTER_CONTENT_CONTAINS, FILTER_CONTENT_CONTAINS,
FILTER_CONTENT_PAGE, FILTER_CONTENT_PAGE,
FILTER_TAG_ASSIGNED,
LIMIT = 1024 LIMIT = 1024
}; };