Warning, file /frameworks/kactivities-stats/src/resultwatcher.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2015, 2016 Ivan Cukic <ivan.cukic(at)kde.org> 0003 0004 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL 0005 */ 0006 0007 #include "resultwatcher.h" 0008 0009 // Qt 0010 #include <QCoreApplication> 0011 #include <QList> 0012 #include <QRegularExpression> 0013 #include <QSqlError> 0014 #include <QSqlQuery> 0015 0016 // Local 0017 #include "kactivities-stats-logsettings.h" 0018 #include <common/database/Database.h> 0019 #include <utils/debug_and_return.h> 0020 0021 // STL 0022 #include <functional> 0023 #include <iterator> 0024 #include <limits> 0025 #include <mutex> 0026 0027 // KActivities 0028 #include <kactivities/consumer.h> 0029 0030 #include "activitiessync_p.h" 0031 #include "common/dbus/common.h" 0032 #include "common/specialvalues.h" 0033 #include "resourceslinking_interface.h" 0034 #include "resourcesscoring_interface.h" 0035 #include "utils/lazy_val.h" 0036 #include "utils/qsqlquery_iterator.h" 0037 0038 #include <algorithm> 0039 0040 #define QDBG qCDebug(KACTIVITIES_STATS_LOG) << "KActivitiesStats(" << (void *)this << ")" 0041 0042 namespace KActivities 0043 { 0044 namespace Stats 0045 { 0046 // Main class 0047 0048 class ResultWatcherPrivate 0049 { 0050 public: 0051 mutable ActivitiesSync::ConsumerPtr activities; 0052 QList<QRegularExpression> urlFilters; 0053 0054 ResultWatcherPrivate(ResultWatcher *parent, Query query) 0055 : linking(new KAMD_DBUS_CLASS_INTERFACE("Resources/Linking", ResourcesLinking, nullptr)) 0056 , scoring(new KAMD_DBUS_CLASS_INTERFACE("Resources/Scoring", ResourcesScoring, nullptr)) 0057 , q(parent) 0058 , query(query) 0059 { 0060 for (const auto &urlFilter : query.urlFilters()) { 0061 urlFilters << Common::starPatternToRegex(urlFilter); 0062 } 0063 0064 m_resultInvalidationTimer.setSingleShot(true); 0065 m_resultInvalidationTimer.setInterval(200); 0066 QObject::connect(&m_resultInvalidationTimer, &QTimer::timeout, q, Q_EMIT & ResultWatcher::resultsInvalidated); 0067 } 0068 0069 template<typename Collection, typename Predicate> 0070 inline bool any_of(const Collection &collection, Predicate &&predicate) const 0071 { 0072 const auto begin = collection.cbegin(); 0073 const auto end = collection.cend(); 0074 0075 return begin == end || std::any_of(begin, end, std::forward<Predicate>(predicate)); 0076 } 0077 0078 #define DEBUG_MATCHERS 0 0079 0080 // Processing the list of activities as specified by the query. 0081 // If it contains :any, we are returning true, otherwise 0082 // we want to match a specific activity (be it the current 0083 // activity or not). The :global special value is not special here 0084 bool activityMatches(const QString &activity) const 0085 { 0086 #if DEBUG_MATCHERS 0087 qCDebug(KACTIVITIES_STATS_LOG) << "Activity " << activity << "matching against" << query.activities(); 0088 #endif 0089 0090 return kamd::utils::debug_and_return(DEBUG_MATCHERS, 0091 " -> returning ", 0092 activity == ANY_ACTIVITY_TAG || any_of(query.activities(), [&](const QString &matcher) { 0093 return matcher == ANY_ACTIVITY_TAG ? true 0094 : matcher == CURRENT_ACTIVITY_TAG 0095 ? (matcher == activity || activity == ActivitiesSync::currentActivity(activities)) 0096 : activity == matcher; 0097 })); 0098 } 0099 0100 // Same as above, but for agents 0101 bool agentMatches(const QString &agent) const 0102 { 0103 #if DEBUG_MATCHERS 0104 qCDebug(KACTIVITIES_STATS_LOG) << "Agent " << agent << "matching against" << query.agents(); 0105 #endif 0106 0107 return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", agent == ANY_AGENT_TAG || any_of(query.agents(), [&](const QString &matcher) { 0108 return matcher == ANY_AGENT_TAG ? true 0109 : matcher == CURRENT_AGENT_TAG 0110 ? (matcher == agent || agent == QCoreApplication::applicationName()) 0111 : agent == matcher; 0112 })); 0113 } 0114 0115 // Same as above, but for urls 0116 bool urlMatches(const QString &url) const 0117 { 0118 #if DEBUG_MATCHERS 0119 qCDebug(KACTIVITIES_STATS_LOG) << "Url " << url << "matching against" << urlFilters; 0120 #endif 0121 0122 return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", any_of(urlFilters, [&](const QRegularExpression &matcher) { 0123 return matcher.match(url).hasMatch(); 0124 })); 0125 } 0126 0127 bool typeMatches(const QString &resource) const 0128 { 0129 // We don't necessarily need to retrieve the type from 0130 // the database. If we do, get it only once 0131 auto type = kamd::utils::make_lazy_val([&]() -> QString { 0132 using Common::Database; 0133 0134 auto database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); 0135 0136 if (!database) { 0137 return QString(); 0138 } 0139 0140 auto query = database->execQuery(QStringLiteral("SELECT mimetype FROM ResourceInfo WHERE " 0141 "targettedResource = '") 0142 + resource + QStringLiteral("'")); 0143 0144 for (const auto &item : query) { 0145 return item[0].toString(); 0146 } 0147 0148 return QString(); 0149 }); 0150 0151 #if DEBUG_MATCHERS 0152 qCDebug(KACTIVITIES_STATS_LOG) << "Type " 0153 << "...type..." 0154 << "matching against" << query.types(); 0155 qCDebug(KACTIVITIES_STATS_LOG) << "ANY_TYPE_TAG" << ANY_TYPE_TAG; 0156 #endif 0157 0158 return kamd::utils::debug_and_return(DEBUG_MATCHERS, " -> returning ", any_of(query.types(), [&](const QString &matcher) { 0159 if (matcher == ANY_TYPE_TAG) { 0160 return true; 0161 } 0162 0163 const QString _type = type; 0164 return matcher == ANY_TYPE_TAG 0165 || (matcher == FILES_TYPE_TAG && !_type.isEmpty() && _type != QStringLiteral("inode/directory")) 0166 || (matcher == DIRECTORIES_TYPE_TAG && _type == QLatin1String("inode/directory")) || matcher == type; 0167 })); 0168 } 0169 0170 bool eventMatches(const QString &agent, const QString &resource, const QString &activity) const 0171 { 0172 // The order of checks is not arbitrary, it is sorted 0173 // from the cheapest, to the most expensive 0174 return kamd::utils::debug_and_return(DEBUG_MATCHERS, 0175 "event matches?", 0176 agentMatches(agent) && activityMatches(activity) && urlMatches(resource) && typeMatches(resource)); 0177 } 0178 0179 void onResourceLinkedToActivity(const QString &agent, const QString &resource, const QString &activity) 0180 { 0181 #if DEBUG_MATCHERS 0182 qCDebug(KACTIVITIES_STATS_LOG) << "Resource has been linked: " << agent << resource << activity; 0183 #endif 0184 0185 // The used resources do not really care about the linked ones 0186 if (query.selection() == Terms::UsedResources) { 0187 return; 0188 } 0189 0190 if (!eventMatches(agent, resource, activity)) { 0191 return; 0192 } 0193 0194 // TODO: See whether it makes sense to have 0195 // lastUpdate/firstUpdate here as well 0196 Q_EMIT q->resultLinked(resource); 0197 } 0198 0199 void onResourceUnlinkedFromActivity(const QString &agent, const QString &resource, const QString &activity) 0200 { 0201 #if DEBUG_MATCHERS 0202 qCDebug(KACTIVITIES_STATS_LOG) << "Resource unlinked: " << agent << resource << activity; 0203 #endif 0204 0205 // The used resources do not really care about the linked ones 0206 if (query.selection() == Terms::UsedResources) { 0207 return; 0208 } 0209 0210 if (!eventMatches(agent, resource, activity)) { 0211 return; 0212 } 0213 0214 Q_EMIT q->resultUnlinked(resource); 0215 } 0216 0217 #undef DEBUG_MATCHERS 0218 0219 void onResourceScoreUpdated(const QString &activity, const QString &agent, const QString &resource, double score, uint lastUpdate, uint firstUpdate) 0220 { 0221 Q_ASSERT_X(activity == QLatin1String("00000000-0000-0000-0000-000000000000") || !QUuid(activity).isNull(), 0222 "ResultWatcher::onResourceScoreUpdated", 0223 "The activity should be always specified here, no magic values"); 0224 0225 // The linked resources do not really care about the stats 0226 if (query.selection() == Terms::LinkedResources) { 0227 return; 0228 } 0229 0230 if (!eventMatches(agent, resource, activity)) { 0231 return; 0232 } 0233 0234 Q_EMIT q->resultScoreUpdated(resource, score, lastUpdate, firstUpdate); 0235 } 0236 0237 void onEarlierStatsDeleted(QString, int) 0238 { 0239 // The linked resources do not really care about the stats 0240 if (query.selection() == Terms::LinkedResources) { 0241 return; 0242 } 0243 0244 scheduleResultsInvalidation(); 0245 } 0246 0247 void onRecentStatsDeleted(QString, int, QString) 0248 { 0249 // The linked resources do not really care about the stats 0250 if (query.selection() == Terms::LinkedResources) { 0251 return; 0252 } 0253 0254 scheduleResultsInvalidation(); 0255 } 0256 0257 void onStatsForResourceDeleted(const QString &activity, const QString &agent, const QString &resource) 0258 { 0259 if (query.selection() == Terms::LinkedResources) { 0260 return; 0261 } 0262 0263 if (activityMatches(activity) && agentMatches(agent)) { 0264 if (resource.contains(QLatin1Char('*'))) { 0265 scheduleResultsInvalidation(); 0266 0267 } else if (typeMatches(resource)) { 0268 if (!m_resultInvalidationTimer.isActive()) { 0269 // Remove a result only if we haven't an invalidation 0270 // request scheduled 0271 q->resultRemoved(resource); 0272 } 0273 } 0274 } 0275 } 0276 0277 // Lets not send a lot of invalidation events at once 0278 QTimer m_resultInvalidationTimer; 0279 void scheduleResultsInvalidation() 0280 { 0281 QDBG << "Scheduling invalidation"; 0282 m_resultInvalidationTimer.start(); 0283 } 0284 0285 std::unique_ptr<org::kde::ActivityManager::ResourcesLinking> linking; 0286 std::unique_ptr<org::kde::ActivityManager::ResourcesScoring> scoring; 0287 0288 ResultWatcher *const q; 0289 Query query; 0290 }; 0291 0292 ResultWatcher::ResultWatcher(Query query, QObject *parent) 0293 : QObject(parent) 0294 , d(new ResultWatcherPrivate(this, query)) 0295 { 0296 using namespace org::kde::ActivityManager; 0297 using namespace std::placeholders; 0298 0299 // There is no need for private slots, when we have bind 0300 0301 // Connecting the linking service 0302 QObject::connect(d->linking.get(), 0303 &ResourcesLinking::ResourceLinkedToActivity, 0304 this, 0305 std::bind(&ResultWatcherPrivate::onResourceLinkedToActivity, d, _1, _2, _3)); 0306 QObject::connect(d->linking.get(), 0307 &ResourcesLinking::ResourceUnlinkedFromActivity, 0308 this, 0309 std::bind(&ResultWatcherPrivate::onResourceUnlinkedFromActivity, d, _1, _2, _3)); 0310 0311 // Connecting the scoring service 0312 QObject::connect(d->scoring.get(), 0313 &ResourcesScoring::ResourceScoreUpdated, 0314 this, 0315 std::bind(&ResultWatcherPrivate::onResourceScoreUpdated, d, _1, _2, _3, _4, _5, _6)); 0316 QObject::connect(d->scoring.get(), 0317 &ResourcesScoring::ResourceScoreDeleted, 0318 this, 0319 std::bind(&ResultWatcherPrivate::onStatsForResourceDeleted, d, _1, _2, _3)); 0320 QObject::connect(d->scoring.get(), &ResourcesScoring::RecentStatsDeleted, this, std::bind(&ResultWatcherPrivate::onRecentStatsDeleted, d, _1, _2, _3)); 0321 QObject::connect(d->scoring.get(), &ResourcesScoring::EarlierStatsDeleted, this, std::bind(&ResultWatcherPrivate::onEarlierStatsDeleted, d, _1, _2)); 0322 } 0323 0324 ResultWatcher::~ResultWatcher() 0325 { 0326 delete d; 0327 } 0328 0329 void ResultWatcher::linkToActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) 0330 { 0331 const auto activities = (!activity.values.isEmpty()) ? activity.values 0332 : (!d->query.activities().isEmpty()) ? d->query.activities() 0333 : Terms::Activity::current().values; 0334 const auto agents = (!agent.values.isEmpty()) ? agent.values : (!d->query.agents().isEmpty()) ? d->query.agents() : Terms::Agent::current().values; 0335 0336 for (const auto &activity : activities) { 0337 for (const auto &agent : agents) { 0338 d->linking->LinkResourceToActivity(agent, resource.toString(), activity); 0339 } 0340 } 0341 } 0342 0343 void ResultWatcher::unlinkFromActivity(const QUrl &resource, const Terms::Activity &activity, const Terms::Agent &agent) 0344 { 0345 const auto activities = !activity.values.isEmpty() ? activity.values 0346 : !d->query.activities().isEmpty() ? d->query.activities() 0347 : Terms::Activity::current().values; 0348 const auto agents = !agent.values.isEmpty() ? agent.values : !d->query.agents().isEmpty() ? d->query.agents() : Terms::Agent::current().values; 0349 0350 for (const auto &activity : activities) { 0351 for (const auto &agent : agents) { 0352 qCDebug(KACTIVITIES_STATS_LOG) << "Unlink " << agent << resource << activity; 0353 d->linking->UnlinkResourceFromActivity(agent, resource.toString(), activity); 0354 } 0355 } 0356 } 0357 0358 } // namespace Stats 0359 } // namespace KActivities