Warning, file /maui/mauikit-filebrowsing/src/code/tagging.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * Copyright 2018 Camilo Higuita <milo.h@aol.com> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU Library General Public License as 0006 * published by the Free Software Foundation; either version 2, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details 0013 * 0014 * You should have received a copy of the GNU Library General Public 0015 * License along with this program; if not, write to the 0016 * Free Software Foundation, Inc., 0017 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 0018 */ 0019 #include "tagging.h" 0020 0021 #include <QMimeDatabase> 0022 0023 #include <QFileInfo> 0024 #include <QDateTime> 0025 #include <QDebug> 0026 #include <QRegularExpression> 0027 0028 #include <QThread> 0029 #include <QCoreApplication> 0030 0031 #include <QSqlError> 0032 #include <QSqlQuery> 0033 #include <QSqlRecord> 0034 0035 #include "fmstatic.h" 0036 #include "tagdb.h" 0037 0038 Tagging *Tagging::m_instance = nullptr; 0039 0040 Tagging::~Tagging() {} 0041 0042 Tagging::Tagging() : QObject() 0043 { 0044 this->setApp(); 0045 connect(qApp, &QCoreApplication::aboutToQuit, [this]() 0046 { 0047 qDebug() << "Lets remove Tagging singleton instance and all opened Tagging DB connections."; 0048 0049 qDeleteAll(m_dbs); 0050 m_dbs.clear(); 0051 0052 delete m_instance; 0053 m_instance = nullptr; 0054 }); 0055 } 0056 0057 TAGDB * Tagging::db() 0058 { 0059 if(m_dbs.contains(QThread::currentThread())) 0060 { 0061 qDebug() << "Using existing TAGGINGDB instance"; 0062 0063 return m_dbs[QThread::currentThread()]; 0064 } 0065 0066 qDebug() << "Creating new TAGGINGDB instance"; 0067 0068 auto new_db = new TAGDB; 0069 m_dbs.insert(QThread::currentThread(), new_db); 0070 return new_db; 0071 } 0072 0073 const QVariantList Tagging::get(const QString &queryTxt, std::function<bool(QVariantMap &item)> modifier) 0074 { 0075 QVariantList mapList; 0076 0077 auto query = this->db()->getQuery(queryTxt); 0078 0079 if (query.exec()) { 0080 const auto keys = FMH::MODEL_NAME.keys(); 0081 0082 while (query.next()) { 0083 QVariantMap data; 0084 for (const auto &key : keys) { 0085 0086 if (query.record().indexOf(FMH::MODEL_NAME[key]) > -1) { 0087 data[FMH::MODEL_NAME[key]] = query.value(FMH::MODEL_NAME[key]).toString(); 0088 } 0089 } 0090 0091 if (modifier) { 0092 if (!modifier(data)) 0093 { 0094 continue; 0095 } 0096 } 0097 0098 mapList << data; 0099 } 0100 0101 } else 0102 { 0103 qDebug() << query.lastError() << query.lastQuery(); 0104 } 0105 0106 return mapList; 0107 } 0108 0109 bool Tagging::tagExists(const QString &tag, const bool &strict) 0110 { 0111 return !strict ? this->db()->checkExistance(TAG::TABLEMAP[TAG::TABLE::TAGS], FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], tag) 0112 : this->db()->checkExistance(QString(QStringLiteral("select t.tag from APP_TAGS where t.org = '%1' and t.tag = '%2'")) 0113 .arg(this->appOrg, tag)); 0114 } 0115 0116 bool Tagging::urlTagExists(const QString &url, const QString &tag) 0117 { 0118 return this->db()->checkExistance(QString(QStringLiteral("select * from TAGS_URLS where url = '%1' and tag = '%2'")).arg(url, tag)); 0119 } 0120 0121 void Tagging::setApp() 0122 { 0123 this->appName = qApp->applicationName(); 0124 this->appComment = QString(); 0125 this->appOrg = qApp->organizationDomain().isEmpty() ? QString(QStringLiteral("org.maui.%1")).arg(this->appName) : qApp->organizationDomain(); 0126 this->app(); // here register the app 0127 } 0128 0129 bool Tagging::tag(const QString &tag, const QString &color, const QString &comment) 0130 { 0131 if (tag.isEmpty()) 0132 return false; 0133 0134 if(tag.contains(QStringLiteral(" ")) || tag.contains(QStringLiteral("\n"))) 0135 { 0136 return false; 0137 } 0138 0139 QVariantMap tag_map { 0140 {FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], tag}, 0141 {FMH::MODEL_NAME[FMH::MODEL_KEY::COLOR], color}, 0142 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime().toString(Qt::TextDate)}, 0143 {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], comment}, 0144 }; 0145 0146 this->db()->insert(TAG::TABLEMAP[TAG::TABLE::TAGS], tag_map); 0147 0148 const QVariantMap appTag_map { 0149 {FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], tag}, 0150 {FMH::MODEL_NAME[FMH::MODEL_KEY::ORG], this->appOrg}, 0151 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime().toString(Qt::TextDate)}}; 0152 0153 if (this->db()->insert(TAG::TABLEMAP[TAG::TABLE::APP_TAGS], appTag_map)) { 0154 setTagIconName(tag_map); 0155 Q_EMIT this->tagged(tag_map); 0156 return true; 0157 } 0158 0159 return false; 0160 } 0161 0162 bool Tagging::tagUrl(const QString &url, const QString &tag, const QString &color, const QString &comment) 0163 { 0164 const auto myTag = tag.trimmed(); 0165 0166 this->tag(myTag, color, comment); 0167 0168 QMimeDatabase mimedb; 0169 0170 QVariantMap tag_url_map {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url}, 0171 {FMH::MODEL_NAME[FMH::MODEL_KEY::TAG], myTag}, 0172 {FMH::MODEL_NAME[FMH::MODEL_KEY::TITLE], QFileInfo(url).baseName()}, 0173 {FMH::MODEL_NAME[FMH::MODEL_KEY::MIME], mimedb.mimeTypeForFile(url).name()}, 0174 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()}, 0175 {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], comment}}; 0176 0177 if(this->db()->insert(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], tag_url_map)) 0178 { 0179 qDebug() << "tagging url" << url <<tag; 0180 Q_EMIT this->urlTagged(url, myTag); 0181 0182 // auto fileMetaData = KFileMetaData::UserMetaData(QUrl::fromUserInput(url).toLocalFile()); 0183 // fileMetaData.setTags({tag}); 0184 0185 return true; 0186 } 0187 0188 return false; 0189 } 0190 0191 bool Tagging::updateUrlTags(const QString &url, const QStringList &tags, const bool &strict) 0192 { 0193 this->removeUrlTags(url, strict); 0194 0195 for (const auto &tag : std::as_const(tags)) 0196 { 0197 this->tagUrl(url, tag); 0198 } 0199 0200 return true; 0201 } 0202 0203 bool Tagging::updateUrl(const QString &url, const QString &newUrl) 0204 { 0205 return this->db()->update(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], {{FMH::MODEL_KEY::URL, newUrl}}, {{FMH::MODEL_NAME[FMH::MODEL_KEY::URL], url}}); 0206 } 0207 0208 QVariantList Tagging::getUrlsTags(const bool &strict) //all used tags, meaning, all tags that are used with an url in tags_url table 0209 { 0210 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")) : 0211 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); 0212 0213 return this->get(query, &setTagIconName); 0214 } 0215 0216 bool Tagging::setTagIconName(QVariantMap &item) 0217 { 0218 item.insert(QStringLiteral("icon"), item.value(QStringLiteral("tag")).toString() == QStringLiteral("fav") ? QStringLiteral("love") : QStringLiteral("tag")); 0219 return true; 0220 } 0221 0222 QVariantList Tagging::getAllTags(const bool &strict) 0223 { 0224 return !strict ? this->get(QStringLiteral("select * from tags"), &setTagIconName) 0225 : 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), 0226 &setTagIconName); 0227 } 0228 0229 QVariantList Tagging::getUrls(const QString &tag, const bool &strict, const int &limit, const QString &mimeType, std::function<bool(QVariantMap &item)> modifier) 0230 { 0231 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) 0232 : this->get(QString(QStringLiteral("select distinct turl.*, t.color, t.comment as tagComment from TAGS t " 0233 "inner join APP_TAGS at on t.tag = at.tag " 0234 "inner join TAGS_URLS turl on turl.tag = t.tag " 0235 "where at.org = '%1' and turl.mime like '%4%' " 0236 "and t.tag = '%2' limit %3")) 0237 .arg(this->appOrg, tag, QString::number(limit), mimeType), 0238 modifier); 0239 } 0240 0241 QVariantList Tagging::getUrlTags(const QString &url, const bool &strict) 0242 { 0243 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)) 0244 : 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 " 0245 "where at.org = '%1' and turl.url = '%2'")) 0246 .arg(this->appOrg, url)); 0247 } 0248 0249 bool Tagging::removeUrlTags(const QString &url, const bool &strict) // same as removing the url from the tags_urls 0250 { 0251 Q_UNUSED(strict) 0252 return this->removeUrl(url); 0253 } 0254 0255 bool Tagging::removeUrlTag(const QString &url, const QString &tag) 0256 { 0257 qDebug() << "Remove url tag" << url << tag; 0258 FMH::MODEL data {{FMH::MODEL_KEY::URL, url}, {FMH::MODEL_KEY::TAG, tag}}; 0259 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], data)) 0260 { 0261 Q_EMIT this->urlTagRemoved(tag, url); 0262 return true; 0263 } 0264 0265 return false; 0266 } 0267 0268 bool Tagging::removeUrl(const QString &url) 0269 { 0270 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], {{FMH::MODEL_KEY::URL, url}})) 0271 { 0272 Q_EMIT this->urlRemoved(url); 0273 } 0274 0275 return false; 0276 } 0277 0278 bool Tagging::app() 0279 { 0280 qDebug() << "REGISTER APP" << this->appName << this->appOrg << this->appComment; 0281 const QVariantMap app_map { 0282 {FMH::MODEL_NAME[FMH::MODEL_KEY::NAME], this->appName}, 0283 {FMH::MODEL_NAME[FMH::MODEL_KEY::ORG], this->appOrg}, 0284 {FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE], QDateTime::currentDateTime()}, 0285 {FMH::MODEL_NAME[FMH::MODEL_KEY::COMMENT], this->appComment}, 0286 }; 0287 0288 return this->db()->insert(TAG::TABLEMAP[TAG::TABLE::APPS], app_map); 0289 } 0290 0291 bool Tagging::removeTag(const QString& tag, const bool &strict) 0292 { 0293 if(strict) 0294 { 0295 FMH::MODEL data0 {{FMH::MODEL_KEY::TAG, tag}, {FMH::MODEL_KEY::ORG, this->appOrg}}; 0296 0297 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::APP_TAGS], data0)) 0298 { 0299 return true; 0300 } 0301 0302 }else 0303 { 0304 FMH::MODEL data1 {{FMH::MODEL_KEY::TAG, tag}}; 0305 0306 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS_URLS], data1)) 0307 { 0308 FMH::MODEL data2 {{FMH::MODEL_KEY::TAG, tag}, {FMH::MODEL_KEY::ORG, this->appOrg}}; 0309 0310 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::APP_TAGS], data2)) 0311 { 0312 if(this->db()->remove(TAG::TABLEMAP[TAG::TABLE::TAGS], data1)) 0313 { 0314 Q_EMIT this->tagRemoved(tag); 0315 return true; 0316 } 0317 } 0318 } 0319 } 0320 0321 0322 0323 return false; 0324 } 0325 0326 static bool doNameFilter(const QString &name, const QStringList &filters) 0327 { 0328 const auto filtersAccumulate = std::accumulate(filters.constBegin(), filters.constEnd(), QVector<QRegularExpression> {}, [](QVector<QRegularExpression> &res, const QString &filter) -> QVector<QRegularExpression> { 0329 QString wildcardExp = QRegularExpression::wildcardToRegularExpression(filter).replace(QStringLiteral("[^/]"), QStringLiteral(".")); 0330 QRegularExpression reg(wildcardExp, QRegularExpression::CaseInsensitiveOption); 0331 res.append(reg); 0332 return res; 0333 }); 0334 0335 for (const auto &filter : filtersAccumulate) { 0336 qDebug() << "trying to match" << name << filter; 0337 if (filter.match(name).hasMatch()) { 0338 qDebug() << "trying to match" << true; 0339 0340 return true; 0341 } 0342 } 0343 return false; 0344 } 0345 0346 QList<QUrl> Tagging::getTagUrls(const QString &tag, const QStringList &filters, const bool &strict, const int &limit, const QString &mime) 0347 { 0348 QList<QUrl> urls; 0349 0350 std::function<bool(QVariantMap & item)> filter = nullptr; 0351 0352 if (!filters.isEmpty()) 0353 { 0354 filter = [filters](QVariantMap &item) -> bool 0355 { 0356 return doNameFilter(FMH::mapValue(item, FMH::MODEL_KEY::URL), filters); 0357 }; 0358 } 0359 0360 const auto tagUrls = getUrls(tag, strict, limit, mime, filter); 0361 for (const auto &data : tagUrls) 0362 { 0363 const auto url = QUrl(data.toMap()[FMH::MODEL_NAME[FMH::MODEL_KEY::URL]].toString()); 0364 if (url.isLocalFile() && !FMH::fileExists(url)) 0365 continue; 0366 urls << url; 0367 } 0368 0369 return urls; 0370 } 0371 0372 FMH::MODEL_LIST Tagging::getTags(const int &limit) 0373 { 0374 Q_UNUSED(limit); 0375 FMH::MODEL_LIST data; 0376 const auto tags = getAllTags(false); 0377 for (const auto &tag : tags) 0378 { 0379 const QVariantMap item = tag.toMap(); 0380 const auto label = item.value(FMH::MODEL_NAME[FMH::MODEL_KEY::TAG]).toString(); 0381 0382 data << FMH::MODEL {{FMH::MODEL_KEY::PATH, FMStatic::PATHTYPE_URI[FMStatic::PATHTYPE_KEY::TAGS_PATH] + label}, 0383 {FMH::MODEL_KEY::ICON, item.value(FMH::MODEL_NAME[FMH::MODEL_KEY::ICON], QStringLiteral("tag")).toString()}, 0384 {FMH::MODEL_KEY::MODIFIED, QDateTime::fromString(item.value(FMH::MODEL_NAME[FMH::MODEL_KEY::ADDDATE]).toString(), Qt::TextDate).toString()}, 0385 {FMH::MODEL_KEY::IS_DIR, QStringLiteral("true")}, 0386 {FMH::MODEL_KEY::LABEL, label}, 0387 {FMH::MODEL_KEY::TYPE, FMStatic::PathTypeLabel(FMStatic::PATHTYPE_KEY::TAGS_PATH)}}; 0388 } 0389 0390 return data; 0391 } 0392 0393 FMH::MODEL_LIST Tagging::getUrlTags(const QUrl &url) 0394 { 0395 return FMH::toModelList(this->getUrlTags(url.toString(), false)); 0396 } 0397 0398 bool Tagging::addTagToUrl(const QString tag, const QUrl &url) 0399 { 0400 return this->tagUrl(url.toString(), tag); 0401 } 0402 0403 bool Tagging::removeTagToUrl(const QString tag, const QUrl &url) 0404 { 0405 return this->removeUrlTag(url.toString(), tag); 0406 } 0407 0408 bool Tagging::toggleFav(const QUrl &url) 0409 { 0410 if (this->isFav(url)) 0411 return this->unFav(url); 0412 0413 return this->fav(url); 0414 } 0415 0416 bool Tagging::fav(const QUrl &url) 0417 { 0418 return this->tagUrl(url.toString(), QStringLiteral("fav"), QStringLiteral("#e91e63")); 0419 } 0420 0421 bool Tagging::unFav(const QUrl &url) 0422 { 0423 return this->removeUrlTag(url.toString(), QStringLiteral("fav")); 0424 } 0425 0426 bool Tagging::isFav(const QUrl &url, const bool &strict) 0427 { 0428 Q_UNUSED(strict) 0429 0430 return urlTagExists(url.toString(), QStringLiteral("fav")); 0431 } 0432