Compare commits

..

6 Commits

11 changed files with 258 additions and 14 deletions

View File

@ -1,6 +1,7 @@
#include <QCommandLineParser> #include <QCommandLineParser>
#include "commandtag.h" #include "commandtag.h"
#include "logger.h" #include "logger.h"
#include "tagmanager.h"
int CommandTag::handle(QStringList arguments) int CommandTag::handle(QStringList arguments)
{ {
@ -52,7 +53,8 @@ int CommandTag::handle(QStringList arguments)
paths[i] = absolutePath; paths[i] = absolutePath;
} }
bool result = this->dbService->addTag(tag, paths); 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;

View File

@ -16,6 +16,9 @@
#include <QScreen> #include <QScreen>
#include <QProgressDialog> #include <QProgressDialog>
#include <QDesktopWidget> #include <QDesktopWidget>
#include <QWidgetAction>
#include <QInputDialog>
#include "mainwindow.h" #include "mainwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
#include "clicklabel.h" #include "clicklabel.h"
@ -42,6 +45,9 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
indexer = new Indexer(*(this->dbService)); indexer = new Indexer(*(this->dbService));
indexer->setParent(this); indexer->setParent(this);
tagManager = new TagManager(*(this->dbService));
connectSignals(); connectSignals();
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
ui->tabWidget->setCurrentIndex(0); ui->tabWidget->setCurrentIndex(0);
@ -1008,6 +1014,65 @@ void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath()); this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
}); });
} }
QMenu *tagMenu = menu.addMenu("Tag file with: ");
QVector<QString> allTags = this->dbService->getTags();
QHash<QString, bool> fileTags;
QString path = fileInfo.absoluteFilePath();
for(const QString &fileTag : this->dbService->getTagsForPath(path))
{
fileTags[fileTag] = true;
}
for(const QString &tag : allTags)
{
QCheckBox *checkBox = new QCheckBox(tagMenu);
QWidgetAction *checkableAction = new QWidgetAction(tagMenu);
checkableAction->setDefaultWidget(checkBox);
checkBox->setText(tag);
checkBox->setChecked(fileTags.contains(tag));
tagMenu->addAction(checkableAction);
connect(checkBox, &QCheckBox::stateChanged, this,
[this, checkBox, path]
{
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
QString checkBoxText = checkBox->text();
if(checkBox->isChecked())
{
if(!this->tagManager->addTagsToPath(path, {checkBoxText}))
{
QMessageBox::critical(this, "Error while adding tag",
"An error occured while trying to add the tag");
}
}
else
{
if(!this->tagManager->removeTagsForPath(path, {checkBoxText}))
{
QMessageBox::critical(this, "Error while removing tag",
"An error occured while trying to remove the tag");
}
}
});
}
tagMenu->addAction("Add new tags", this,
[this, path]
{
bool ok;
QString text =
QInputDialog::getText(this, tr("Enter new tags"), tr("New tags (comma separated):"),
QLineEdit::Normal, "", &ok);
if(ok && !this->tagManager->addTagsToPath(path, text, ','))
{
QMessageBox::critical(this, "Error while trying to add tags",
"An error occured while trying to add tags");
}
});
} }
void MainWindow::openDocument(QString path, int num) void MainWindow::openDocument(QString path, int num)
@ -1062,6 +1127,7 @@ MainWindow::~MainWindow()
delete this->dbService; delete this->dbService;
delete this->dbFactory; delete this->dbFactory;
delete this->indexer; delete this->indexer;
delete this->tagManager;
delete ui; delete ui;
} }

View File

@ -14,6 +14,7 @@
#include "../shared/indexsyncer.h" #include "../shared/indexsyncer.h"
#include "previewcoordinator.h" #include "previewcoordinator.h"
#include "indexer.h" #include "indexer.h"
#include "tagmanager.h"
namespace Ui namespace Ui
{ {
class MainWindow; class MainWindow;
@ -39,6 +40,9 @@ class MainWindow : public QMainWindow
QFutureWatcher<QVector<SearchResult>> searchWatcher; QFutureWatcher<QVector<SearchResult>> searchWatcher;
LooqsQuery contentSearchQuery; LooqsQuery contentSearchQuery;
QVector<QString> searchHistory; QVector<QString> searchHistory;
TagManager *tagManager;
int currentSearchHistoryIndex = 0; int currentSearchHistoryIndex = 0;
QString currentSavedSearchText; QString currentSavedSearchText;
bool previewDirty = false; bool previewDirty = false;

View File

@ -285,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")
{ {

View File

@ -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 \
@ -93,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 \

View File

@ -104,6 +104,91 @@ 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;
}
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;
}
}
db.commit();
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;
@ -248,6 +333,8 @@ bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
QSqlQuery tagQuery(db); QSqlQuery tagQuery(db);
QSqlQuery fileTagQuery(db); QSqlQuery fileTagQuery(db);
tag = tag.toLower();
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)"); tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
tagQuery.addBindValue(tag); tagQuery.addBindValue(tag);

View File

@ -23,13 +23,19 @@ class SqliteDbService
public: public:
SqliteDbService(DatabaseFactory &dbFactory); SqliteDbService(DatabaseFactory &dbFactory);
SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly); SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
bool deleteFile(QString path); bool 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); 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, QString path);
bool addTag(QString tag, const QVector<QString> &paths); bool addTag(QString tag, const QVector<QString> &paths);
QVector<QString> getTags();
QVector<QString> getTagsForPath(QString path);
bool setTags(QString path, const QSet<QString> &tags);
QVector<SearchResult> search(const LooqsQuery &query); QVector<SearchResult> search(const LooqsQuery &query);
std::optional<QChar> queryFileType(QString absPath); std::optional<QChar> queryFileType(QString absPath);

View File

@ -143,6 +143,11 @@ 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)");
} }
@ -179,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

41
shared/tagmanager.cpp Normal file
View File

@ -0,0 +1,41 @@
#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::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);
}

26
shared/tagmanager.h Normal file
View File

@ -0,0 +1,26 @@
#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 deleteTag(QString tag);
QVector<QString> getTags(QString path);
QVector<QString> getPaths(QString tag);
};
#endif // TAGMANAGER_H

View File

@ -22,6 +22,7 @@ enum TokenType
FILTER_CONTENT = 512, FILTER_CONTENT = 512,
FILTER_CONTENT_CONTAINS, FILTER_CONTENT_CONTAINS,
FILTER_CONTENT_PAGE, FILTER_CONTENT_PAGE,
FILTER_TAG_ASSIGNED,
LIMIT = 1024 LIMIT = 1024
}; };