File indexing completed on 2025-01-26 05:00:55

0001 /*
0002  *   SPDX-FileCopyrightText: 2011, 2012, 2013, 2014, 2015 Ivan Cukic <ivan.cukic(at)kde.org>
0003  *
0004  *   SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 // Self
0008 #include "ResourceLinking.h"
0009 #include <kactivities-features.h>
0010 
0011 // Qt
0012 #include <QDBusConnection>
0013 #include <QFileSystemWatcher>
0014 #include <QSqlQuery>
0015 
0016 // KDE
0017 #include <kconfig.h>
0018 #include <kdirnotify.h>
0019 
0020 // Boost
0021 #include <boost/range/algorithm/binary_search.hpp>
0022 #include <utils/range.h>
0023 
0024 // Local
0025 #include "Database.h"
0026 #include "DebugResources.h"
0027 #include "StatsPlugin.h"
0028 #include "Utils.h"
0029 #include "resourcelinkingadaptor.h"
0030 
0031 ResourceLinking::ResourceLinking(QObject *parent)
0032     : QObject(parent)
0033 {
0034     new ResourcesLinkingAdaptor(this);
0035     QDBusConnection::sessionBus().registerObject(QStringLiteral("/ActivityManager/Resources/Linking"), this);
0036 }
0037 
0038 void ResourceLinking::init()
0039 {
0040     auto activities = StatsPlugin::self()->activitiesInterface();
0041 
0042     connect(activities, SIGNAL(CurrentActivityChanged(QString)), this, SLOT(onCurrentActivityChanged(QString)));
0043     connect(activities, SIGNAL(ActivityAdded(QString)), this, SLOT(onActivityAdded(QString)));
0044     connect(activities, SIGNAL(ActivityRemoved(QString)), this, SLOT(onActivityRemoved(QString)));
0045 }
0046 
0047 void ResourceLinking::LinkResourceToActivity(QString initiatingAgent, QString targettedResource, QString usedActivity)
0048 {
0049     qCDebug(KAMD_LOG_RESOURCES) << "Linking " << targettedResource << " to " << usedActivity << " from " << initiatingAgent;
0050 
0051     if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) {
0052         qCWarning(KAMD_LOG_RESOURCES) << "Invalid arguments" << initiatingAgent << targettedResource << usedActivity;
0053         return;
0054     }
0055 
0056     if (usedActivity == ":any") {
0057         usedActivity = ":global";
0058     }
0059 
0060     Q_ASSERT_X(!initiatingAgent.isEmpty(), "ResourceLinking::LinkResourceToActivity", "Agent should not be empty");
0061     Q_ASSERT_X(!usedActivity.isEmpty(), "ResourceLinking::LinkResourceToActivity", "Activity should not be empty");
0062     Q_ASSERT_X(!targettedResource.isEmpty(), "ResourceLinking::LinkResourceToActivity", "Resource should not be empty");
0063 
0064     Utils::prepare(*resourcesDatabase(),
0065                    linkResourceToActivityQuery,
0066                    QStringLiteral("INSERT OR REPLACE INTO ResourceLink"
0067                                   "        (usedActivity,  initiatingAgent,  targettedResource) "
0068                                   "VALUES ( "
0069                                   "COALESCE(:usedActivity,''),"
0070                                   "COALESCE(:initiatingAgent,''),"
0071                                   "COALESCE(:targettedResource,'')"
0072                                   ")"));
0073 
0074     DATABASE_TRANSACTION(*resourcesDatabase());
0075 
0076     Utils::exec(*resourcesDatabase(),
0077                 Utils::FailOnError,
0078                 *linkResourceToActivityQuery,
0079                 ":usedActivity",
0080                 usedActivity,
0081                 ":initiatingAgent",
0082                 initiatingAgent,
0083                 ":targettedResource",
0084                 targettedResource);
0085 
0086     if (!usedActivity.isEmpty()) {
0087         // qCDebug(KAMD_LOG_RESOURCES) << "Sending link event added: activities:/" << usedActivity;
0088         org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/") + usedActivity));
0089 
0090         if (usedActivity == StatsPlugin::self()->currentActivity()) {
0091             // qCDebug(KAMD_LOG_RESOURCES) << "Sending link event added: activities:/current";
0092             org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/current")));
0093         }
0094     }
0095 
0096     Q_EMIT ResourceLinkedToActivity(initiatingAgent, targettedResource, usedActivity);
0097 }
0098 
0099 void ResourceLinking::UnlinkResourceFromActivity(QString initiatingAgent, QString targettedResource, QString usedActivity)
0100 {
0101     // qCDebug(KAMD_LOG_RESOURCES) << "Unlinking " << targettedResource << " from " << usedActivity << " from " << initiatingAgent;
0102 
0103     if (!validateArguments(initiatingAgent, targettedResource, usedActivity, false)) {
0104         qCWarning(KAMD_LOG_RESOURCES) << "Invalid arguments" << initiatingAgent << targettedResource << usedActivity;
0105         return;
0106     }
0107 
0108     Q_ASSERT_X(!initiatingAgent.isEmpty(), "ResourceLinking::UnlinkResourceFromActivity", "Agent should not be empty");
0109     Q_ASSERT_X(!usedActivity.isEmpty(), "ResourceLinking::UnlinkResourceFromActivity", "Activity should not be empty");
0110     Q_ASSERT_X(!targettedResource.isEmpty(), "ResourceLinking::UnlinkResourceFromActivity", "Resource should not be empty");
0111 
0112     QSqlQuery *query = nullptr;
0113 
0114     if (usedActivity == ":any") {
0115         Utils::prepare(*resourcesDatabase(),
0116                        unlinkResourceFromAllActivitiesQuery,
0117                        QStringLiteral("DELETE FROM ResourceLink "
0118                                       "WHERE "
0119                                       "initiatingAgent   = COALESCE(:initiatingAgent  , '') AND "
0120                                       "(targettedResource = COALESCE(:targettedResource, '') OR "
0121                                       "(initiatingAgent = 'org.kde.plasma.favorites.applications' "
0122                                       "AND targettedResource = 'applications:' || COALESCE(:targettedResource, '')))"));
0123         query = unlinkResourceFromAllActivitiesQuery.get();
0124     } else {
0125         Utils::prepare(*resourcesDatabase(),
0126                        unlinkResourceFromActivityQuery,
0127                        QStringLiteral("DELETE FROM ResourceLink "
0128                                       "WHERE "
0129                                       "usedActivity      = COALESCE(:usedActivity     , '') AND "
0130                                       "initiatingAgent   = COALESCE(:initiatingAgent  , '') AND "
0131                                       "(targettedResource = COALESCE(:targettedResource, '') OR "
0132                                       "(initiatingAgent = 'org.kde.plasma.favorites.applications'"
0133                                       "AND targettedResource =  'applications:' || COALESCE(:targettedResource, '')))"));
0134         query = unlinkResourceFromActivityQuery.get();
0135     }
0136 
0137     DATABASE_TRANSACTION(*resourcesDatabase());
0138     // BUG 385814, some existing entries don't have the applications:
0139     // prefix, so we remove it and check in the sql if they match
0140     // TODO Remove when we can expect all users to have a fresher install than 5.18
0141     if (initiatingAgent == QLatin1String("org.kde.plasma.favorites.applications")) {
0142         targettedResource = targettedResource.remove(QLatin1String("applications:"));
0143     }
0144     Utils::exec(*resourcesDatabase(),
0145                 Utils::FailOnError,
0146                 *query,
0147                 ":usedActivity",
0148                 usedActivity,
0149                 ":initiatingAgent",
0150                 initiatingAgent,
0151                 ":targettedResource",
0152                 targettedResource);
0153 
0154     if (!usedActivity.isEmpty()) {
0155         // auto mangled = QString::fromUtf8(QUrl::toPercentEncoding(targettedResource));
0156         auto mangled = QString::fromLatin1(targettedResource.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals));
0157 
0158         // qCDebug(KAMD_LOG_RESOURCES) << "Sending link event removed: activities:/" << usedActivity << '/' << mangled;
0159         org::kde::KDirNotify::emitFilesRemoved({QUrl(QStringLiteral("activities:/") + usedActivity + '/' + mangled)});
0160 
0161         if (usedActivity == StatsPlugin::self()->currentActivity()) {
0162             // qCDebug(KAMD_LOG_RESOURCES) << "Sending link event removed: activities:/current/" << mangled;
0163             org::kde::KDirNotify::emitFilesRemoved({QUrl(QStringLiteral("activities:/current/") + mangled)});
0164         }
0165     }
0166 
0167     Q_EMIT ResourceUnlinkedFromActivity(initiatingAgent, targettedResource, usedActivity);
0168 }
0169 
0170 bool ResourceLinking::IsResourceLinkedToActivity(QString initiatingAgent, QString targettedResource, QString usedActivity)
0171 {
0172     if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) {
0173         return false;
0174     }
0175 
0176     Q_ASSERT_X(!initiatingAgent.isEmpty(), "ResourceLinking::IsResourceLinkedToActivity", "Agent should not be empty");
0177     Q_ASSERT_X(!usedActivity.isEmpty(), "ResourceLinking::IsResourceLinkedToActivity", "Activity should not be empty");
0178     Q_ASSERT_X(!targettedResource.isEmpty(), "ResourceLinking::IsResourceLinkedToActivity", "Resource should not be empty");
0179 
0180     Utils::prepare(*resourcesDatabase(),
0181                    isResourceLinkedToActivityQuery,
0182                    QStringLiteral("SELECT * FROM ResourceLink "
0183                                   "WHERE "
0184                                   "usedActivity      = COALESCE(:usedActivity     , '') AND "
0185                                   "initiatingAgent   = COALESCE(:initiatingAgent  , '') AND "
0186                                   "targettedResource = COALESCE(:targettedResource, '') "));
0187 
0188     Utils::exec(*resourcesDatabase(),
0189                 Utils::FailOnError,
0190                 *isResourceLinkedToActivityQuery,
0191                 ":usedActivity",
0192                 usedActivity,
0193                 ":initiatingAgent",
0194                 initiatingAgent,
0195                 ":targettedResource",
0196                 targettedResource);
0197 
0198     return isResourceLinkedToActivityQuery->next();
0199 }
0200 
0201 bool ResourceLinking::validateArguments(QString &initiatingAgent, QString &targettedResource, QString &usedActivity, bool checkFilesExist)
0202 {
0203     // Validating targetted resource
0204     if (targettedResource.isEmpty()) {
0205         qCDebug(KAMD_LOG_RESOURCES) << "Resource is invalid -- empty";
0206         return false;
0207     }
0208 
0209     if (targettedResource.startsWith(QStringLiteral("file://"))) {
0210         targettedResource = QUrl(targettedResource).toLocalFile();
0211     }
0212 
0213     if (targettedResource.startsWith(QStringLiteral("/")) && checkFilesExist) {
0214         QFileInfo file(targettedResource);
0215 
0216         if (!file.exists()) {
0217             qCDebug(KAMD_LOG_RESOURCES) << "Resource is invalid -- the file does not exist";
0218             return false;
0219         }
0220 
0221         targettedResource = file.canonicalFilePath();
0222     }
0223 
0224     // Handling special values for the agent
0225     if (initiatingAgent.isEmpty()) {
0226         initiatingAgent = ":global";
0227     }
0228 
0229     // Handling special values for activities
0230     if (usedActivity == ":current") {
0231         usedActivity = StatsPlugin::self()->currentActivity();
0232 
0233     } else if (usedActivity.isEmpty()) {
0234         usedActivity = ":global";
0235     }
0236 
0237     // If the activity is not empty and the passed activity
0238     // does not exist, cancel the request
0239     if (!usedActivity.isEmpty() && usedActivity != ":global" && usedActivity != ":any" && !StatsPlugin::self()->listActivities().contains(usedActivity)) {
0240         qCDebug(KAMD_LOG_RESOURCES) << "Activity is invalid, it does not exist";
0241         return false;
0242     }
0243 
0244     // qCDebug(KAMD_LOG_RESOURCES) << "agent" << initiatingAgent
0245     //                             << "resource" << targettedResource
0246     //                             << "activity" << usedActivity;
0247 
0248     return true;
0249 }
0250 
0251 void ResourceLinking::onActivityAdded(const QString &activity)
0252 {
0253     Q_UNUSED(activity);
0254 
0255     // Notify KIO
0256     // qCDebug(KAMD_LOG_RESOURCES) << "Added: activities:/  (" << activity << ")";
0257     org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/")));
0258 }
0259 
0260 void ResourceLinking::onActivityRemoved(const QString &activity)
0261 {
0262     // Notify KIO
0263     // qCDebug(KAMD_LOG_RESOURCES) << "Removed: activities:/" << activity;
0264     org::kde::KDirNotify::emitFilesRemoved({QUrl(QStringLiteral("activities:/") + activity)});
0265 
0266     // Remove statistics for the activity
0267 }
0268 
0269 void ResourceLinking::onCurrentActivityChanged(const QString &activity)
0270 {
0271     Q_UNUSED(activity);
0272 
0273     // Notify KIO
0274     // qCDebug(KAMD_LOG_RESOURCES) << "Changed: activities:/current -> " << activity;
0275     org::kde::KDirNotify::emitFilesAdded({QUrl(QStringLiteral("activities:/current"))});
0276 }
0277 
0278 #include "moc_ResourceLinking.cpp"