MauiKit File Browsing

tagging.cpp
1/*
2 * Copyright 2018 Camilo Higuita <milo.h@aol.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19#include "tagging.h"
20
21#include <QMimeDatabase>
22
23#include <QFileInfo>
24#include <QDateTime>
25#include <QDebug>
26#include <QRegularExpression>
27
28#include <QThread>
29#include <QCoreApplication>
30
31#include <QSqlError>
32#include <QSqlQuery>
33#include <QSqlRecord>
34
35#include "fmstatic.h"
36#include "tagdb.h"
37
38Q_GLOBAL_STATIC(Tagging, taggingInstance)
39
40Tagging::~Tagging() {}
41
42Tagging::Tagging() : QObject()
43{
44 this->setApp();
46 {
47 qDebug() << "Lets remove Tagging singleton instance and all opened Tagging DB connections.";
48
49 qDeleteAll(m_dbs);
50 m_dbs.clear();
51 });
52}
53
54TAGDB * Tagging::db()
55{
57 {
58 qDebug() << "Using existing TAGGINGDB instance";
59
60 return m_dbs[QThread::currentThread()];
61 }
62
63 qDebug() << "Creating new TAGGINGDB instance";
64
65 auto new_db = new TAGDB;
66 m_dbs.insert(QThread::currentThread(), new_db);
67 return new_db;
68}
69
71{
72 return taggingInstance();
73}
74
75const QVariantList Tagging::get(const QString &queryTxt, std::function<bool(QVariantMap &item)> modifier)
76{
77 QVariantList mapList;
78
79 auto query = this->db()->getQuery(queryTxt);
80
81 if (query.exec()) {
82 const auto keys = FMH::MODEL_NAME.keys();
83
84 while (query.next()) {
85 QVariantMap data;
86 for (const auto &key : keys) {
87
88 if (query.record().indexOf(FMH::MODEL_NAME[key]) > -1) {
89 data[FMH::MODEL_NAME[key]] = query.value(FMH::MODEL_NAME[key]).toString();
90 }
91 }
92
93 if (modifier) {
94 if (!modifier(data))
95 {
96 continue;
97 }
98 }
99
100 mapList << data;
101 }
102
103 } else
104 {
105 qDebug() << query.lastError() << query.lastQuery();
106 }
107
108 return mapList;
109}
110
111bool Tagging::tagExists(const QString &tag, const bool &strict)
112{
113 return !strict ? this->db()->checkExistance(TAG::TABLEMAP[TAG::TABLE::TAGS], FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], tag)
114 : this->db()->checkExistance(QString(QStringLiteral("select t.tag from APP_TAGS where t.org = '%1' and t.tag = '%2'"))
115 .arg(this->appOrg, tag));
116}
117
118bool Tagging::urlTagExists(const QString &url, const QString &tag)
119{
120 return this->db()->checkExistance(QString(QStringLiteral("select * from TAGS_URLS where url = '%1' and tag = '%2'")).arg(url, tag));
121}
122
123void Tagging::setApp()
124{
125 this->appName = qApp->applicationName();
126 this->appComment = QString();
127 this->appOrg = qApp->organizationDomain().isEmpty() ? QString(QStringLiteral("org.maui.%1")).arg(this->appName) : qApp->organizationDomain();
128 this->app(); // here register the app
129}
130
131bool Tagging::tag(const QString &tag, const QString &color, const QString &comment)
132{
133 if (tag.isEmpty())
134 return false;
135
136 if(tag.contains(QStringLiteral(" ")) || tag.contains(QStringLiteral("\n")))
137 {
138 return false;
139 }
140
141 QVariantMap tag_map {
142 {FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], tag},
143 {FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], color},
145 {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], comment},
146 };
147
148 this->db()->insert(TAG::TABLEMAP[TAG::TABLE::TAGS], tag_map);
149
150 const QVariantMap appTag_map {
151 {FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], tag},
152 {FMH::MODEL_NAME[FMH::MODEL_KEY::ORG], this->appOrg},
153 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime().toString(Qt::TextDate)}};
154
155 if (this->db()->insert(TAG::TABLEMAP[TAG::TABLE::APP_TAGS], appTag_map)) {
156 setTagIconName(tag_map);
157 Q_EMIT this->tagged(tag_map);
158 return true;
159 }
160
161 return false;
162}
163
164bool Tagging::tagUrl(const QString &url, const QString &tag, const QString &color, const QString &comment)
165{
166 const auto myTag = tag.trimmed();
167
168 this->tag(myTag, color, comment);
169
170 QMimeDatabase mimedb;
171
172 QVariantMap tag_url_map {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url},
173 {FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], myTag},
174 {FMH::MODEL_NAME[FMH::MODEL_KEY::TITLE], QFileInfo(url).baseName()},
175 {FMH::MODEL_NAME[FMH::MODEL_KEY::MIME], mimedb.mimeTypeForFile(url).name()},
176 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()},
177 {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], comment}};
178
179 if(this->db()->insert(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], tag_url_map))
180 {
181 qDebug() << "tagging url" << url <<tag;
182 Q_EMIT this->urlTagged(url, myTag);
183
184 // auto fileMetaData = KFileMetaData::UserMetaData(QUrl::fromUserInput(url).toLocalFile());
185 // fileMetaData.setTags({tag});
186
187 return true;
188 }
189
190 return false;
191}
192
193bool Tagging::updateUrlTags(const QString &url, const QStringList &tags, const bool &strict)
194{
195 this->removeUrlTags(url, strict);
196
197 for (const auto &tag : std::as_const(tags))
198 {
199 this->tagUrl(url, tag);
200 }
201
202 return true;
203}
204
205bool Tagging::updateUrl(const QString &url, const QString &newUrl)
206{
207 return this->db()->update(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], {{FMH::MODEL_KEY::URL, newUrl}}, {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url}});
208}
209
210QVariantList Tagging::getUrlsTags(const bool &strict) //all used tags, meaning, all tags that are used with an url in tags_url table
211{
212 const auto query = !strict ? QString(QStringLiteral("select distinct t.* from TAGS t inner join TAGS_URLS turl where t.tag = turl.tag order by t.tag asc")) :
213 QString(QStringLiteral("select distinct t.* from TAGS t inner join APP_TAGS at on at.tag = t.tag inner join TAGS_URLS turl on t.tag = turl.tag where at.org = '%1' order by t.tag asc")).arg(this->appOrg);
214
215 return this->get(query, &setTagIconName);
216}
217
218bool Tagging::setTagIconName(QVariantMap &item)
219{
220 item.insert(QStringLiteral("icon"), item.value(QStringLiteral("tag")).toString() == QStringLiteral("fav") ? QStringLiteral("love") : QStringLiteral("tag"));
221 return true;
222}
223
224QVariantList Tagging::getAllTags(const bool &strict)
225{
226 return !strict ? this->get(QStringLiteral("select * from tags"), &setTagIconName)
227 : this->get(QString(QStringLiteral("select t.* from TAGS t inner join APP_TAGS at on t.tag = at.tag where at.org = '%1'")).arg(this->appOrg),
228 &setTagIconName);
229}
230
231QVariantList Tagging::getUrls(const QString &tag, const bool &strict, const int &limit, const QString &mimeType, std::function<bool(QVariantMap &item)> modifier)
232{
233 return !strict ? this->get(QString(QStringLiteral("select distinct * from TAGS_URLS where tag = '%1' and mime like '%2%' limit %3")).arg(tag, mimeType, QString::number(limit)), modifier)
234 : this->get(QString(QStringLiteral("select distinct turl.*, t.color, t.comment as tagComment from TAGS t "
235 "inner join APP_TAGS at on t.tag = at.tag "
236 "inner join TAGS_URLS turl on turl.tag = t.tag "
237 "where at.org = '%1' and turl.mime like '%4%' "
238 "and t.tag = '%2' limit %3"))
239 .arg(this->appOrg, tag, QString::number(limit), mimeType),
240 modifier);
241}
242
243QVariantList Tagging::getUrlTags(const QString &url, const bool &strict)
244{
245 return !strict ? this->get(QString(QStringLiteral("select distinct turl.*, t.color, t.comment as tagComment from tags t inner join TAGS_URLS turl on turl.tag = t.tag where turl.url = '%1'")).arg(url))
246 : this->get(QString(QStringLiteral("select distinct t.* from TAGS t inner join APP_TAGS at on t.tag = at.tag inner join TAGS_URLS turl on turl.tag = t.tag "
247 "where at.org = '%1' and turl.url = '%2'"))
248 .arg(this->appOrg, url));
249}
250
251bool Tagging::removeUrlTags(const QString &url, const bool &strict) // same as removing the url from the tags_urls
252{
253 Q_UNUSED(strict)
254 return this->removeUrl(url);
255}
256
257bool Tagging::removeUrlTag(const QString &url, const QString &tag)
258{
259 qDebug() << "Remove url tag" << url << tag;
260 FMH::MODEL data {{FMH::MODEL_KEY::URL, url}, {FMH::MODEL_KEY::TAG, tag}};
261 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], data))
262 {
263 Q_EMIT this->urlTagRemoved(tag, url);
264 return true;
265 }
266
267 return false;
268}
269
271{
272 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], {{FMH::MODEL_KEY::URL, url}}))
273 {
274 Q_EMIT this->urlRemoved(url);
275 }
276
277 return false;
278}
279
280bool Tagging::app()
281{
282 qDebug() << "REGISTER APP" << this->appName << this->appOrg << this->appComment;
283 const QVariantMap app_map {
284 {FMH::MODEL_NAME[FMH::MODEL_KEY::NAME], this->appName},
285 {FMH::MODEL_NAME[FMH::MODEL_KEY::ORG], this->appOrg},
286 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()},
287 {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], this->appComment},
288 };
289
290 return this->db()->insert(TAG::TABLEMAP[TAG::TABLE::APPS], app_map);
291}
292
293bool Tagging::removeTag(const QString& tag, const bool &strict)
294{
295 if(strict)
296 {
297 FMH::MODEL data0 {{FMH::MODEL_KEY::TAG, tag}, {FMH::MODEL_KEY::ORG, this->appOrg}};
298
299 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::APP_TAGS], data0))
300 {
301 return true;
302 }
303
304 }else
305 {
306 FMH::MODEL data1 {{FMH::MODEL_KEY::TAG, tag}};
307
308 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], data1))
309 {
310 FMH::MODEL data2 {{FMH::MODEL_KEY::TAG, tag}, {FMH::MODEL_KEY::ORG, this->appOrg}};
311
312 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::APP_TAGS], data2))
313 {
314 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS], data1))
315 {
316 Q_EMIT this->tagRemoved(tag);
317 return true;
318 }
319 }
320 }
321 }
322
323
324
325 return false;
326}
327
328static bool doNameFilter(const QString &name, const QStringList &filters)
329{
330 const auto filtersAccumulate = std::accumulate(filters.constBegin(), filters.constEnd(), QVector<QRegularExpression> {}, [](QVector<QRegularExpression> &res, const QString &filter) -> QVector<QRegularExpression> {
331 QString wildcardExp = QRegularExpression::wildcardToRegularExpression(filter).replace(QStringLiteral("[^/]"), QStringLiteral("."));
332 QRegularExpression reg(wildcardExp, QRegularExpression::CaseInsensitiveOption);
333 res.append(reg);
334 return res;
335});
336
337 for (const auto &filter : filtersAccumulate) {
338 qDebug() << "trying to match" << name << filter;
339 if (filter.match(name).hasMatch()) {
340 qDebug() << "trying to match" << true;
341
342 return true;
343 }
344 }
345 return false;
346}
347
348QList<QUrl> Tagging::getTagUrls(const QString &tag, const QStringList &filters, const bool &strict, const int &limit, const QString &mime)
349{
350 QList<QUrl> urls;
351
352 std::function<bool(QVariantMap & item)> filter = nullptr;
353
354 if (!filters.isEmpty())
355 {
356 filter = [filters](QVariantMap &item) -> bool
357 {
358 return doNameFilter(FMH::mapValue(item, FMH::MODEL_KEY::URL), filters);
359 };
360 }
361
362 const auto tagUrls = getUrls(tag, strict, limit, mime, filter);
363 for (const auto &data : tagUrls)
364 {
365 const auto url = QUrl(data.toMap()[FMH::MODEL_NAME[FMH::MODEL_KEY::URL]].toString());
366 if (url.isLocalFile() && !FMH::fileExists(url))
367 continue;
368 urls << url;
369 }
370
371 return urls;
372}
373
375{
376 Q_UNUSED(limit);
377 FMH::MODEL_LIST data;
378 const auto tags = getAllTags(false);
379 for (const auto &tag : tags)
380 {
381 const QVariantMap item = tag.toMap();
382 const auto label = item.value(FMH::MODEL_NAME[FMH::MODEL_KEY::TAG]).toString();
383
384 data << FMH::MODEL {{FMH::MODEL_KEY::PATH, FMStatic::PATHTYPE_URI[FMStatic::PATHTYPE_KEY::TAGS_PATH] + label},
385 {FMH::MODEL_KEY::ICON, item.value(FMH::MODEL_NAME[FMH::MODEL_KEY::ICON], QStringLiteral("tag")).toString()},
386 {FMH::MODEL_KEY::MODIFIED, QDateTime::fromString(item.value(FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE]).toString(), Qt::TextDate).toString()},
387 {FMH::MODEL_KEY::IS_DIR, QStringLiteral("true")},
388 {FMH::MODEL_KEY::LABEL, label},
390}
391
392return data;
393}
394
396{
397 return FMH::toModelList(this->getUrlTags(url.toString(), false));
398}
399
400bool Tagging::addTagToUrl(const QString tag, const QUrl &url)
401{
402 return this->tagUrl(url.toString(), tag);
403}
404
405bool Tagging::removeTagToUrl(const QString tag, const QUrl &url)
406{
407 return this->removeUrlTag(url.toString(), tag);
408}
409
410bool Tagging::toggleFav(const QUrl &url)
411{
412 if (this->isFav(url))
413 return this->unFav(url);
414
415 return this->fav(url);
416}
417
418bool Tagging::fav(const QUrl &url)
419{
420 return this->tagUrl(url.toString(), QStringLiteral("fav"), QStringLiteral("#e91e63"));
421}
422
423bool Tagging::unFav(const QUrl &url)
424{
425 return this->removeUrlTag(url.toString(), QStringLiteral("fav"));
426}
427
428bool Tagging::isFav(const QUrl &url, const bool &strict)
429{
430 Q_UNUSED(strict)
431
432 return urlTagExists(url.toString(), QStringLiteral("fav"));
433}
434
static const QHash< PATHTYPE_KEY, QString > PATHTYPE_URI
Similar to PATHTYPE_SCHEME, but mapped with the complete scheme.
Definition fmstatic.h:369
static QString PathTypeLabel(const FMStatic::PATHTYPE_KEY &key)
Given a PATHTYPE_KEY return a user friendly string.
Definition fmstatic.cpp:31
@ TAGS_PATH
A tag location.
Definition fmstatic.h:276
The TAGDB class exposes methods to add, remove and modify tags in the MauiKit FileBrowsing Tagging sy...
Definition tagdb.h:56
bool update(const QString &tableName, const FMH::MODEL &updateData, const QVariantMap &where) const
Update data in the database.
Definition tagdb.cpp:182
bool checkExistance(const QString &tableName, const QString &searchId, const QString &search) const
Check for the existence of an entry.
Definition tagdb.cpp:120
bool insert(const QString &tableName, const QVariantMap &insertData) const
Insert data into the given table.
Definition tagdb.cpp:152
QSqlQuery getQuery(const QString &queryTxt) const
Retrieve the database query object of a performed a query.
Definition tagdb.cpp:140
The Tagging class provides quick methods to access and modify the tags associated to the files.
Definition tagging.h:43
void urlTagged(QString url, QString tag)
Emitted when a tag has been assigened to a file URL.
bool tag(const QString &tag, const QString &color=QString(), const QString &comment=QString())
Adds a new tag, the newly created tag gets associated to the app making the call.
Definition tagging.cpp:131
bool removeUrl(const QString &url)
Removes a URL with its associated tags.
Definition tagging.cpp:270
const QVariantList get(const QString &query, std::function< bool(QVariantMap &item)> modifier=nullptr)
Retrieve the information into a model, optionally you can pass a modifier callback function to manipu...
Definition tagging.cpp:75
QList< QUrl > getTagUrls(const QString &tag, const QStringList &filters, const bool &strict=false, const int &limit=9999, const QString &mime=QStringLiteral(""))
Shortcut for getting a list of file URLs associated to a tag, the resulting list of URLs can be filte...
Definition tagging.cpp:348
QVariantList getUrlsTags(const bool &strict=false)
Give a list of all tags associated to files.
Definition tagging.cpp:210
bool removeTag(const QString &tag, const bool &strict=false)
Remove a tag.
Definition tagging.cpp:293
bool tagUrl(const QString &url, const QString &tag, const QString &color=QString(), const QString &comment=QString())
Adds a tag to a given file URL, if the given tag doesn't exists then it gets created.
Definition tagging.cpp:164
bool tagExists(const QString &tag, const bool &strict=false)
Checks if a given tag exists, it can be strictly enforced, meaning it is checked if the tag was creat...
Definition tagging.cpp:111
QVariantList getUrls(const QString &tag, const bool &strict=false, const int &limit=MAX_LIMIT, const QString &mimeType=QStringLiteral(""), std::function< bool(QVariantMap &item)> modifier=nullptr)
Returns a model of all the file URLs associated to a tag, the result can be strictly enforced to only...
Definition tagging.cpp:231
void tagRemoved(QString tag)
Emitted when a tag has been removed.
void tagged(QVariantMap tag)
Emitted when a new tag has been created.
bool fav(const QUrl &url)
Marks a file URL as favorite.
Definition tagging.cpp:418
bool isFav(const QUrl &url, const bool &strict=false)
Checks if a file URL has been marked as favorite.
Definition tagging.cpp:428
FMH::MODEL_LIST getTags(const int &limit=5)
Get all the tags available with detailed information packaged as a FMH::MODEL_LIST.
Definition tagging.cpp:374
static Tagging * getInstance()
Returns an instance to the tagging object.
Definition tagging.cpp:70
bool updateUrlTags(const QString &url, const QStringList &tags, const bool &strict=false)
Updates the tags associated to a file URL.
Definition tagging.cpp:193
QVariantList getAllTags(const bool &strict=false)
Returns a list model of all the tags.
Definition tagging.cpp:224
bool removeTagToUrl(const QString tag, const QUrl &url)
Removes a tag from a file URL if the tags exists.
Definition tagging.cpp:405
bool unFav(const QUrl &url)
If the given file has been marked as favorite then the tag is removed.
Definition tagging.cpp:423
bool removeUrlTag(const QString &url, const QString &tag)
Removes a given tag associated to a given file URL.
Definition tagging.cpp:257
QVariantList getUrlTags(const QString &url, const bool &strict=false)
Returns a model list of all the tags associated to a file URL.
Definition tagging.cpp:243
bool urlTagExists(const QString &url, const QString &tag)
Checks if a given tag is associated to a give file URL.
Definition tagging.cpp:118
void urlTagRemoved(QString tag, QString url)
Emitted when a tag has been dissociated from a file URL.
bool updateUrl(const QString &url, const QString &newUrl)
Updates a file URL to a new URL, preserving all associated tags.
Definition tagging.cpp:205
bool toggleFav(const QUrl &url)
Toggle the fav tag of a given file, meaning, if a file is marked as fav then the tag gets removed and...
Definition tagging.cpp:410
void urlRemoved(QString url)
Emitted when a file has been removed a thus all associations to any tag.
bool removeUrlTags(const QString &url, const bool &strict=false)
Given a file URL remove all the tags associated to it.
Definition tagging.cpp:251
bool addTagToUrl(const QString tag, const QUrl &url)
Adds a tag to a given file URL.
Definition tagging.cpp:400
const QString mapValue(const QVariantMap &map, const MODEL_KEY &key)
const FMH::MODEL_LIST toModelList(const QVariantList &list)
bool fileExists(const QUrl &path)
static const QHash< MODEL_KEY, QString > MODEL_NAME
QDateTime currentDateTime()
QDateTime fromString(QStringView string, QStringView format, QCalendar cal)
QString toString(QStringView format, QCalendar cal) const const
QString baseName() const const
bool contains(const Key &key) const const
iterator insert(const Key &key, const T &value)
const_iterator constBegin() const const
const_iterator constEnd() const const
qsizetype indexOf(const AT &value, qsizetype from) const const
bool isEmpty() const const
T value(qsizetype i) const const
QMimeType mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const const
Q_EMITQ_EMIT
QString arg(Args &&... args) const const
bool isEmpty() const const
QString number(double n, char format, int precision)
TextDate
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
QThread * currentThread()
QString toString(FormattingOptions options) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 27 2024 11:48:47 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.