diff --git a/src/resultset.cpp b/src/resultset.cpp index 15b82e8..3fe4df3 100644 --- a/src/resultset.cpp +++ b/src/resultset.cpp @@ -1,587 +1,587 @@ /* * Copyright (C) 2015, 2016 Ivan Cukic * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) version 3, or any * later version accepted by the membership of KDE e.V. (or its * successor approved by the membership of KDE e.V.), which shall * act as a proxy defined in Section 6 of version 3 of the license. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. * If not, see . */ #include "resultset.h" // Qt #include #include #include #include #include // Local #include #include #include #include #include "kactivities-stats-logsettings.h" // Boost and STL #include #include #include #include // KActivities #include "activitiessync_p.h" #define DEBUG_QUERIES 0 namespace KActivities { namespace Stats { using namespace Terms; class ResultSet_ResultPrivate { public: QString resource; QString title; QString mimetype; double score; uint lastUpdate; uint firstUpdate; ResultSet::Result::LinkStatus linkStatus; QStringList linkedActivities; }; ResultSet::Result::Result() : d(new ResultSet_ResultPrivate()) { } ResultSet::Result::Result(Result &&result) : d(result.d) { result.d = nullptr; } ResultSet::Result::Result(const Result &result) : d(new ResultSet_ResultPrivate(*result.d)) { } ResultSet::Result &ResultSet::Result::operator=(Result result) { std::swap(d, result.d); return *this; } ResultSet::Result::~Result() { delete d; } #define CREATE_GETTER_AND_SETTER(Type, Name, Set) \ Type ResultSet::Result::Name() const \ { \ return d->Name; \ } \ \ void ResultSet::Result::Set(Type Name) \ { \ d->Name = Name; \ } CREATE_GETTER_AND_SETTER(QString, resource, setResource) CREATE_GETTER_AND_SETTER(QString, title, setTitle) CREATE_GETTER_AND_SETTER(QString, mimetype, setMimetype) CREATE_GETTER_AND_SETTER(double, score, setScore) CREATE_GETTER_AND_SETTER(uint, lastUpdate, setLastUpdate) CREATE_GETTER_AND_SETTER(uint, firstUpdate, setFirstUpdate) CREATE_GETTER_AND_SETTER(ResultSet::Result::LinkStatus, linkStatus, setLinkStatus) CREATE_GETTER_AND_SETTER(QStringList, linkedActivities, setLinkedActivities) #undef CREATE_GETTER_AND_SETTER QUrl ResultSet::Result::url() const { if (QDir::isAbsolutePath(d->resource)) { return QUrl::fromLocalFile(d->resource); } else { return QUrl(d->resource); } } class ResultSetPrivate { public: Common::Database::Ptr database; QSqlQuery query; Query queryDefinition; mutable ActivitiesSync::ConsumerPtr activities; void initQuery() { if (!database || query.isActive()) { return; } auto selection = queryDefinition.selection(); query = database->execQuery(replaceQueryParameters( selection == LinkedResources ? linkedResourcesQuery() : selection == UsedResources ? usedResourcesQuery() : selection == AllResources ? allResourcesQuery() : QString())); if (query.lastError().isValid()) { qCWarning(KACTIVITIES_STATS_LOG) << "[Error at ResultSetPrivate::initQuery]: " << query.lastError(); } } QString agentClause(const QString &agent) const { if (agent == QLatin1String(":any")) return QStringLiteral("1"); return QStringLiteral("agent = '") + ( agent == QLatin1String(":current") ? QCoreApplication::instance()->applicationName() : agent ) + QStringLiteral("'"); } QString activityClause(const QString &activity) const { if (activity == QLatin1String(":any")) return QStringLiteral("1"); return QStringLiteral("activity = '") + ( activity == QLatin1String(":current") ? ActivitiesSync::currentActivity(activities) : activity ) + QStringLiteral("'"); } inline QString starPattern(const QString &pattern) const { return Common::parseStarPattern(pattern, QStringLiteral("%"), [] (QString str) { return str.replace(QLatin1String("%"), QLatin1String("\\%")).replace(QLatin1String("_"), QLatin1String("\\_")); }); } QString urlFilterClause(const QString &urlFilter) const { if (urlFilter == QLatin1String("*")) return QStringLiteral("1"); return QStringLiteral("resource LIKE '") + Common::starPatternToLike(urlFilter) + QStringLiteral("' ESCAPE '\\'"); } QString mimetypeClause(const QString &mimetype) const { if (mimetype == ANY_TYPE_TAG || mimetype == QLatin1String("*")) return QStringLiteral("1"); else if (mimetype == FILES_TYPE_TAG) return QStringLiteral("mimetype != 'inode/directory' AND mimetype != ''"); else if (mimetype == DIRECTORIES_TYPE_TAG) return QStringLiteral("mimetype = 'inode/directory'"); return QStringLiteral("mimetype LIKE '") + Common::starPatternToLike(mimetype) + QStringLiteral("' ESCAPE '\\'"); } QString dateClause(QDate start, QDate end) const { if (end.isNull()) { // only date filtering return QStringLiteral("DATE(re.start, 'unixepoch') = '") + start.toString(Qt::ISODate) + QStringLiteral("' "); } else { // date range filtering return QStringLiteral("DATE(re.start, 'unixepoch') >= '") + start.toString(Qt::ISODate) + QStringLiteral("' AND DATE(re.start, 'unixepoch') <= '") + end.toString(Qt::ISODate) + QStringLiteral("' "); } } QString resourceEventJoinClause() const { return QStringLiteral(R"sql( LEFT JOIN ResourceEvent re ON from_table.targettedResource = re.targettedResource AND from_table.usedActivity = re.usedActivity AND from_table.initiatingAgent = re.initiatingAgent )sql"); } /** * Transforms the input list's elements with the f member method, * and returns the resulting list */ template inline QStringList transformedList(const QStringList &input, F f) const { using namespace std::placeholders; QStringList result; boost::transform(input, std::back_inserter(result), std::bind(f, this, _1)); return result; } QString limitOffsetSuffix() const { QString result; const int limit = queryDefinition.limit(); if (limit > 0) { result += QStringLiteral(" LIMIT ") + QString::number(limit); const int offset = queryDefinition.offset(); if (offset > 0) { result += QStringLiteral(" OFFSET ") + QString::number(offset); } } return result; } inline QString replaceQueryParameters(const QString &_query) const { // ORDER BY column auto ordering = queryDefinition.ordering(); QString orderingColumn = QStringLiteral("linkStatus DESC, ") + ( ordering == HighScoredFirst ? QStringLiteral("score DESC,") : ordering == RecentlyCreatedFirst ? QStringLiteral("firstUpdate DESC,") : ordering == RecentlyUsedFirst ? QStringLiteral("lastUpdate DESC,") : ordering == OrderByTitle ? QStringLiteral("title ASC,") : QString() ); // WHERE clause for filtering on agents QStringList agentsFilter = transformedList( queryDefinition.agents(), &ResultSetPrivate::agentClause); // WHERE clause for filtering on activities QStringList activitiesFilter = transformedList( queryDefinition.activities(), &ResultSetPrivate::activityClause); // WHERE clause for filtering on resource URLs QStringList urlFilter = transformedList( queryDefinition.urlFilters(), &ResultSetPrivate::urlFilterClause); // WHERE clause for filtering on resource mime QStringList mimetypeFilter = transformedList( queryDefinition.types(), &ResultSetPrivate::mimetypeClause); QString dateColumn = QStringLiteral("1"), resourceEventJoin; // WHERE clause for access date filtering and ResourceEvent table Join if (!queryDefinition.dateStart().isNull()) { dateColumn = dateClause(queryDefinition.dateStart(), queryDefinition.dateEnd()); resourceEventJoin = resourceEventJoinClause(); } auto queryString = _query; queryString.replace(QStringLiteral("ORDER_BY_CLAUSE"), QStringLiteral("ORDER BY $orderingColumn resource ASC")) .replace(QStringLiteral("LIMIT_CLAUSE"), limitOffsetSuffix()); return kamd::utils::debug_and_return(DEBUG_QUERIES, "Query: ", queryString .replace(QLatin1String("$orderingColumn"), orderingColumn) .replace(QLatin1String("$agentsFilter"), agentsFilter.join(QStringLiteral(" OR "))) .replace(QLatin1String("$activitiesFilter"), activitiesFilter.join(QStringLiteral(" OR "))) .replace(QLatin1String("$urlFilter"), urlFilter.join(QStringLiteral(" OR "))) .replace(QLatin1String("$mimetypeFilter"), mimetypeFilter.join(QStringLiteral(" OR "))) .replace(QLatin1String("$resourceEventJoin"), resourceEventJoin) .replace(QLatin1String("$dateFilter"), dateColumn) ); } static const QString &linkedResourcesQuery() { // TODO: We need to correct the scores based on the time that passed // since the cache was last updated, although, for this query, // scores are not that important. static const QString queryString = QStringLiteral(R"sql( SELECT from_table.targettedResource as resource , SUM(rsc.cachedScore) as score , MIN(rsc.firstUpdate) as firstUpdate , MAX(rsc.lastUpdate) as lastUpdate , from_table.usedActivity as activity , from_table.initiatingAgent as agent , COALESCE(ri.title, from_table.targettedResource) as title , ri.mimetype as mimetype , 2 as linkStatus FROM ResourceLink from_table LEFT JOIN ResourceScoreCache rsc ON from_table.targettedResource = rsc.targettedResource AND from_table.usedActivity = rsc.usedActivity AND from_table.initiatingAgent = rsc.initiatingAgent LEFT JOIN ResourceInfo ri ON from_table.targettedResource = ri.targettedResource $resourceEventJoin WHERE ($agentsFilter) AND ($activitiesFilter) AND ($urlFilter) AND ($mimetypeFilter) AND ($dateFilter) GROUP BY resource, title ORDER_BY_CLAUSE LIMIT_CLAUSE )sql") ; return queryString; } static const QString &usedResourcesQuery() { // TODO: We need to correct the scores based on the time that passed // since the cache was last updated static const QString queryString = QStringLiteral(R"sql( SELECT from_table.targettedResource as resource , SUM(from_table.cachedScore) as score , MIN(from_table.firstUpdate) as firstUpdate , MAX(from_table.lastUpdate) as lastUpdate , from_table.usedActivity as activity , from_table.initiatingAgent as agent , COALESCE(ri.title, from_table.targettedResource) as title , ri.mimetype as mimetype , 1 as linkStatus FROM ResourceScoreCache from_table LEFT JOIN ResourceInfo ri ON from_table.targettedResource = ri.targettedResource $resourceEventJoin WHERE ($agentsFilter) AND ($activitiesFilter) AND ($urlFilter) AND ($mimetypeFilter) AND ($dateFilter) GROUP BY resource, title ORDER_BY_CLAUSE LIMIT_CLAUSE )sql") ; return queryString; } static const QString &allResourcesQuery() { // TODO: We need to correct the scores based on the time that passed // since the cache was last updated, although, for this query, // scores are not that important. static const QString queryString = QStringLiteral(R"sql( WITH LinkedResourcesResults AS ( SELECT from_table.targettedResource as resource , rsc.cachedScore as score , rsc.firstUpdate as firstUpdate , rsc.lastUpdate as lastUpdate , from_table.usedActivity as activity , from_table.initiatingAgent as agent , 2 as linkStatus FROM ResourceLink from_table LEFT JOIN ResourceScoreCache rsc ON from_table.targettedResource = rsc.targettedResource AND from_table.usedActivity = rsc.usedActivity - AND rl.initiatingAgent = rsc.initiatingAgent + AND from_table.initiatingAgent = rsc.initiatingAgent $resourceEventJoin WHERE ($agentsFilter) AND ($activitiesFilter) AND ($urlFilter) AND ($mimetypeFilter) AND ($dateFilter) ), UsedResourcesResults AS ( SELECT from_table.targettedResource as resource , from_table.cachedScore as score , from_table.firstUpdate as firstUpdate , from_table.lastUpdate as lastUpdate , from_table.usedActivity as activity , from_table.initiatingAgent as agent , 0 as linkStatus FROM ResourceScoreCache from_table $resourceEventJoin WHERE ($agentsFilter) AND ($activitiesFilter) AND ($urlFilter) AND ($mimetypeFilter) AND ($dateFilter) ), CollectedResults AS ( SELECT * FROM LinkedResourcesResults UNION SELECT * FROM UsedResourcesResults WHERE resource NOT IN (SELECT resource FROM LinkedResourcesResults) ) SELECT resource , SUM(score) as score , MIN(firstUpdate) as firstUpdate , MAX(lastUpdate) as lastUpdate , activity , agent , COALESCE(ri.title, resource) as title , ri.mimetype as mimetype , linkStatus FROM CollectedResults cr LEFT JOIN ResourceInfo ri ON cr.resource = ri.targettedResource GROUP BY resource, title ORDER_BY_CLAUSE LIMIT_CLAUSE )sql") ; return queryString; } ResultSet::Result currentResult() const { ResultSet::Result result; if (!database || !query.isActive()) return result; result.setResource(query.value(QStringLiteral("resource")).toString()); result.setTitle(query.value(QStringLiteral("title")).toString()); result.setMimetype(query.value(QStringLiteral("mimetype")).toString()); result.setScore(query.value(QStringLiteral("score")).toDouble()); result.setLastUpdate(query.value(QStringLiteral("lastUpdate")).toUInt()); result.setFirstUpdate(query.value(QStringLiteral("firstUpdate")).toUInt()); result.setLinkStatus( static_cast(query.value(QStringLiteral("linkStatus")).toUInt())); auto linkedActivitiesQuery = database->createQuery(); linkedActivitiesQuery.prepare(QStringLiteral(R"sql( SELECT usedActivity FROM ResourceLink WHERE targettedResource = :resource )sql")); linkedActivitiesQuery.bindValue(QStringLiteral(":resource"), result.resource()); linkedActivitiesQuery.exec(); QStringList linkedActivities; for (const auto &item: linkedActivitiesQuery) { linkedActivities << item[0].toString(); } result.setLinkedActivities(linkedActivities); // qDebug(KACTIVITIES_STATS_LOG) << result.resource() << "linked to activities" << result.linkedActivities(); return result; } }; ResultSet::ResultSet(Query queryDefinition) : d(new ResultSetPrivate()) { using namespace Common; d->database = Database::instance(Database::ResourcesDatabase, Database::ReadOnly); if (!(d->database)) { qCWarning(KACTIVITIES_STATS_LOG) << "KActivities ERROR: There is no database. This probably means " "that you do not have the Activity Manager running, or that " "something else is broken on your system. Recent documents and " "alike will not work!"; } d->queryDefinition = queryDefinition; d->initQuery(); } ResultSet::ResultSet(ResultSet &&source) : d(nullptr) { std::swap(d, source.d); } ResultSet::ResultSet(const ResultSet &source) : d(new ResultSetPrivate(*source.d)) { } ResultSet &ResultSet::operator= (ResultSet source) { std::swap(d, source.d); return *this; } ResultSet::~ResultSet() { delete d; } ResultSet::Result ResultSet::at(int index) const { if (!d->query.isActive()) return Result(); d->query.seek(index); return d->currentResult(); } } // namespace Stats } // namespace KActivities #include "resultset_iterator.cpp"