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