diff --git a/autotests/QueryTest.cpp b/autotests/QueryTest.cpp index 969092f..a41a0c6 100644 --- a/autotests/QueryTest.cpp +++ b/autotests/QueryTest.cpp @@ -1,327 +1,332 @@ /* * Copyright (C) 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program 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 General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "QueryTest.h" #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; QueryTest::QueryTest(QObject *parent) : Test(parent) { } void QueryTest::testDefaults() { TEST_CHUNK(QStringLiteral("Testing the term defaults")); Query query; QCOMPARE(query.selection(), AllResources); QCOMPARE(query.types(), {QStringLiteral(":any")}); QCOMPARE(query.agents(), {QStringLiteral(":current")}); QCOMPARE(query.activities(), {QStringLiteral(":current")}); QCOMPARE(query.ordering(), HighScoredFirst); } void QueryTest::testDebuggingOutput() { TEST_CHUNK(QStringLiteral("Debugging output for a query")); Query query; // Testing whether qDebug can be called (compilation check) qDebug() << "Writing out a query:" << query; } void QueryTest::testDerivationFromDefault() { TEST_CHUNK(QStringLiteral("Testing query derivation from default")) Query queryDefault; auto queryDerived = queryDefault | LinkedResources; // queryDefault should not have been modified QCOMPARE(queryDefault.selection(), AllResources); QCOMPARE(queryDerived.selection(), LinkedResources); // Changing queryDerived back to AllResources, should be == to queryDefault queryDerived.setSelection(AllResources); QCOMPARE(queryDefault, queryDerived); } void QueryTest::testDerivationFromCustom() { TEST_CHUNK(QStringLiteral("Testing query derivation from custom")) Query queryCustom; auto queryDerived = queryCustom | LinkedResources; // q1 should not have been modified QCOMPARE(queryCustom.selection(), AllResources); QCOMPARE(queryDerived.selection(), LinkedResources); // Changing queryDerived back to AllResources, should be == to queryDefault queryDerived.setSelection(AllResources); QCOMPARE(queryCustom, queryDerived); } void QueryTest::testNormalSyntaxAgentManipulation() { TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Agents")) Query query; query.addAgents(QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); query.addAgents(QStringList() << QStringLiteral("kwrite")); QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate") << QStringLiteral("kwrite")); query.clearAgents(); QCOMPARE(query.agents(), QStringList() << QStringLiteral(":current")); } void QueryTest::testNormalSyntaxTypeManipulation() { TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Types")) Query query; query.addTypes(QStringList() << QStringLiteral("text/html") << QStringLiteral("text/plain")); QCOMPARE(query.types(), QStringList() << QStringLiteral("text/html") << QStringLiteral("text/plain")); query.addTypes(QStringList() << QStringLiteral("text/xml")); QCOMPARE(query.types(), QStringList() << QStringLiteral("text/html") << QStringLiteral("text/plain") << QStringLiteral("text/xml")); query.clearTypes(); QCOMPARE(query.types(), QStringList() << QStringLiteral(":any")); } void QueryTest::testNormalSyntaxActivityManipulation() { TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Activities")) Query query; query.addActivities(QStringList() << QStringLiteral("a1") << QStringLiteral("a2")); QCOMPARE(query.activities(), QStringList() << QStringLiteral("a1") << QStringLiteral("a2")); query.addActivities(QStringList() << QStringLiteral("a3")); QCOMPARE(query.activities(), QStringList() << QStringLiteral("a1") << QStringLiteral("a2") << QStringLiteral("a3")); query.clearActivities(); QCOMPARE(query.activities(), QStringList() << QStringLiteral(":current")); } void QueryTest::testNormalSyntaxOrderingManipulation() { TEST_CHUNK(QStringLiteral("Testing normal syntax manipulation: Activities")) Query query; QCOMPARE(query.ordering(), HighScoredFirst); query.setOrdering(RecentlyCreatedFirst); QCOMPARE(query.ordering(), RecentlyCreatedFirst); query.setOrdering(OrderByUrl); QCOMPARE(query.ordering(), OrderByUrl); } void QueryTest::testFancySyntaxBasic() { TEST_CHUNK(QStringLiteral("Testing the fancy syntax, non c++11")) auto query = LinkedResources | Type(QStringLiteral("text")) | Type(QStringLiteral("image")) | Agent(QStringLiteral("test")) | RecentlyCreatedFirst; QCOMPARE(query.selection(), LinkedResources); QCOMPARE(query.types(), QStringList() << QStringLiteral("text") << QStringLiteral("image")); QCOMPARE(query.agents(), QStringList() << QStringLiteral("test")); QCOMPARE(query.activities(), QStringList() << QStringLiteral(":current")); QCOMPARE(query.ordering(), RecentlyCreatedFirst); #ifdef Q_COMPILER_INITIALIZER_LISTS TEST_CHUNK(QStringLiteral("Testing the fancy syntax, c++11")) // Testing the fancy c++11 syntax auto queryCXX11 = LinkedResources | Type{QStringLiteral("text"), QStringLiteral("image")} | Agent{QStringLiteral("test")} | RecentlyCreatedFirst; QCOMPARE(query, queryCXX11); #endif } void QueryTest::testFancySyntaxAgentDefinition() { TEST_CHUNK(QStringLiteral("Testing the fancy syntax, agent definition")) { auto query = LinkedResources | OrderByUrl; QCOMPARE(query.agents(), QStringList() << QStringLiteral(":current")); } { auto query = LinkedResources | Agent(QStringLiteral("gvim")); QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim")); } { auto query = LinkedResources | Agent(QStringLiteral("gvim")) | Agent(QStringLiteral("kate")); QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); } { auto query = LinkedResources | Agent(QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); QCOMPARE(query.agents(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); } } void QueryTest::testFancySyntaxTypeDefinition() { TEST_CHUNK(QStringLiteral("Testing the fancy syntax, type definition")) { auto query = LinkedResources | OrderByUrl; QCOMPARE(query.types(), QStringList() << QStringLiteral(":any")); } { auto query = LinkedResources | Type(QStringLiteral("text/plain")); QCOMPARE(query.types(), QStringList() << QStringLiteral("text/plain")); } { auto query = LinkedResources | Type(QStringLiteral("text/plain")) | Type(QStringLiteral("text/html")); QCOMPARE(query.types(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); } { auto query = LinkedResources | Type(QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); QCOMPARE(query.types(), QStringList() << QStringLiteral("text/plain") << QStringLiteral("text/html")); } } void QueryTest::testFancySyntaxActivityDefinition() { TEST_CHUNK(QStringLiteral("Testing the fancy syntax, activity definition")) { auto query = LinkedResources | OrderByUrl; QCOMPARE(query.activities(), QStringList() << QStringLiteral(":current")); } { auto query = LinkedResources | Activity(QStringLiteral("gvim")); QCOMPARE(query.activities(), QStringList() << QStringLiteral("gvim")); } { auto query = LinkedResources | Activity(QStringLiteral("gvim")) | Activity(QStringLiteral("kate")); QCOMPARE(query.activities(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); } { auto query = LinkedResources | Activity(QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); QCOMPARE(query.activities(), QStringList() << QStringLiteral("gvim") << QStringLiteral("kate")); } } void QueryTest::testFancySyntaxOrderingDefinition() { TEST_CHUNK(QStringLiteral("Testing the fancy syntax, activity definition")) { auto query = LinkedResources | OrderByUrl; QCOMPARE(query.ordering(), OrderByUrl); } { auto query = LinkedResources | HighScoredFirst; QCOMPARE(query.ordering(), HighScoredFirst); } { auto query = LinkedResources | RecentlyCreatedFirst; QCOMPARE(query.ordering(), RecentlyCreatedFirst); } { auto query = LinkedResources | RecentlyCreatedFirst | OrderByUrl; QCOMPARE(query.ordering(), OrderByUrl); } { auto query = LinkedResources | RecentlyCreatedFirst | HighScoredFirst; QCOMPARE(query.ordering(), HighScoredFirst); } } void QueryTest::testNormalSyntaxDateDefinition() { TEST_CHUNK(QStringLiteral("Testing the Date definition")) { auto query = Date::today(); - QCOMPARE(query.value, QDate::currentDate()); + QCOMPARE(query.start, QDate::currentDate()); } { auto query = Date::yesterday(); QDate date = QDate::currentDate(); - QCOMPARE(query.value, date.addDays(-1)); + QCOMPARE(query.start, date.addDays(-1)); } { auto query = Date(QDate::fromString(QStringLiteral("2019-07-25"))); - QCOMPARE(query.value, QDate::fromString(QStringLiteral("2019-07-25"))); + QCOMPARE(query.start, QDate::fromString(QStringLiteral("2019-07-25"))); + } + { + auto query = Date(QDate::fromString(QStringLiteral("2019-07-24,2019-07-25"))); + QCOMPARE(query.start, QDate::fromString(QStringLiteral("2019-07-24"))); + QCOMPARE(query.end, QDate::fromString(QStringLiteral("2019-07-25"))); } } void QueryTest::initTestCase() { // CHECK_CONDITION(isActivityManagerRunning, FailIfTrue); } void QueryTest::cleanupTestCase() { emit testFinished(); } diff --git a/autotests/ResultSetTest.cpp b/autotests/ResultSetTest.cpp index aca6b66..86bdd11 100644 --- a/autotests/ResultSetTest.cpp +++ b/autotests/ResultSetTest.cpp @@ -1,256 +1,274 @@ /* * Copyright (C) 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program 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 General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "ResultSetTest.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; ResultSetTest::ResultSetTest(QObject *parent) : Test(parent) { } namespace { QString getBarredUri(const KAStats::ResultSet::Result &result) { return result.resource() + QStringLiteral("|"); } QString concatenateResults(const KAStats::ResultSet &results) { using boost::accumulate; using boost::adaptors::transformed; return accumulate( results | transformed(getBarredUri), QStringLiteral("|") ); } } void ResultSetTest::testLinkedResources() { using namespace KAStats; using namespace KAStats::Terms; // TEST_CHUNK("Getting the linked resources alphabetically") // { // ResultSet result(LinkedResources // | Agent { "gvim" } // | Activity { "860d9ec8-87f9-8e96-1558-1faf54b98e97" } // | OrderAlphabetically // ); // // QCOMPARE(result.at(0).resource, QStringLiteral("/path/mid1_a1")); // QCOMPARE(result.at(1).resource, QStringLiteral("/path/mid2_a1")); // } } void ResultSetTest::testUsedResources() { using namespace KAStats; using namespace KAStats::Terms; qDebug() << "Agent: " << QCoreApplication::instance()->applicationName(); TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, default query")) { ResultSet result(UsedResources); qDebug() << "-----------------------------"; for (const auto &item: result) { qDebug() << "Item: " << item.resource(); } qDebug() << "-----------------------------"; QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high5_act1_kast")); QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high7_act1_kast")); QCOMPARE(result.at(2).resource(), QStringLiteral("/path/high8_act1_kast")); // END! QCOMPARE(result.at(3).resource(), QString()); // Testing whether range works QCOMPARE(QStringLiteral("|/path/high5_act1_kast|/path/high7_act1_kast|/path/high8_act1_kast|"), concatenateResults(result)); } TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, gvim")) { ResultSet result(UsedResources | HighScoredFirst | Agent{QStringLiteral("gvim")} ); QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high4_act1_gvim")); } TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, global agent")) { ResultSet result(UsedResources | HighScoredFirst | Agent::global() ); QCOMPARE(result.at(0).resource(), QStringLiteral("/path/mid6_act1_glob")); QCOMPARE(result.at(1).resource(), QStringLiteral("/path/mid7_act1_glob")); QCOMPARE(result.at(2).resource(), QStringLiteral("/path/mid8_act1_glob")); } TEST_CHUNK(QStringLiteral("Getting the used resources by the highest score, any agent")) { ResultSet result(UsedResources | HighScoredFirst | Agent::any() | Activity::any() ); QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high2_act2_kate")); QCOMPARE(result.at(2).resource(), QStringLiteral("/path/high3_act1_kate")); } TEST_CHUNK(QStringLiteral("Getting the used resources filter by Date")) { ResultSet result(UsedResources | HighScoredFirst | Agent::any() | Activity::any() | Date::fromString(QStringLiteral("2015-01-15")) ); QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); } + + TEST_CHUNK(QStringLiteral("Getting the used resources filter by Date range")) + { + ResultSet result(UsedResources + | HighScoredFirst + | Agent::any() + | Activity::any() + | Date::fromString(QStringLiteral("2015-01-14,2015-01-15")) + ); + + QCOMPARE(result.at(0).resource(), QStringLiteral("/path/high1_act1_gvim")); + QCOMPARE(result.at(1).resource(), QStringLiteral("/path/high2_act2_kate")); + } } void ResultSetTest::initTestCase() { QTemporaryDir dir(QDir::tempPath() + QStringLiteral("/KActivitiesStatsTest_ResultSetTest_XXXXXX")); dir.setAutoRemove(false); if (!dir.isValid()) { qFatal("Can not create a temporary directory"); } const auto databaseFile = dir.path() + QStringLiteral("/database"); Common::ResourcesDatabaseSchema::overridePath(databaseFile); qDebug() << "Creating database in " << databaseFile; // Creating the database, and pushing some dummy data into it auto database = Common::Database::instance(Common::Database::ResourcesDatabase, Common::Database::ReadWrite); Common::ResourcesDatabaseSchema::initSchema(*database); database->execQuery( QStringLiteral("INSERT INTO ResourceScoreCache (usedActivity, initiatingAgent, targettedResource, scoreType, cachedScore, firstUpdate, lastUpdate) VALUES " " ('activity1' , 'gvim' , '/path/high1_act1_gvim' , '0' , '800' , '-1' , '1421446599')" " , ('activity2' , 'kate' , '/path/high2_act2_kate' , '0' , '700' , '-1' , '1421439442')" " , ('activity1' , 'kate' , '/path/high3_act1_kate' , '0' , '600' , '-1' , '1421439442')" " , ('activity1' , 'gvim' , '/path/high4_act1_gvim' , '0' , '500' , '-1' , '1421446488')" " , ('activity1' , 'KActivitiesStatsTest' , '/path/high5_act1_kast' , '0' , '400' , '-1' , '1421446599')" " , ('activity2' , 'KActivitiesStatsTest' , '/path/high6_act2_kast' , '0' , '300' , '-1' , '1421439442')" " , ('activity1' , 'KActivitiesStatsTest' , '/path/high7_act1_kast' , '0' , '200' , '-1' , '1421439442')" " , ('activity1' , 'KActivitiesStatsTest' , '/path/high8_act1_kast' , '0' , '100' , '-1' , '1421446488')" " , ('activity1' , 'gvim' , '/path/mid1_act1_gvim' , '0' , '17' , '-1' , '1421433419')" " , ('activity1' , 'gvim' , '/path/mid2_act1_gvim' , '0' , '54' , '-1' , '1421431630')" " , ('activity2' , 'gvim' , '/path/mid3_act2_gvim' , '0' , '8' , '-1' , '1421433172')" " , ('activity2' , 'gvim' , '/path/mid4_act2_gvim' , '0' , '8' , '-1' , '1421432545')" " , ('activity2' , 'gvim' , '/path/mid5_act2_gvim' , '0' , '79' , '-1' , '1421439118')" " , ('activity1' , ':global' , '/path/mid6_act1_glob' , '0' , '20' , '-1' , '1421439331')" " , ('activity1' , ':global' , '/path/mid7_act1_glob' , '0' , '8' , '-1' , '0')" " , ('activity1' , ':global' , '/path/mid8_act1_glob' , '0' , '7' , '-1' , '1421432617')" " , ('activity1' , 'gvim' , '/path/low3_act1_gvim' , '0' , '6' , '-1' , '1421434704')" " , ('activity1' , 'kate' , '/path/low2_act1_kate' , '0' , '3' , '-1' , '1421433266')" " , ('activity1' , 'kate' , '/path/low1_act1_kate' , '0' , '2' , '-1' , '1421433254')") ); database->execQuery( QStringLiteral("INSERT INTO ResourceEvent (usedActivity, initiatingAgent, targettedResource, start, end ) VALUES" - "('activity1' , 'gvim' , '/path/high1_act1_gvim' , '1421345799', '1421345799')") + // 15 january 2015 + " ('activity1' , 'gvim' , '/path/high1_act1_gvim' , '1421345799', '1421345799')" + // 14 january 2015 + " , ('activity2' , 'kate' , '/path/high2_act2_kate' , '1421259377', '1421259377')" + ) ); database->execQuery( QStringLiteral("INSERT INTO ResourceInfo (targettedResource, title, mimetype, autoTitle, autoMimetype) VALUES" - "('/path/high1_act1_gvim', 'high1_act1_gvim', 'text/plain', 1, 1 )") + "('/path/high1_act1_gvim', 'high1_act1_gvim', 'text/plain', 1, 1 ) ," + "('/path/high2_act2_kate', 'high2_act2_kate', 'text/plain', 1, 1 )") ); // Renaming the activity1 to the current acitivty KActivities::Consumer kamd; while (kamd.serviceStatus() == KActivities::Consumer::Unknown) { QCoreApplication::processEvents(); } database->execQuery( QStringLiteral("UPDATE ResourceScoreCache SET usedActivity = '") + kamd.currentActivity() + QStringLiteral("' WHERE usedActivity = 'activity1'")); database->execQuery( QStringLiteral("UPDATE ResourceEvent SET usedActivity = '") + kamd.currentActivity() + QStringLiteral("' WHERE usedActivity = 'activity1'")); database->execQuery( QStringLiteral("INSERT INTO ResourceLink (usedActivity, initiatingAgent, targettedResource) VALUES " "('activity1' , 'gvim' , '/path/mid1_a1')" ", ('activity1' , 'gvim' , '/path/mid2_a1')" ", ('activity2' , 'gvim' , '/path/mid3_a2')" ", ('activity2' , 'gvim' , '/path/mid4_a2')" ", ('activity2' , 'gvim' , '/path/link5_a2')" ", ('activity1' , 'kate' , '/path/link6_a1')" ", ('activity1' , 'kate' , '/path/link7_a1')" ", ('activity1' , 'kate' , '/path/link8_a1')") ); } void ResultSetTest::cleanupTestCase() { emit testFinished(); } diff --git a/src/query.cpp b/src/query.cpp index 13acaf6..0d20c29 100644 --- a/src/query.cpp +++ b/src/query.cpp @@ -1,221 +1,232 @@ /* * 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 "query.h" #include #include namespace KActivities { namespace Stats { namespace details { inline void validateTypes(QStringList &types) { // Nothing at the moment Q_UNUSED(types); } inline void validateAgents(QStringList &agents) { // Nothing at the moment Q_UNUSED(agents); } inline void validateActivities(QStringList &activities) { // Nothing at the moment Q_UNUSED(activities); } inline void validateUrlFilters(QStringList &urlFilters) { auto i = urlFilters.begin(); const auto end = urlFilters.end(); for (; i != end ; ++i) { i->replace(QLatin1String("'"), QLatin1String("")); } } } // namespace details class QueryPrivate { public: QueryPrivate() : ordering(Terms::HighScoredFirst) , limit(0) , offset(0) { } Terms::Select selection; QStringList types; QStringList agents; QStringList activities; QStringList urlFilters; Terms::Order ordering; - QDate date; + QDate start, end; int limit; int offset; }; Query::Query(Terms::Select selection) : d(new QueryPrivate()) { d->selection = selection; } Query::Query(Query &&source) : d(nullptr) { std::swap(d, source.d); } Query::Query(const Query &source) : d(new QueryPrivate(*source.d)) { } Query &Query::operator= (Query source) { std::swap(d, source.d); return *this; } Query::~Query() { delete d; } bool Query::operator== (const Query &right) const { return selection() == right.selection() && types() == right.types() && agents() == right.agents() && activities() == right.activities() && selection() == right.selection() && urlFilters() == right.urlFilters() && - date() == right.date(); + dateStart() == right.dateStart() && + dateEnd() == right.dateEnd(); } bool Query::operator!= (const Query &right) const { return !(*this == right); } #define IMPLEMENT_QUERY_LIST_FIELD(WHAT, What, Default) \ void Query::add##WHAT(const QStringList &What) \ { \ d->What << What; \ details::validate##WHAT(d->What); \ } \ \ QStringList Query::What() const \ { \ return d->What.size() ? d->What : Default; \ } \ \ void Query::clear##WHAT() \ { \ d->What.clear(); \ } IMPLEMENT_QUERY_LIST_FIELD(Types, types, QStringList(QStringLiteral(":any"))) IMPLEMENT_QUERY_LIST_FIELD(Agents, agents, QStringList(QStringLiteral(":current"))) IMPLEMENT_QUERY_LIST_FIELD(Activities, activities, QStringList(QStringLiteral(":current"))) IMPLEMENT_QUERY_LIST_FIELD(UrlFilters, urlFilters, QStringList(QStringLiteral("*"))) #undef IMPLEMENT_QUERY_LIST_FIELD void Query::setOrdering(Terms::Order ordering) { d->ordering = ordering; } void Query::setSelection(Terms::Select selection) { d->selection = selection; } void Query::setLimit(int limit) { d->limit = limit; } void Query::setOffset(int offset) { d->offset = offset; } -void Query::setDate(QDate date) +void Query::setDateStart(QDate start) { - d->date = date; + d->start = start; +} + +void Query::setDateEnd(QDate end) +{ + d->end = end; } Terms::Order Query::ordering() const { return d->ordering; } Terms::Select Query::selection() const { return d->selection; } int Query::limit() const { return d->limit; } int Query::offset() const { Q_ASSERT_X(d->limit > 0, "Query::offset", "Offset can only be specified if limit is set"); return d->offset; } -QDate Query::date() const +QDate Query::dateStart() const +{ + return d->start; +} + +QDate Query::dateEnd() const { - return d->date; + return d->end; } } // namespace Stats } // namespace KActivities namespace KAStats = KActivities::Stats; QDebug operator<<(QDebug dbg, const KAStats::Query &query) { using namespace KAStats::Terms; dbg.nospace() << "Query { " << query.selection() << ", " << Type(query.types()) << ", " << Agent(query.agents()) << ", " << Activity(query.activities()) << ", " << Url(query.urlFilters()) - << ", " << Date(query.date()) + << ", " << Date(query.dateStart(), query.dateEnd()) << ", " << query.ordering() << ", Limit: " << query.limit() << " }"; return dbg; } diff --git a/src/query.h b/src/query.h index bf0a22a..ea0c55c 100644 --- a/src/query.h +++ b/src/query.h @@ -1,198 +1,201 @@ /* * 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 . */ #ifndef KACTIVITIES_STATS_QUERY_H #define KACTIVITIES_STATS_QUERY_H #include #ifdef Q_COMPILER_INITIALIZER_LISTS #include #endif #include #include #include "kactivitiesstats_export.h" #include "terms.h" namespace KActivities { namespace Stats { class QueryPrivate; /** * The activities system tracks resources (documents, contacts, etc.) * that the user has used. It also allows linking resources to * specific activities (like bookmarks, favorites, etc.). * * The Query class specifies which resources to return - * the previously used ones, the linked ones, or to * combine these two. * * It allows filtering the results depending on the resource type, * the agent (application that reported the usage event, * @see KActivities::ResourceInstance) and the activity the resource * has been used in, or linked to. It also allows filtering * on the URL of the resource. * * While it can be explicitly instantiated, a preferred approach * is to use the pipe syntax like this: * * * auto query = UsedResources * | RecentlyUsedFirst * | Agent::any() * | Type::any() * | Activity::current(); * */ class KACTIVITIESSTATS_EXPORT Query { public: Query(Terms::Select selection = Terms::AllResources); // The damned rule of five minus one :) Query(Query && source); Query(const Query &source); Query &operator= (Query source); ~Query(); // Not all are born equal bool operator== (const Query &right) const; bool operator!= (const Query &right) const; Terms::Select selection() const; QStringList types() const; QStringList agents() const; QStringList activities() const; QStringList urlFilters() const; Terms::Order ordering() const; int offset() const; int limit() const; - QDate date() const; + QDate dateStart() const; + QDate dateEnd() const; void setSelection(Terms::Select selection); void addTypes(const QStringList &types); void addAgents(const QStringList &agents); void addActivities(const QStringList &activities); void addUrlFilters(const QStringList &urlFilters); void setOrdering(Terms::Order ordering); void setOffset(int offset); void setLimit(int limit); - void setDate(QDate date); + void setDateStart(QDate date); + void setDateEnd(QDate date); void clearTypes(); void clearAgents(); void clearActivities(); void clearUrlFilters(); void removeTypes(const QStringList &types); void removeAgents(const QStringList &agents); void removeActivities(const QStringList &activities); void removeUrlFilters(const QStringList &urlFilters); private: inline void addTerm(const Terms::Type &term) { addTypes(term.values); } inline void addTerm(const Terms::Agent &term) { addAgents(term.values); } inline void addTerm(const Terms::Activity &term) { addActivities(term.values); } inline void addTerm(const Terms::Url &term) { addUrlFilters(term.values); } inline void addTerm(Terms::Order ordering) { setOrdering(ordering); } inline void addTerm(Terms::Select selection) { setSelection(selection); } inline void addTerm(Terms::Limit limit) { setLimit(limit.value); } inline void addTerm(Terms::Offset offset) { setOffset(offset.value); } inline void addTerm(Terms::Date date) { - setDate(date.value); + setDateStart(date.start); + setDateEnd(date.end); } public: template friend inline Query operator| (const Query &query, Term &&term) { Query result(query); result.addTerm(term); return result; } template friend inline Query operator| (Query &&query, Term &&term) { query.addTerm(term); - return query; + return std::move(query); } private: QueryPrivate* d; }; template inline Query operator| (Terms::Select selection, Term &&term) { return Query(selection) | term; } } // namespace Stats } // namespace KActivities KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Query &query); #endif // KACTIVITIES_STATS_QUERY_H diff --git a/src/resultset.cpp b/src/resultset.cpp index 42a2ce0..fb4c727 100644 --- a/src/resultset.cpp +++ b/src/resultset.cpp @@ -1,564 +1,572 @@ /* * 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 // Local #include #include #include // 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 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()) { qWarning() << "[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 == QLatin1String(":any") || mimetype == QLatin1String("*")) return QStringLiteral("1"); return QStringLiteral("mimetype LIKE '") + Common::starPatternToLike(mimetype) + QStringLiteral("' ESCAPE '\\'"); } - QString dateClause(QDate date) const { - return QStringLiteral("DATE(re.start, 'unixepoch') = '") + - date.toString(Qt::ISODate) + QStringLiteral("' "); + 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.date().isNull()) { - dateColumn = dateClause(queryDefinition.date()); + 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 $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() << 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)) { qWarning() << "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" diff --git a/src/terms.cpp b/src/terms.cpp index 2be6c8f..6603cde 100644 --- a/src/terms.cpp +++ b/src/terms.cpp @@ -1,143 +1,171 @@ /* * 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 "terms.h" #include namespace KActivities { namespace Stats { // Term classes #define IMPLEMENT_TERM_CONSTRUCTORS(TYPE) \ Terms::TYPE::TYPE(QStringList values) \ : values(values) \ {} \ \ Terms::TYPE::TYPE(QString value) \ : values(QStringList() << value) \ {} #define IMPLEMENT_SPECIAL_TERM_VALUE(TYPE, VALUE_NAME, VALUE) \ Terms::TYPE Terms::TYPE::VALUE_NAME() \ { \ return Terms::TYPE(VALUE); \ } IMPLEMENT_TERM_CONSTRUCTORS(Type) IMPLEMENT_SPECIAL_TERM_VALUE(Type, any, QStringLiteral(":any")) IMPLEMENT_TERM_CONSTRUCTORS(Agent) IMPLEMENT_SPECIAL_TERM_VALUE(Agent, any, QStringLiteral(":any")) IMPLEMENT_SPECIAL_TERM_VALUE(Agent, global, QStringLiteral(":global")) IMPLEMENT_SPECIAL_TERM_VALUE(Agent, current, QStringLiteral(":current")) IMPLEMENT_TERM_CONSTRUCTORS(Activity) IMPLEMENT_SPECIAL_TERM_VALUE(Activity, any, QStringLiteral(":any")) IMPLEMENT_SPECIAL_TERM_VALUE(Activity, global, QStringLiteral(":global")) IMPLEMENT_SPECIAL_TERM_VALUE(Activity, current, QStringLiteral(":current")) IMPLEMENT_TERM_CONSTRUCTORS(Url) IMPLEMENT_SPECIAL_TERM_VALUE(Url, localFile, QStringLiteral("/*")) IMPLEMENT_SPECIAL_TERM_VALUE(Url, file, QStringList() << QStringLiteral("/*") << QStringLiteral("smb:*") << QStringLiteral("fish:*") << QStringLiteral("sftp:*") << QStringLiteral("ftp:*")) #undef IMPLEMENT_TERM_CONSTRUCTORS #undef IMPLEMENT_SPECIAL_TERM_VALUE Terms::Limit::Limit(int value) : value(value) { } Terms::Limit Terms::Limit::all() { return Limit(0); } Terms::Offset::Offset(int value) : value(value) { } Terms::Date::Date(QDate value) - : value(value) + : start(value) +{ +} + +Terms::Date::Date(QDate start, QDate end) + : start(start), end(end) { } Terms::Date Terms::Date::today() { return Date(QDate::currentDate()); } Terms::Date Terms::Date::yesterday() { auto date = QDate::currentDate(); return Date(date.addDays(-1)); } +Terms::Date Terms::Date::currentWeek() +{ + auto start = QDate::currentDate(); + auto end = start.addDays(-7); + return Date(start, end); +} + +Terms::Date Terms::Date::previousWeek() +{ + auto start = QDate::currentDate().addDays(-7); + auto end = start.addDays(-7); + return Date(start, end); +} + Terms::Date Terms::Date::fromString(QString string) { - auto date = QDate::fromString(string, Qt::ISODate); - return Date(date); + auto splitted = string.split(QStringLiteral(",")); + if (splitted.count() == 2) { + // date range case + auto start = QDate::fromString(splitted[0], Qt::ISODate); + auto end = QDate::fromString(splitted[1], Qt::ISODate); + return Date(start, end); + } else { + auto date = QDate::fromString(string, Qt::ISODate); + return Date(date); + } } Terms::Url Terms::Url::startsWith(const QString &prefix) { return Url(prefix + QStringLiteral("*")); } Terms::Url Terms::Url::contains(const QString &infix) { return Url(QStringLiteral("*") + infix + QStringLiteral("*")); } } // namespace Stats } // namespace KActivities namespace KAStats = KActivities::Stats; #define QDEBUG_TERM_OUT(TYPE, OUT) \ QDebug operator<<(QDebug dbg, const KAStats::Terms::TYPE &_) \ { \ using namespace KAStats::Terms; \ dbg.nospace() << #TYPE << ": " << (OUT); \ return dbg; \ } QDEBUG_TERM_OUT(Order, _ == HighScoredFirst ? "HighScore" : _ == RecentlyUsedFirst ? "RecentlyUsed" : _ == RecentlyCreatedFirst ? "RecentlyCreated" : "Alphabetical" ) QDEBUG_TERM_OUT(Select, _ == LinkedResources ? "LinkedResources" : _ == UsedResources ? "UsedResources" : "AllResources" ) QDEBUG_TERM_OUT(Type, _.values) QDEBUG_TERM_OUT(Agent, _.values) QDEBUG_TERM_OUT(Activity, _.values) QDEBUG_TERM_OUT(Url, _.values) QDEBUG_TERM_OUT(Limit, _.value) QDEBUG_TERM_OUT(Offset, _.value) -QDEBUG_TERM_OUT(Date, _.value) +QDEBUG_TERM_OUT(Date, _.end.isNull() ? _.start.toString(Qt::ISODate) : + _.start.toString(Qt::ISODate) + QStringLiteral(",") + _.end.toString(Qt::ISODate)) #undef QDEBUG_TERM_OUT diff --git a/src/terms.h b/src/terms.h index fe1a100..f0868a2 100644 --- a/src/terms.h +++ b/src/terms.h @@ -1,251 +1,256 @@ /* * 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 . */ #ifndef KACTIVITIES_STATS_TERMS_H #define KACTIVITIES_STATS_TERMS_H #include #ifdef Q_COMPILER_INITIALIZER_LISTS #include #endif #include #include #include #include "kactivitiesstats_export.h" namespace KActivities { namespace Stats { namespace Terms { /** * Enumerator specifying the ordering in which the * results of the query should be listed */ enum KACTIVITIESSTATS_EXPORT Order { HighScoredFirst, ///< Resources with the highest scores first RecentlyUsedFirst, ///< Recently used resources first RecentlyCreatedFirst, ///< Recently created resources first OrderByUrl, ///< Order by uri, alphabetically OrderByTitle ///< Order by uri, alphabetically }; /** * Which resources should be returned */ enum KACTIVITIESSTATS_EXPORT Select { LinkedResources, ///< Resources linked to an activity, or globally UsedResources, ///< Resources that have been accessed AllResources ///< Combined set of accessed and linked resources }; /** * How many items do you need? */ struct KACTIVITIESSTATS_EXPORT Limit { Limit(int value); static Limit all(); int value; }; /** * How many items to skip? * This can be specified only if limit is also set to a finite value. */ struct KACTIVITIESSTATS_EXPORT Offset { Offset(int value); int value; }; /** * Term to filter the resources according to their types */ struct KACTIVITIESSTATS_EXPORT Type { /** * Show resources of any type */ static Type any(); #ifdef Q_COMPILER_INITIALIZER_LISTS inline Type(std::initializer_list types) : values(types) { } #endif Type(QStringList types); Type(QString type); const QStringList values; }; /** * Term to filter the resources according the agent (application) which * accessed it */ struct KACTIVITIESSTATS_EXPORT Agent { /** * Show resources accessed/linked by any application */ static Agent any(); /** * Show resources not tied to a specific agent */ static Agent global(); /** * Show resources accessed/linked by the current application */ static Agent current(); #ifdef Q_COMPILER_INITIALIZER_LISTS inline Agent(std::initializer_list agents) : values(agents) { } #endif Agent(QStringList agents); Agent(QString agent); const QStringList values; }; /** * Term to filter the resources according the activity in which they * were accessed */ struct KACTIVITIESSTATS_EXPORT Activity { /** * Show resources accessed in / linked to any activity */ static Activity any(); /** * Show resources linked to all activities */ static Activity global(); /** * Show resources linked to all activities */ static Activity current(); #ifdef Q_COMPILER_INITIALIZER_LISTS inline Activity(std::initializer_list activities) : values(activities) { } #endif Activity(QStringList activities); Activity(QString activity); const QStringList values; }; /** * Url filtering. */ struct KACTIVITIESSTATS_EXPORT Url { /** * Show only resources that start with the specified prefix */ static Url startsWith(const QString &prefix); /** * Show resources that contain the specified infix */ static Url contains(const QString &infix); /** * Show local files */ static Url localFile(); /** * Show local files, smb, fish, ftp and stfp */ static Url file(); #ifdef Q_COMPILER_INITIALIZER_LISTS inline Url(std::initializer_list urlPatterns) : values(urlPatterns) { } #endif Url(QStringList urlPatterns); Url(QString urlPattern); const QStringList values; }; /** * On which start access date do you want to filter ? */ struct KACTIVITIESSTATS_EXPORT Date { Date(QDate value); + Date(QDate start, QDate end); + static Date today(); static Date yesterday(); + static Date currentWeek(); + static Date previousWeek(); static Date fromString(QString); - QDate value; + + QDate start, end; }; } // namespace Terms } // namespace Stats } // namespace KActivities KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Order &order); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Select &select); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Type &type); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Agent &agent); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Activity &activity); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Url &url); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Limit &limit); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Offset &offset); KACTIVITIESSTATS_EXPORT QDebug operator<<(QDebug dbg, const KActivities::Stats::Terms::Date &date); #endif // KACTIVITIES_STATS_TERMS_H diff --git a/tests/model/window.cpp b/tests/model/window.cpp index f9d0e89..6bd6ceb 100644 --- a/tests/model/window.cpp +++ b/tests/model/window.cpp @@ -1,368 +1,368 @@ /* * Copyright (C) 2015 Ivan Cukic * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2, * or (at your option) any later version, as published by the Free * Software Foundation * * This program 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 General Public License for more details * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "window.h" #include "ui_window.h" #include "modeltest.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace KAStats = KActivities::Stats; using namespace KAStats; using namespace KAStats::Terms; class Delegate: public QItemDelegate { public: void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { painter->save(); const QString title = index.data(ResultModel::TitleRole).toString(); QRect titleRect = painter->fontMetrics().boundingRect(title); int lineHeight = titleRect.height(); // Header background titleRect.moveTop(option.rect.top()); titleRect.setWidth(option.rect.width()); painter->fillRect(titleRect.x(), titleRect.y(), titleRect.width(), titleRect.height() + 16, QColor(32, 32, 32)); // Painting the title painter->setPen(QColor(255,255,255)); titleRect.moveTop(titleRect.top() + 8); titleRect.setLeft(8); titleRect.setWidth(titleRect.width() - 8); painter->drawText(titleRect, index.data(ResultModel::TitleRole).toString()); // Painting the score painter->drawText(titleRect, QStringLiteral("Score: ") + QString::number(index.data(ResultModel::ScoreRole).toDouble()), QTextOption(Qt::AlignRight)); // Painting the moification and creation times titleRect.moveTop(titleRect.bottom() + 16); painter->fillRect(titleRect.x() - 4, titleRect.y() - 8, titleRect.width() + 8, titleRect.height() + 8 + lineHeight, QColor(64, 64, 64)); titleRect.moveTop(titleRect.top() - 4); painter->drawText(titleRect, index.data(ResultModel::ResourceRole).toString() ); auto firstUpdate = QDateTime::fromTime_t(index.data(ResultModel::FirstUpdateRole).toUInt()); auto lastUpdate = QDateTime::fromTime_t(index.data(ResultModel::LastUpdateRole).toUInt()); titleRect.moveTop(titleRect.top() + lineHeight); painter->drawText(titleRect, QStringLiteral("Modified: ") + lastUpdate.toString() ); painter->drawText(titleRect, QStringLiteral("Created: ") + firstUpdate.toString(), QTextOption(Qt::AlignRight)); painter->restore(); } QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override { Q_UNUSED(option); Q_UNUSED(index); return QSize(0, 100); } }; Window::Window() : ui(new Ui::MainWindow()) , model(nullptr) , activities(new KActivities::Consumer()) { ui->setupUi(this); ui->viewResults->setItemDelegate(new Delegate()); // ui->viewResults->setUniformItemSizes(true); // ui->viewResults->setGridSize(QSize(200, 100)); while (activities->serviceStatus() == KActivities::Consumer::Unknown) { QCoreApplication::processEvents(); } connect(ui->buttonUpdate, SIGNAL(clicked()), this, SLOT(updateResults())); connect(ui->buttonReloadRowCount, SIGNAL(clicked()), this, SLOT(updateRowCount())); for (const auto &activity : (QStringList() << QStringLiteral(":current") << QStringLiteral(":any") << QStringLiteral(":global")) + activities->activities()) { ui->comboActivity->addItem(activity); } const auto argumentsList = QCoreApplication::arguments(); for (const auto &arg : argumentsList) { if (arg == QLatin1String("--used")) { ui->radioSelectUsedResources->setChecked(true); } else if (arg == QLatin1String("--linked")) { ui->radioSelectLinkedResources->setChecked(true); } else if (arg == QLatin1String("--combined")) { ui->radioSelectAllResources->setChecked(true); } else if (arg.startsWith(QLatin1String("--activity="))) { ui->comboActivity->setCurrentText(arg.split(QLatin1String("="))[1]); } else if (arg.startsWith(QLatin1String("--agent="))) { ui->textAgent->setText(arg.split(QLatin1String("="))[1]); } else if (arg.startsWith(QLatin1String("--mimetype="))) { ui->textMimetype->setText(arg.split(QLatin1String("="))[1]); } else if (arg == QLatin1String("--select")) { updateResults(); } } auto redisplayAction = new QAction(this); addAction(redisplayAction); redisplayAction->setShortcut(Qt::Key_F5); connect(redisplayAction, SIGNAL(triggered()), this, SLOT(updateResults())); // loading the presets const auto recentQueryBase = UsedResources | RecentlyUsedFirst | Agent::any() | Type::any() | Activity::current(); const auto popularQueryBase = UsedResources | HighScoredFirst | Agent::any() | Type::any() | Activity::current(); presets = { { QStringLiteral("kicker-favorites"), LinkedResources | Agent { QStringLiteral("org.kde.plasma.favorites.applications"), QStringLiteral("org.kde.plasma.favorites.documents"), QStringLiteral("org.kde.plasma.favorites.contacts") } | Type::any() | Activity::current() | Activity::global() | Limit(15) }, { QStringLiteral("kicker-recent-apps-n-docs"), recentQueryBase | Url::startsWith(QStringLiteral("applications:")) | Url::file() | Limit(30) }, { QStringLiteral("kicker-recent-apps"), recentQueryBase | Url::startsWith(QStringLiteral("applications:")) | Limit(15) }, { QStringLiteral("kicker-recent-docs"), recentQueryBase | Url::file() | Limit(15) }, { QStringLiteral("kicker-popular-apps-n-docs"), popularQueryBase | Url::startsWith(QStringLiteral("applications:")) | Url::file() | Limit(30) }, { QStringLiteral("kicker-popular-apps"), popularQueryBase | Url::startsWith(QStringLiteral("applications:")) | Limit(15) }, { QStringLiteral("kicker-popular-docs"), popularQueryBase | Url::file() | Limit(15) } }; ui->comboPreset->addItem(QStringLiteral("Choose a preset"), QVariant()); for (const auto& presetId: presets.keys()) { ui->comboPreset->addItem(presetId, presetId); } connect(ui->comboPreset, SIGNAL(activated(int)), this, SLOT(selectPreset())); } Window::~Window() { } void Window::selectPreset() { const auto id = ui->comboPreset->currentData().toString(); if (id.isEmpty()) return; const auto &query = presets[id]; qDebug() << "Id: " << id; qDebug() << "Query: " << query; // Selection qDebug() << "\tSelection:" << query.selection(); ui->radioSelectUsedResources->setChecked(query.selection() == UsedResources); ui->radioSelectLinkedResources->setChecked(query.selection() == LinkedResources); ui->radioSelectAllResources->setChecked(query.selection() == AllResources); // Ordering qDebug() << "\tOrdering:" << query.ordering(); ui->radioOrderHighScoredFirst->setChecked(query.ordering() == HighScoredFirst); ui->radioOrderRecentlyUsedFirst->setChecked(query.ordering() == RecentlyUsedFirst); ui->radioOrderRecentlyCreatedFirst->setChecked(query.ordering() == RecentlyCreatedFirst); ui->radioOrderByUrl->setChecked(query.ordering() == OrderByUrl); ui->radioOrderByTitle->setChecked(query.ordering() == OrderByTitle); // Agents qDebug() << "\tAgents:" << query.agents(); ui->textAgent->setText(query.agents().join(QLatin1Char(','))); // Types qDebug() << "\tTypes:" << query.types(); ui->textMimetype->setText(query.types().join(QLatin1Char(','))); // Activities qDebug() << "\tActivities:" << query.activities(); ui->comboActivity->setEditText(query.activities().join(QLatin1Char(','))); // Url filters qDebug() << "\tUrl filters:" << query.urlFilters(); ui->textUrl->setText(query.urlFilters().join(QLatin1Char(','))); // Limit ui->spinLimitCount->setValue(query.limit()); updateResults(); } void Window::updateRowCount() { ui->labelRowCount->setText(QString::number( ui->viewResults->model()->rowCount() )); } void Window::setQuery(const KActivities::Stats::Query &query) { qDebug() << "Updating the results"; ui->viewResults->setModel(nullptr); // Log results QString buffer; for (const ResultSet::Result &result : ResultSet(query)) { buffer += result.title() + QStringLiteral(" (") + result.resource() + QStringLiteral(")\n"); } ui->textLog->setText(buffer); model.reset(new ResultModel(query)); if (QCoreApplication::arguments().contains(QLatin1String("--enable-model-test"))) { modelTest.reset(); modelTest.reset(new ModelTest(new ResultModel(query))); } ui->viewResults->setModel(model.get()); // QML auto context = ui->viewResultsQML->rootContext(); ui->viewResultsQML->setResizeMode(QQuickWidget::SizeRootObjectToView); context->setContextProperty(QStringLiteral("kamdmodel"), model.get()); ui->viewResultsQML->setSource(QUrl(QStringLiteral("qrc:/main.qml"))); } void Window::updateResults() { qDebug() << "Updating the results"; QString textDate = ui->textDate->text(); setQuery( // What should we get ( ui->radioSelectUsedResources->isChecked() ? UsedResources : ui->radioSelectLinkedResources->isChecked() ? LinkedResources : AllResources ) | // How we should order it ( ui->radioOrderHighScoredFirst->isChecked() ? HighScoredFirst : ui->radioOrderRecentlyUsedFirst->isChecked() ? RecentlyUsedFirst : ui->radioOrderRecentlyCreatedFirst->isChecked() ? RecentlyCreatedFirst : ui->radioOrderByUrl->isChecked() ? OrderByUrl : OrderByTitle ) | // Which agents? Agent(ui->textAgent->text().split(QLatin1Char(','))) | // Which mime? Type(ui->textMimetype->text().split(QLatin1Char(','))) | // Which activities? Activity(ui->comboActivity->currentText().split(QLatin1Char(','))) | // And URL filters Url(ui->textUrl->text().split(QLatin1Char(','), QString::SkipEmptyParts)) | // And date filter ( textDate == QStringLiteral("today") ? Date::today() : textDate == QStringLiteral("yesterday") ? Date::yesterday() : - Date(QDate::fromString(textDate, Qt::ISODate)) + Date::fromString(textDate) ) | // And how many items Limit(ui->spinLimitCount->value()) ); }