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 }