Compare commits
6 Commits
a3cfb7ade1
...
15e50c7bab
Author | SHA1 | Date | |
---|---|---|---|
15e50c7bab | |||
d4e35c0279 | |||
0e99fee643 | |||
b1178a9df2 | |||
cd400b2d71 | |||
f324da0369 |
@ -1,6 +1,7 @@
|
||||
#include <QCommandLineParser>
|
||||
#include "commandtag.h"
|
||||
#include "logger.h"
|
||||
#include "tagmanager.h"
|
||||
|
||||
int CommandTag::handle(QStringList arguments)
|
||||
{
|
||||
@ -52,7 +53,8 @@ int CommandTag::handle(QStringList arguments)
|
||||
paths[i] = absolutePath;
|
||||
}
|
||||
|
||||
bool result = this->dbService->addTag(tag, paths);
|
||||
TagManager tagManager{*this->dbService};
|
||||
bool result = tagManager.addPathsToTag(tag, paths);
|
||||
if(!result)
|
||||
{
|
||||
Logger::error() << "Failed to assign tags" << Qt::endl;
|
||||
|
@ -16,6 +16,9 @@
|
||||
#include <QScreen>
|
||||
#include <QProgressDialog>
|
||||
#include <QDesktopWidget>
|
||||
#include <QWidgetAction>
|
||||
#include <QInputDialog>
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
#include "clicklabel.h"
|
||||
@ -42,6 +45,9 @@ MainWindow::MainWindow(QWidget *parent, QString socketPath)
|
||||
|
||||
indexer = new Indexer(*(this->dbService));
|
||||
indexer->setParent(this);
|
||||
|
||||
tagManager = new TagManager(*(this->dbService));
|
||||
|
||||
connectSignals();
|
||||
ui->treeResultsList->setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||
ui->tabWidget->setCurrentIndex(0);
|
||||
@ -1008,6 +1014,65 @@ void MainWindow::createSearchResultMenu(QMenu &menu, const QFileInfo &fileInfo)
|
||||
this->ui->comboPreviewFiles->setCurrentText(fileInfo.absoluteFilePath());
|
||||
});
|
||||
}
|
||||
|
||||
QMenu *tagMenu = menu.addMenu("Tag file with: ");
|
||||
QVector<QString> allTags = this->dbService->getTags();
|
||||
QHash<QString, bool> fileTags;
|
||||
|
||||
QString path = fileInfo.absoluteFilePath();
|
||||
|
||||
for(const QString &fileTag : this->dbService->getTagsForPath(path))
|
||||
{
|
||||
fileTags[fileTag] = true;
|
||||
}
|
||||
|
||||
for(const QString &tag : allTags)
|
||||
{
|
||||
QCheckBox *checkBox = new QCheckBox(tagMenu);
|
||||
QWidgetAction *checkableAction = new QWidgetAction(tagMenu);
|
||||
checkableAction->setDefaultWidget(checkBox);
|
||||
checkBox->setText(tag);
|
||||
checkBox->setChecked(fileTags.contains(tag));
|
||||
tagMenu->addAction(checkableAction);
|
||||
|
||||
connect(checkBox, &QCheckBox::stateChanged, this,
|
||||
[this, checkBox, path]
|
||||
{
|
||||
QVector<QString> currentTags = this->dbService->getTagsForPath(path);
|
||||
QString checkBoxText = checkBox->text();
|
||||
if(checkBox->isChecked())
|
||||
{
|
||||
if(!this->tagManager->addTagsToPath(path, {checkBoxText}))
|
||||
{
|
||||
QMessageBox::critical(this, "Error while adding tag",
|
||||
"An error occured while trying to add the tag");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(!this->tagManager->removeTagsForPath(path, {checkBoxText}))
|
||||
{
|
||||
QMessageBox::critical(this, "Error while removing tag",
|
||||
"An error occured while trying to remove the tag");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tagMenu->addAction("Add new tags", this,
|
||||
[this, path]
|
||||
{
|
||||
bool ok;
|
||||
QString text =
|
||||
QInputDialog::getText(this, tr("Enter new tags"), tr("New tags (comma separated):"),
|
||||
QLineEdit::Normal, "", &ok);
|
||||
|
||||
if(ok && !this->tagManager->addTagsToPath(path, text, ','))
|
||||
{
|
||||
QMessageBox::critical(this, "Error while trying to add tags",
|
||||
"An error occured while trying to add tags");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void MainWindow::openDocument(QString path, int num)
|
||||
@ -1062,6 +1127,7 @@ MainWindow::~MainWindow()
|
||||
delete this->dbService;
|
||||
delete this->dbFactory;
|
||||
delete this->indexer;
|
||||
delete this->tagManager;
|
||||
delete ui;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "../shared/indexsyncer.h"
|
||||
#include "previewcoordinator.h"
|
||||
#include "indexer.h"
|
||||
#include "tagmanager.h"
|
||||
namespace Ui
|
||||
{
|
||||
class MainWindow;
|
||||
@ -39,6 +40,9 @@ class MainWindow : public QMainWindow
|
||||
QFutureWatcher<QVector<SearchResult>> searchWatcher;
|
||||
LooqsQuery contentSearchQuery;
|
||||
QVector<QString> searchHistory;
|
||||
|
||||
TagManager *tagManager;
|
||||
|
||||
int currentSearchHistoryIndex = 0;
|
||||
QString currentSavedSearchText;
|
||||
bool previewDirty = false;
|
||||
|
@ -285,6 +285,10 @@ LooqsQuery LooqsQuery::build(QString expression, TokenType loneWordsTokenType, b
|
||||
{
|
||||
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
|
||||
else if(filtername == "sort")
|
||||
{
|
||||
|
@ -60,6 +60,7 @@ SOURCES += sqlitesearch.cpp \
|
||||
processor.cpp \
|
||||
sandboxedprocessor.cpp \
|
||||
sqlitedbservice.cpp \
|
||||
tagmanager.cpp \
|
||||
tagstripperprocessor.cpp \
|
||||
utils.cpp \
|
||||
../submodules/exile.h/exile.c \
|
||||
@ -93,6 +94,7 @@ HEADERS += sqlitesearch.h \
|
||||
savefileresult.h \
|
||||
searchresult.h \
|
||||
sqlitedbservice.h \
|
||||
tagmanager.h \
|
||||
tagstripperprocessor.h \
|
||||
token.h \
|
||||
common.h \
|
||||
|
@ -104,6 +104,91 @@ unsigned int SqliteDbService::getFiles(QVector<FileData> &results, QString wildC
|
||||
return processedRows;
|
||||
}
|
||||
|
||||
QVector<QString> SqliteDbService::getTags()
|
||||
{
|
||||
QVector<QString> result;
|
||||
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
||||
query.prepare("SELECT name FROM tag ORDER by name ASC");
|
||||
query.setForwardOnly(true);
|
||||
if(!query.exec())
|
||||
{
|
||||
throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
|
||||
}
|
||||
while(query.next())
|
||||
{
|
||||
QString tagname = query.value(0).toString();
|
||||
result.append(tagname);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QVector<QString> SqliteDbService::getTagsForPath(QString path)
|
||||
{
|
||||
QVector<QString> result;
|
||||
auto query = QSqlQuery(dbFactory->forCurrentThread());
|
||||
query.prepare("SELECT name FROM tag INNER JOIN filetag ON tag.id = filetag.tagid INNER JOIN file ON filetag.fileid "
|
||||
"= file.id WHERE file.path = ? ORDER BY name ASC");
|
||||
query.addBindValue(path);
|
||||
query.setForwardOnly(true);
|
||||
if(!query.exec())
|
||||
{
|
||||
throw LooqsGeneralException("Error while trying to retrieve tags from database: " + query.lastError().text());
|
||||
}
|
||||
while(query.next())
|
||||
{
|
||||
QString tagname = query.value(0).toString();
|
||||
result.append(tagname);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
QString ftsInsertStatement;
|
||||
@ -248,6 +333,8 @@ bool SqliteDbService::addTag(QString tag, const QVector<QString> &paths)
|
||||
QSqlQuery tagQuery(db);
|
||||
QSqlQuery fileTagQuery(db);
|
||||
|
||||
tag = tag.toLower();
|
||||
|
||||
tagQuery.prepare("INSERT OR IGNORE INTO tag (name) VALUES(?)");
|
||||
tagQuery.addBindValue(tag);
|
||||
|
||||
|
@ -23,13 +23,19 @@ class SqliteDbService
|
||||
public:
|
||||
SqliteDbService(DatabaseFactory &dbFactory);
|
||||
SaveFileResult saveFile(QFileInfo fileInfo, QVector<PageData> &pageData, bool pathsOnly);
|
||||
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
|
||||
|
||||
bool deleteFile(QString path);
|
||||
bool fileExistsInDatabase(QString path);
|
||||
bool fileExistsInDatabase(QString path, qint64 mtime);
|
||||
bool fileExistsInDatabase(QString path, qint64 mtime, QChar filetype);
|
||||
unsigned int getFiles(QVector<FileData> &results, QString wildCardPattern, int offset, int limit);
|
||||
|
||||
bool addTag(QString tag, QString path);
|
||||
bool addTag(QString tag, const QVector<QString> &paths);
|
||||
QVector<QString> getTags();
|
||||
QVector<QString> getTagsForPath(QString path);
|
||||
bool setTags(QString path, const QSet<QString> &tags);
|
||||
|
||||
QVector<SearchResult> search(const LooqsQuery &query);
|
||||
|
||||
std::optional<QChar> queryFileType(QString absPath);
|
||||
|
@ -143,6 +143,11 @@ QPair<QString, QVector<QString>> SqliteSearch::createSql(const Token &token)
|
||||
{
|
||||
return {" fts MATCH ? ", {escapeFtsArgument(value)}};
|
||||
}
|
||||
if(token.type == FILTER_TAG_ASSIGNED)
|
||||
{
|
||||
return {" file.id IN (SELECT fileid FROM filetag WHERE tagid = (SELECT id FROM tag WHERE name = ?)) ",
|
||||
{value.toLower()}};
|
||||
}
|
||||
throw LooqsGeneralException("Unknown token passed (should not happen)");
|
||||
}
|
||||
|
||||
@ -179,18 +184,18 @@ QSqlQuery SqliteSearch::makeSqlQuery(const LooqsQuery &query)
|
||||
}
|
||||
QString whereSqlTrigram = whereSql;
|
||||
whereSqlTrigram.replace("fts MATCH", "fts_trigram MATCH"); // A bit dirty...
|
||||
prepSql =
|
||||
"SELECT DISTINCT path, page, mtime, size, filetype FROM ("
|
||||
"SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
||||
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||
"content.fileid "
|
||||
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
|
||||
whereSql +
|
||||
"UNION ALL SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
||||
"file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||
"content.fileid " +
|
||||
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " + whereSqlTrigram +
|
||||
" ) " + sortSql;
|
||||
prepSql = "SELECT DISTINCT path, page, mtime, size, filetype FROM ("
|
||||
"SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
||||
"file.filetype AS filetype, 0 AS prio, fts.rank AS rank FROM file INNER JOIN content ON file.id = "
|
||||
"content.fileid "
|
||||
"INNER JOIN fts ON content.ftsid = fts.ROWID WHERE 1=1 AND " +
|
||||
whereSql +
|
||||
"UNION ALL SELECT file.path AS path, content.page AS page, file.mtime AS mtime, file.size AS size, "
|
||||
"file.filetype AS filetype, 1 as prio, fts_trigram.rank AS rank FROM file INNER JOIN content ON "
|
||||
"file.id = "
|
||||
"content.fileid " +
|
||||
"INNER JOIN fts_trigram ON content.fts_trigramid = fts_trigram.ROWID WHERE 1=1 AND " +
|
||||
whereSqlTrigram + " ) " + sortSql;
|
||||
++bindIterations;
|
||||
}
|
||||
else
|
||||
|
41
shared/tagmanager.cpp
Normal file
41
shared/tagmanager.cpp
Normal 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
26
shared/tagmanager.h
Normal 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
|
@ -22,6 +22,7 @@ enum TokenType
|
||||
FILTER_CONTENT = 512,
|
||||
FILTER_CONTENT_CONTAINS,
|
||||
FILTER_CONTENT_PAGE,
|
||||
FILTER_TAG_ASSIGNED,
|
||||
LIMIT = 1024
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user