File indexing completed on 2025-01-26 05:00:55
0001 /* 0002 * SPDX-FileCopyrightText: 2011, 2012 Ivan Cukic <ivan.cukic(at)kde.org> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 // Self 0008 #include "ResourceScoreCache.h" 0009 #include <kactivities-features.h> 0010 0011 // STD 0012 #include <cmath> 0013 0014 // Utils 0015 #include <utils/d_ptr_implementation.h> 0016 #include <utils/qsqlquery_iterator.h> 0017 0018 // Local 0019 #include "Database.h" 0020 #include "DebugResources.h" 0021 #include "StatsPlugin.h" 0022 #include "Utils.h" 0023 0024 class ResourceScoreCache::Queries 0025 { 0026 private: 0027 Queries() 0028 : createResourceScoreCacheQuery(resourcesDatabase()->createQuery()) 0029 , getResourceScoreCacheQuery(resourcesDatabase()->createQuery()) 0030 , updateResourceScoreCacheQuery(resourcesDatabase()->createQuery()) 0031 , getScoreAdditionQuery(resourcesDatabase()->createQuery()) 0032 { 0033 Utils::prepare(*resourcesDatabase(), 0034 createResourceScoreCacheQuery, 0035 QStringLiteral("INSERT INTO ResourceScoreCache " 0036 "VALUES (:usedActivity, :initiatingAgent, :targettedResource, " 0037 "0, 0, " // type, score 0038 ":firstUpdate, " // lastUpdate 0039 ":firstUpdate)")); 0040 0041 Utils::prepare(*resourcesDatabase(), 0042 getResourceScoreCacheQuery, 0043 QStringLiteral("SELECT cachedScore, lastUpdate, firstUpdate FROM ResourceScoreCache " 0044 "WHERE " 0045 ":usedActivity = usedActivity AND " 0046 ":initiatingAgent = initiatingAgent AND " 0047 ":targettedResource = targettedResource ")); 0048 0049 Utils::prepare(*resourcesDatabase(), 0050 updateResourceScoreCacheQuery, 0051 QStringLiteral("UPDATE ResourceScoreCache SET " 0052 "cachedScore = :cachedScore, " 0053 "lastUpdate = :lastUpdate " 0054 "WHERE " 0055 ":usedActivity = usedActivity AND " 0056 ":initiatingAgent = initiatingAgent AND " 0057 ":targettedResource = targettedResource ")); 0058 0059 Utils::prepare(*resourcesDatabase(), 0060 getScoreAdditionQuery, 0061 QStringLiteral("SELECT start, end " 0062 "FROM ResourceEvent " 0063 "WHERE " 0064 ":usedActivity = usedActivity AND " 0065 ":initiatingAgent = initiatingAgent AND " 0066 ":targettedResource = targettedResource AND " 0067 "start > :start " 0068 "ORDER BY " 0069 "start ASC")); 0070 } 0071 0072 public: 0073 QSqlQuery createResourceScoreCacheQuery; 0074 QSqlQuery getResourceScoreCacheQuery; 0075 QSqlQuery updateResourceScoreCacheQuery; 0076 QSqlQuery getScoreAdditionQuery; 0077 0078 static Queries &self(); 0079 }; 0080 0081 ResourceScoreCache::Queries &ResourceScoreCache::Queries::self() 0082 { 0083 static Queries queries; 0084 return queries; 0085 } 0086 0087 class ResourceScoreCache::Private 0088 { 0089 public: 0090 QString activity; 0091 QString application; 0092 QString resource; 0093 0094 inline qreal timeFactor(int days) const 0095 { 0096 // Exp is falling rather quickly, we are slowing it 32 times 0097 return std::exp(-days / 32.0); 0098 } 0099 0100 inline qreal timeFactor(const QDateTime &fromTime, const QDateTime &toTime) const 0101 { 0102 return timeFactor(fromTime.daysTo(toTime)); 0103 } 0104 }; 0105 0106 ResourceScoreCache::ResourceScoreCache(const QString &activity, const QString &application, const QString &resource) 0107 { 0108 d->activity = activity; 0109 d->application = application; 0110 d->resource = resource; 0111 0112 Q_ASSERT_X(!d->application.isEmpty(), "ResourceScoreCache::constructor", "Agent should not be empty"); 0113 Q_ASSERT_X(!d->activity.isEmpty(), "ResourceScoreCache::constructor", "Activity should not be empty"); 0114 Q_ASSERT_X(!d->resource.isEmpty(), "ResourceScoreCache::constructor", "Resource should not be empty"); 0115 } 0116 0117 ResourceScoreCache::~ResourceScoreCache() 0118 { 0119 } 0120 0121 void ResourceScoreCache::update() 0122 { 0123 QDateTime lastUpdate; 0124 QDateTime firstUpdate; 0125 QDateTime currentTime = QDateTime::currentDateTime(); 0126 qreal score = 0; 0127 0128 DATABASE_TRANSACTION(*resourcesDatabase()); 0129 0130 qCDebug(KAMD_LOG_RESOURCES) << "Creating the cache for: " << d->resource; 0131 0132 // This can fail if we have the cache already made 0133 auto isCacheNew = Utils::exec(*resourcesDatabase(), 0134 Utils::IgnoreError, 0135 Queries::self().createResourceScoreCacheQuery, 0136 ":usedActivity", 0137 d->activity, 0138 ":initiatingAgent", 0139 d->application, 0140 ":targettedResource", 0141 d->resource, 0142 ":firstUpdate", 0143 currentTime.toSecsSinceEpoch()); 0144 0145 // Getting the old score 0146 Utils::exec(*resourcesDatabase(), 0147 Utils::FailOnError, 0148 Queries::self().getResourceScoreCacheQuery, 0149 ":usedActivity", 0150 d->activity, 0151 ":initiatingAgent", 0152 d->application, 0153 ":targettedResource", 0154 d->resource); 0155 0156 // Only and always one result 0157 for (const auto &result : Queries::self().getResourceScoreCacheQuery) { 0158 lastUpdate.setSecsSinceEpoch(result["lastUpdate"].toUInt()); 0159 firstUpdate.setSecsSinceEpoch(result["firstUpdate"].toUInt()); 0160 0161 qCDebug(KAMD_LOG_RESOURCES) << "Already in database? " << (!isCacheNew); 0162 qCDebug(KAMD_LOG_RESOURCES) << " First update : " << firstUpdate; 0163 qCDebug(KAMD_LOG_RESOURCES) << " Last update : " << lastUpdate; 0164 0165 if (isCacheNew) { 0166 // If we haven't had the cache before, set the score to 0 0167 firstUpdate = currentTime; 0168 score = 0; 0169 0170 } else { 0171 // Adjusting the score depending on the time that passed since the 0172 // last update 0173 score = result["cachedScore"].toReal(); 0174 score *= d->timeFactor(lastUpdate, currentTime); 0175 } 0176 } 0177 0178 // Calculating the updated score 0179 // We are processing all events since the last cache update 0180 0181 qCDebug(KAMD_LOG_RESOURCES) << "After the adjustment"; 0182 qCDebug(KAMD_LOG_RESOURCES) << " Current score : " << score; 0183 qCDebug(KAMD_LOG_RESOURCES) << " First update : " << firstUpdate; 0184 qCDebug(KAMD_LOG_RESOURCES) << " Last update : " << lastUpdate; 0185 0186 Utils::exec(*resourcesDatabase(), 0187 Utils::FailOnError, 0188 Queries::self().getScoreAdditionQuery, 0189 ":usedActivity", 0190 d->activity, 0191 ":initiatingAgent", 0192 d->application, 0193 ":targettedResource", 0194 d->resource, 0195 ":start", 0196 lastUpdate.toSecsSinceEpoch()); 0197 0198 uint lastEventStart = currentTime.toSecsSinceEpoch(); 0199 0200 for (const auto &result : Queries::self().getScoreAdditionQuery) { 0201 lastEventStart = result["start"].toUInt(); 0202 0203 const auto end = result["end"].toUInt(); 0204 const auto intervalLength = end - lastEventStart; 0205 0206 qCDebug(KAMD_LOG_RESOURCES) << "Interval length is " << intervalLength; 0207 0208 if (intervalLength == 0) { 0209 // We have an Accessed event - otherwise, this wouldn't be 0 0210 score += d->timeFactor(QDateTime::fromSecsSinceEpoch(end), currentTime); // like it is open for 1 minute 0211 0212 } else { 0213 score += d->timeFactor(QDateTime::fromSecsSinceEpoch(end), currentTime) * intervalLength / 60.0; 0214 } 0215 } 0216 0217 qCDebug(KAMD_LOG_RESOURCES) << " New score : " << score; 0218 0219 // Updating the score 0220 0221 Utils::exec(*resourcesDatabase(), 0222 Utils::FailOnError, 0223 Queries::self().updateResourceScoreCacheQuery, 0224 ":usedActivity", 0225 d->activity, 0226 ":initiatingAgent", 0227 d->application, 0228 ":targettedResource", 0229 d->resource, 0230 ":cachedScore", 0231 score, 0232 ":lastUpdate", 0233 lastEventStart); 0234 0235 // Notifying the world 0236 qCDebug(KAMD_LOG_RESOURCES) << "ResourceScoreUpdated:" << d->activity << d->application << d->resource; 0237 Q_EMIT QMetaObject::invokeMethod(StatsPlugin::self(), 0238 "ResourceScoreUpdated", 0239 Qt::QueuedConnection, 0240 Q_ARG(QString, d->activity), 0241 Q_ARG(QString, d->application), 0242 Q_ARG(QString, d->resource), 0243 Q_ARG(double, score), 0244 Q_ARG(uint, lastEventStart), 0245 Q_ARG(uint, firstUpdate.toSecsSinceEpoch())); 0246 }