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"