diff --git a/CMakeLists.txt b/CMakeLists.txt index dd1efc0..34e804d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,80 +1,82 @@ # vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: cmake_minimum_required (VERSION 3.0) project (KActivities) option (KACTIVITIES_LIBRARY_ONLY "If true, compiles only the KActivities library, without the service and other modules." OFF) option (KACTIVITIES_ENABLE_EXCEPTIONS "If you have Boost 1.53, you need to build KActivities with exceptions enabled. This is UNTESTED and EXPERIMENTAL!" OFF) set(QT_MIN_VERSION "5.12.0") set(KF5_MIN_VERSION "5.62.0") # We don't build in-source if ("${CMAKE_SOURCE_DIR}" STREQUAL "${CMAKE_BINARY_DIR}") message ( FATAL_ERROR "kactivities require an out of source build. Please create a separate build directory and run 'cmake path_to_plasma [options]' there." ) endif () set (KACTIVITIES_CURRENT_ROOT_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}) # Extra CMake stuff include(FeatureSummary) find_package(ECM ${KF5_MIN_VERSION} NO_MODULE) set_package_properties(ECM PROPERTIES TYPE REQUIRED DESCRIPTION "Extra CMake Modules." URL "https://projects.kde.org/projects/kdesupport/extra-cmake-modules") feature_summary(WHAT REQUIRED_PACKAGES_NOT_FOUND FATAL_ON_MISSING_REQUIRED_PACKAGES) set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) include (KDEInstallDirs) include (KDECMakeSettings) include (KDECompilerSettings NO_POLICY_SCOPE) include (GenerateExportHeader) include (ECMGenerateHeaders) include (ECMQtDeclareLoggingCategory) include (ECMSetupQtPluginMacroNames) # Qt find_package (Qt5 ${QT_MIN_VERSION} CONFIG REQUIRED COMPONENTS Core DBus Widgets) # KDE Frameworks find_package (KF5DBusAddons ${KF5_MIN_VERSION} CONFIG REQUIRED) find_package (KF5I18n ${KF5_MIN_VERSION} CONFIG REQUIRED) # Basic includes include (CPack) # Adding local CMake modules set ( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) +if (EXISTS "${CMAKE_SOURCE_DIR}/.git") + add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) +endif() add_definitions( -DQT_NO_URL_CAST_FROM_STRING -DQT_NO_SIGNALS_SLOTS_KEYWORDS - -DQT_DISABLE_DEPRECATED_BEFORE=0x050700 ) add_definitions (-DTRANSLATION_DOMAIN=\"kactivities5\") if (IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/po") ki18n_install (po) endif () if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") set(CMAKE_CXX_VISIBILITY_PRESET default) set(CMAKE_VISIBILITY_INLINES_HIDDEN 0) endif () add_subdirectory (src) if (${ECM_VERSION} STRGREATER "5.58.0") install(FILES kactivitymanagerd.categories DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}) else() install(FILES kactivitymanagerd.categories DESTINATION ${KDE_INSTALL_CONFDIR}) endif() # Write out the features feature_summary (WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp b/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp index 21ffe31..ec5de6c 100644 --- a/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp +++ b/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.cpp @@ -1,140 +1,137 @@ /* * Copyright (C) 2012, 2013, 2014 Ivan Cukic * Copyright (C) 2012 Makis Marimpis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * 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, see . */ #include "GlobalShortcutsPlugin.h" #include #include -#include #include #include #include #include KAMD_EXPORT_PLUGIN(globalshortcutsplugin, GlobalShortcutsPlugin, "kactivitymanagerd-plugin-globalshortcuts.json") const auto objectNamePattern = QStringLiteral("switch-to-activity-%1"); const auto objectNamePatternLength = objectNamePattern.length() - 2; GlobalShortcutsPlugin::GlobalShortcutsPlugin(QObject *parent, const QVariantList &args) : Plugin(parent) , m_activitiesService(nullptr) - , m_signalMapper(new QSignalMapper(this)) , m_actionCollection(new KActionCollection(this)) { Q_UNUSED(args); m_actionCollection->setComponentName("ActivityManager"); m_actionCollection->setComponentDisplayName(i18n("Activity Manager")); } GlobalShortcutsPlugin::~GlobalShortcutsPlugin() { m_actionCollection->clear(); } bool GlobalShortcutsPlugin::init(QHash &modules) { Plugin::init(modules); m_activitiesService = modules["activities"]; m_activitiesList = Plugin::retrieve( m_activitiesService, "ListActivities", "QStringList"); for (const auto &activity: m_activitiesList) { activityAdded(activity); } - - connect(m_signalMapper, SIGNAL(mapped(QString)), + connect(this, SIGNAL(currentActivityChanged(QString)), m_activitiesService, SLOT(SetCurrentActivity(QString)), Qt::QueuedConnection); + connect(m_activitiesService, SIGNAL(ActivityAdded(QString)), this, SLOT(activityAdded(QString))); connect(m_activitiesService, SIGNAL(ActivityRemoved(QString)), this, SLOT(activityRemoved(QString))); m_actionCollection->readSettings(); return true; } void GlobalShortcutsPlugin::activityAdded(const QString &activity) { if (activity == "00000000-0000-0000-0000-000000000000") { return; } if (!m_activitiesList.contains(activity)) { m_activitiesList << activity; } const auto action = m_actionCollection->addAction( objectNamePattern.arg(activity)); action->setText(i18nc("@action", "Switch to activity \"%1\"", activityName(activity))); KGlobalAccel::self()->setDefaultShortcut(action, QList{}); - connect(action, SIGNAL(triggered()), m_signalMapper, SLOT(map())); - m_signalMapper->setMapping(action, activity); + connect(action, &QAction::triggered, [this, activity]() { Q_EMIT currentActivityChanged(activity);}); // m_actionCollection->writeSettings(); } QString GlobalShortcutsPlugin::activityForAction(QAction *action) const { return action->objectName().mid(objectNamePatternLength); } void GlobalShortcutsPlugin::activityRemoved(const QString &deletedActivity) { m_activitiesList.removeAll(deletedActivity); // Removing all shortcuts that refer to an unknown activity for (const auto &action: m_actionCollection->actions()) { const auto actionActivity = activityForAction(action); if ((deletedActivity.isEmpty() && !m_activitiesList.contains(actionActivity)) || deletedActivity == actionActivity) { KGlobalAccel::self()->removeAllShortcuts(action); m_actionCollection->removeAction(action); } } m_actionCollection->writeSettings(); } void GlobalShortcutsPlugin::activityChanged(const QString &activity) { for (const auto &action: m_actionCollection->actions()) { if (activity == activityForAction(action)) { action->setText(i18nc("@action", "Switch to activity \"%1\"", activityName(activity))); } } } QString GlobalShortcutsPlugin::activityName(const QString &activity) const { return Plugin::retrieve( m_activitiesService, "ActivityName", "QString", Q_ARG(QString, activity)); } #include "GlobalShortcutsPlugin.moc" diff --git a/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h b/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h index b1d3bbc..6f61862 100644 --- a/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h +++ b/src/service/plugins/globalshortcuts/GlobalShortcutsPlugin.h @@ -1,53 +1,53 @@ /* * Copyright (C) 2012 Makis Marimpis * Copyright (C) 2012, 2013, 2014 Ivan Cukic * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * 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, see . */ #ifndef PLUGINS_GLOBAL_SHORTCUTS_PLUGIN_H #define PLUGINS_GLOBAL_SHORTCUTS_PLUGIN_H #include - -class QSignalMapper; class KActionCollection; class QAction; class GlobalShortcutsPlugin : public Plugin { Q_OBJECT // Q_PLUGIN_METADATA(IID "org.kde.ActivityManager.plugins.globalshortcutsplugin") public: explicit GlobalShortcutsPlugin(QObject *parent = nullptr, const QVariantList &args = QVariantList()); ~GlobalShortcutsPlugin() override; bool init(QHash &modules) override; private Q_SLOTS: void activityAdded(const QString &activity); void activityRemoved(const QString &activity); void activityChanged(const QString &activity); +Q_SIGNALS: + void currentActivityChanged(const QString &activity); + private: inline QString activityName(const QString &activity) const; inline QString activityForAction(QAction *action) const; QObject *m_activitiesService; - QSignalMapper *m_signalMapper; QStringList m_activitiesList; KActionCollection *m_actionCollection; }; #endif // PLUGINS_GLOBAL_SHORTCUTS_PLUGIN_H diff --git a/src/service/plugins/sqlite/ResourceScoreCache.cpp b/src/service/plugins/sqlite/ResourceScoreCache.cpp index f30e892..5f4c3ee 100644 --- a/src/service/plugins/sqlite/ResourceScoreCache.cpp +++ b/src/service/plugins/sqlite/ResourceScoreCache.cpp @@ -1,263 +1,263 @@ /* * Copyright (C) 2011, 2012 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. */ // Self #include #include "ResourceScoreCache.h" // STD #include // Utils #include #include // Local #include "DebugResources.h" #include "StatsPlugin.h" #include "Database.h" #include "Utils.h" class ResourceScoreCache::Queries { private: Queries() : createResourceScoreCacheQuery(resourcesDatabase()->createQuery()) , getResourceScoreCacheQuery(resourcesDatabase()->createQuery()) , updateResourceScoreCacheQuery(resourcesDatabase()->createQuery()) , getScoreAdditionQuery(resourcesDatabase()->createQuery()) { Utils::prepare(*resourcesDatabase(), createResourceScoreCacheQuery, QStringLiteral( "INSERT INTO ResourceScoreCache " "VALUES (:usedActivity, :initiatingAgent, :targettedResource, " "0, 0, " // type, score ":firstUpdate, " // lastUpdate ":firstUpdate)" )); Utils::prepare(*resourcesDatabase(), getResourceScoreCacheQuery, QStringLiteral( "SELECT cachedScore, lastUpdate, firstUpdate FROM ResourceScoreCache " "WHERE " ":usedActivity = usedActivity AND " ":initiatingAgent = initiatingAgent AND " ":targettedResource = targettedResource " )); Utils::prepare(*resourcesDatabase(), updateResourceScoreCacheQuery, QStringLiteral( "UPDATE ResourceScoreCache SET " "cachedScore = :cachedScore, " "lastUpdate = :lastUpdate " "WHERE " ":usedActivity = usedActivity AND " ":initiatingAgent = initiatingAgent AND " ":targettedResource = targettedResource " )); Utils::prepare(*resourcesDatabase(), getScoreAdditionQuery, QStringLiteral( "SELECT start, end " "FROM ResourceEvent " "WHERE " ":usedActivity = usedActivity AND " ":initiatingAgent = initiatingAgent AND " ":targettedResource = targettedResource AND " "start > :start " "ORDER BY " "start ASC" )); } public: QSqlQuery createResourceScoreCacheQuery; QSqlQuery getResourceScoreCacheQuery; QSqlQuery updateResourceScoreCacheQuery; QSqlQuery getScoreAdditionQuery; static Queries &self(); }; ResourceScoreCache::Queries &ResourceScoreCache::Queries::self() { static Queries queries; return queries; } class ResourceScoreCache::Private { public: QString activity; QString application; QString resource; inline qreal timeFactor(int days) const { // Exp is falling rather quickly, we are slowing it 32 times return std::exp(-days / 32.0); } inline qreal timeFactor(const QDateTime &fromTime, const QDateTime &toTime) const { return timeFactor(fromTime.daysTo(toTime)); } }; ResourceScoreCache::ResourceScoreCache(const QString &activity, const QString &application, const QString &resource) { d->activity = activity; d->application = application; d->resource = resource; Q_ASSERT_X(!d->application.isEmpty(), "ResourceScoreCache::constructor", "Agent should not be empty"); Q_ASSERT_X(!d->activity.isEmpty(), "ResourceScoreCache::constructor", "Activity should not be empty"); Q_ASSERT_X(!d->resource.isEmpty(), "ResourceScoreCache::constructor", "Resource should not be empty"); } ResourceScoreCache::~ResourceScoreCache() { } void ResourceScoreCache::update() { QDateTime lastUpdate; QDateTime firstUpdate; QDateTime currentTime = QDateTime::currentDateTime(); qreal score = 0; DATABASE_TRANSACTION(*resourcesDatabase()); qCDebug(KAMD_LOG_RESOURCES) << "Creating the cache for: " << d->resource; // This can fail if we have the cache already made auto isCacheNew = Utils::exec( Utils::IgnoreError, Queries::self().createResourceScoreCacheQuery, ":usedActivity", d->activity, ":initiatingAgent", d->application, ":targettedResource", d->resource, - ":firstUpdate", currentTime.toTime_t() + ":firstUpdate", currentTime.toSecsSinceEpoch() ); // Getting the old score Utils::exec( Utils::FailOnError, Queries::self().getResourceScoreCacheQuery, ":usedActivity", d->activity, ":initiatingAgent", d->application, ":targettedResource", d->resource ); // Only and always one result for (const auto &result: Queries::self().getResourceScoreCacheQuery) { - lastUpdate.setTime_t(result["lastUpdate"].toUInt()); - firstUpdate.setTime_t(result["firstUpdate"].toUInt()); + lastUpdate.setSecsSinceEpoch(result["lastUpdate"].toUInt()); + firstUpdate.setSecsSinceEpoch(result["firstUpdate"].toUInt()); qCDebug(KAMD_LOG_RESOURCES) << "Already in database? " << (!isCacheNew); qCDebug(KAMD_LOG_RESOURCES) << " First update : " << firstUpdate; qCDebug(KAMD_LOG_RESOURCES) << " Last update : " << lastUpdate; if (isCacheNew) { // If we haven't had the cache before, set the score to 0 firstUpdate = currentTime; score = 0; } else { // Adjusting the score depending on the time that passed since the // last update score = result["cachedScore"].toReal(); score *= d->timeFactor(lastUpdate, currentTime); } } // Calculating the updated score // We are processing all events since the last cache update qCDebug(KAMD_LOG_RESOURCES) << "After the adjustment"; qCDebug(KAMD_LOG_RESOURCES) << " Current score : " << score; qCDebug(KAMD_LOG_RESOURCES) << " First update : " << firstUpdate; qCDebug(KAMD_LOG_RESOURCES) << " Last update : " << lastUpdate; Utils::exec(Utils::FailOnError, Queries::self().getScoreAdditionQuery, ":usedActivity", d->activity, ":initiatingAgent", d->application, ":targettedResource", d->resource, - ":start", lastUpdate.toTime_t() + ":start", lastUpdate.toSecsSinceEpoch() ); - uint lastEventStart = currentTime.toTime_t(); + uint lastEventStart = currentTime.toSecsSinceEpoch(); for (const auto &result: Queries::self().getScoreAdditionQuery) { lastEventStart = result["start"].toUInt(); const auto end = result["end"].toUInt(); const auto intervalLength = end - lastEventStart; qCDebug(KAMD_LOG_RESOURCES) << "Interval length is " << intervalLength; if (intervalLength == 0) { // We have an Accessed event - otherwise, this wouldn't be 0 - score += d->timeFactor(QDateTime::fromTime_t(end), currentTime); // like it is open for 1 minute + score += d->timeFactor(QDateTime::fromSecsSinceEpoch(end), currentTime); // like it is open for 1 minute } else { - score += d->timeFactor(QDateTime::fromTime_t(end), currentTime) * intervalLength / 60.0; + score += d->timeFactor(QDateTime::fromSecsSinceEpoch(end), currentTime) * intervalLength / 60.0; } } qCDebug(KAMD_LOG_RESOURCES) << " New score : " << score; // Updating the score Utils::exec(Utils::FailOnError, Queries::self().updateResourceScoreCacheQuery, ":usedActivity", d->activity, ":initiatingAgent", d->application, ":targettedResource", d->resource, ":cachedScore", score, ":lastUpdate", lastEventStart ); // Notifying the world qCDebug(KAMD_LOG_RESOURCES) << "ResourceScoreUpdated:" << d->activity << d->application << d->resource ; emit QMetaObject::invokeMethod(StatsPlugin::self(), "ResourceScoreUpdated", Qt::QueuedConnection, Q_ARG(QString, d->activity), Q_ARG(QString, d->application), Q_ARG(QString, d->resource), Q_ARG(double, score), Q_ARG(uint, lastEventStart), - Q_ARG(uint, firstUpdate.toTime_t()) + Q_ARG(uint, firstUpdate.toSecsSinceEpoch()) ); } diff --git a/src/service/plugins/sqlite/StatsPlugin.cpp b/src/service/plugins/sqlite/StatsPlugin.cpp index cd481f9..b362d7a 100644 --- a/src/service/plugins/sqlite/StatsPlugin.cpp +++ b/src/service/plugins/sqlite/StatsPlugin.cpp @@ -1,723 +1,723 @@ /* * Copyright (C) 2011, 2012, 2013, 2014 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. */ // Self #include #include "StatsPlugin.h" // Qt #include #include #include // KDE #include #include #include // Boost #include #include // Local #include "Database.h" #include "ResourceScoreMaintainer.h" #include "ResourceLinking.h" #include "Utils.h" #include "../../Event.h" #include "resourcescoringadaptor.h" #include "common/specialvalues.h" KAMD_EXPORT_PLUGIN(sqliteplugin, StatsPlugin, "kactivitymanagerd-plugin-sqlite.json") StatsPlugin *StatsPlugin::s_instance = nullptr; StatsPlugin::StatsPlugin(QObject *parent, const QVariantList &args) : Plugin(parent) , m_activities(nullptr) , m_resources(nullptr) , m_resourceLinking(new ResourceLinking(this)) { Q_UNUSED(args); s_instance = this; new ResourcesScoringAdaptor(this); KDBusConnectionPool::threadConnection().registerObject( QStringLiteral("/ActivityManager/Resources/Scoring"), this); setName(QStringLiteral("org.kde.ActivityManager.Resources.Scoring")); } bool StatsPlugin::init(QHash &modules) { Plugin::init(modules); if (!resourcesDatabase()) { return false; } m_activities = modules[QStringLiteral("activities")]; m_resources = modules[QStringLiteral("resources")]; m_resourceLinking->init(); connect(m_resources, SIGNAL(ProcessedResourceEvents(EventList)), this, SLOT(addEvents(EventList))); connect(m_resources, SIGNAL(RegisteredResourceMimetype(QString, QString)), this, SLOT(saveResourceMimetype(QString, QString))); connect(m_resources, SIGNAL(RegisteredResourceTitle(QString, QString)), this, SLOT(saveResourceTitle(QString, QString))); connect(modules[QStringLiteral("config")], SIGNAL(pluginConfigChanged()), this, SLOT(loadConfiguration())); loadConfiguration(); return true; } void StatsPlugin::loadConfiguration() { auto conf = config(); conf.config()->reparseConfiguration(); const QString configFile = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + QStringLiteral("kactivitymanagerd-pluginsrc"); m_blockedByDefault = conf.readEntry("blocked-by-default", false); m_blockAll = false; m_whatToRemember = (WhatToRemember)conf.readEntry("what-to-remember", (int)AllApplications); m_apps.clear(); if (m_whatToRemember == SpecificApplications) { auto apps = conf.readEntry( m_blockedByDefault ? "allowed-applications" : "blocked-applications", QStringList()); m_apps.insert(apps.cbegin(), apps.cend()); } // Delete old events, as per configuration. // For people who do not restart their computers, we should do this from // time to time. Doing this twice a day should be more than enough. deleteOldEvents(); m_deleteOldEventsTimer.setInterval(12 * 60 * 60 * 1000); connect(&m_deleteOldEventsTimer, &QTimer::timeout, this, &StatsPlugin::deleteOldEvents); // Loading URL filters m_urlFilters.clear(); auto filters = conf.readEntry("url-filters", QStringList() << "about:*" // Ignore about: stuff << "*/.*" // Ignore hidden files << "/" // Ignore root << "/tmp/*" // Ignore everything in /tmp ); for (const auto& filter: filters) { m_urlFilters << Common::starPatternToRegex(filter); } // Loading the private activities m_otrActivities = conf.readEntry("off-the-record-activities", QStringList()); } void StatsPlugin::deleteOldEvents() { DeleteEarlierStats(QString(), config().readEntry("keep-history-for", 0)); } void StatsPlugin::openResourceEvent(const QString &usedActivity, const QString &initiatingAgent, const QString &targettedResource, const QDateTime &start, const QDateTime &end) { Q_ASSERT_X(!initiatingAgent.isEmpty(), "StatsPlugin::openResourceEvent", "Agent should not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "StatsPlugin::openResourceEvent", "Activity should not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "StatsPlugin::openResourceEvent", "Resource should not be empty"); detectResourceInfo(targettedResource); Utils::prepare(*resourcesDatabase(), openResourceEventQuery, QStringLiteral( "INSERT INTO ResourceEvent" " (usedActivity, initiatingAgent, targettedResource, start, end) " "VALUES (:usedActivity, :initiatingAgent, :targettedResource, :start, :end)" )); Utils::exec(Utils::FailOnError, *openResourceEventQuery, ":usedActivity" , usedActivity , ":initiatingAgent" , initiatingAgent , ":targettedResource" , targettedResource , - ":start" , start.toTime_t() , - ":end" , (end.isNull()) ? QVariant() : end.toTime_t() + ":start" , start.toSecsSinceEpoch() , + ":end" , (end.isNull()) ? QVariant() : end.toSecsSinceEpoch() ); } void StatsPlugin::closeResourceEvent(const QString &usedActivity, const QString &initiatingAgent, const QString &targettedResource, const QDateTime &end) { Q_ASSERT_X(!initiatingAgent.isEmpty(), "StatsPlugin::closeResourceEvent", "Agent should not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "StatsPlugin::closeResourceEvent", "Activity should not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "StatsPlugin::closeResourceEvent", "Resource should not be empty"); Utils::prepare(*resourcesDatabase(), closeResourceEventQuery, QStringLiteral( "UPDATE ResourceEvent " "SET end = :end " "WHERE " ":usedActivity = usedActivity AND " ":initiatingAgent = initiatingAgent AND " ":targettedResource = targettedResource AND " "end IS NULL" )); Utils::exec(Utils::FailOnError, *closeResourceEventQuery, ":usedActivity" , usedActivity , ":initiatingAgent" , initiatingAgent , ":targettedResource" , targettedResource , - ":end" , end.toTime_t() + ":end" , end.toSecsSinceEpoch() ); } void StatsPlugin::detectResourceInfo(const QString &_uri) { const QUrl uri = QUrl::fromUserInput(_uri); if (!uri.isLocalFile()) return; const QString file = uri.toLocalFile(); if (!QFile::exists(file)) return; KFileItem item(uri); if (insertResourceInfo(file)) { saveResourceMimetype(file, item.mimetype(), true); const auto text = item.text(); saveResourceTitle(file, text.isEmpty() ? _uri : text, true); } } bool StatsPlugin::insertResourceInfo(const QString &uri) { Utils::prepare(*resourcesDatabase(), getResourceInfoQuery, QStringLiteral( "SELECT targettedResource FROM ResourceInfo WHERE " " targettedResource = :targettedResource " )); getResourceInfoQuery->bindValue(":targettedResource", uri); Utils::exec(Utils::FailOnError, *getResourceInfoQuery); if (getResourceInfoQuery->next()) { return false; } Utils::prepare(*resourcesDatabase(), insertResourceInfoQuery, QStringLiteral( "INSERT INTO ResourceInfo( " " targettedResource" ", title" ", autoTitle" ", mimetype" ", autoMimetype" ") VALUES (" " :targettedResource" ", '' " ", 1 " ", '' " ", 1 " ")" )); Utils::exec(Utils::FailOnError, *insertResourceInfoQuery, ":targettedResource", uri ); return true; } void StatsPlugin::saveResourceTitle(const QString &uri, const QString &title, bool autoTitle) { insertResourceInfo(uri); DATABASE_TRANSACTION(*resourcesDatabase()); Utils::prepare(*resourcesDatabase(), saveResourceTitleQuery, QStringLiteral( "UPDATE ResourceInfo SET " " title = :title" ", autoTitle = :autoTitle " "WHERE " "targettedResource = :targettedResource " )); Utils::exec(Utils::FailOnError, *saveResourceTitleQuery, ":targettedResource" , uri , ":title" , title , ":autoTitle" , (autoTitle ? "1" : "0") ); } void StatsPlugin::saveResourceMimetype(const QString &uri, const QString &mimetype, bool autoMimetype) { insertResourceInfo(uri); DATABASE_TRANSACTION(*resourcesDatabase()); Utils::prepare(*resourcesDatabase(), saveResourceMimetypeQuery, QStringLiteral( "UPDATE ResourceInfo SET " " mimetype = :mimetype" ", autoMimetype = :autoMimetype " "WHERE " "targettedResource = :targettedResource " )); Utils::exec(Utils::FailOnError, *saveResourceMimetypeQuery, ":targettedResource" , uri , ":mimetype" , mimetype , ":autoMimetype" , (autoMimetype ? "1" : "0") ); } StatsPlugin *StatsPlugin::self() { return s_instance; } bool StatsPlugin::acceptedEvent(const Event &event) { using std::bind; using std::any_of; using namespace std::placeholders; return !( // If the URI is empty, we do not want to process it event.uri.isEmpty() || // Skip if the current activity is OTR m_otrActivities.contains(currentActivity()) || // Exclude URIs that match the ignored patterns any_of(m_urlFilters.cbegin(), m_urlFilters.cend(), bind(&QRegExp::exactMatch, _1, event.uri)) || // if blocked by default, the list contains allowed applications // ignore event if the list doesn't contain the application // if not blocked by default, the list contains blocked applications // ignore event if the list contains the application (m_whatToRemember == SpecificApplications && m_blockedByDefault != boost::binary_search(m_apps, event.application)) ); } Event StatsPlugin::validateEvent(Event event) { if (event.uri.startsWith(QStringLiteral("file://"))) { event.uri = QUrl(event.uri).toLocalFile(); } if (event.uri.startsWith(QStringLiteral("/"))) { QFileInfo file(event.uri); event.uri = file.exists() ? file.canonicalFilePath() : QString(); } return event; } QStringList StatsPlugin::listActivities() const { return Plugin::retrieve( m_activities, "ListActivities", "QStringList"); } QString StatsPlugin::currentActivity() const { return Plugin::retrieve( m_activities, "CurrentActivity", "QString"); } void StatsPlugin::addEvents(const EventList &events) { using namespace kamd::utils; if (m_blockAll || m_whatToRemember == NoApplications) { return; } const auto &eventsToProcess = events | transformed(&StatsPlugin::validateEvent, this) | filtered(&StatsPlugin::acceptedEvent, this); if (eventsToProcess.begin() == eventsToProcess.end()) return; DATABASE_TRANSACTION(*resourcesDatabase()); for (auto event : eventsToProcess) { switch (event.type) { case Event::Accessed: openResourceEvent( currentActivity(), event.application, event.uri, event.timestamp, event.timestamp); ResourceScoreMaintainer::self()->processResource( event.uri, event.application); break; case Event::Opened: openResourceEvent( currentActivity(), event.application, event.uri, event.timestamp); break; case Event::Closed: closeResourceEvent( currentActivity(), event.application, event.uri, event.timestamp); ResourceScoreMaintainer::self()->processResource( event.uri, event.application); break; case Event::UserEventType: ResourceScoreMaintainer::self()->processResource( event.uri, event.application); break; default: // Nothing yet // TODO: Add focus and modification break; } } } void StatsPlugin::DeleteRecentStats(const QString &activity, int count, const QString &what) { const auto usedActivity = activity.isEmpty() ? QVariant() : QVariant(activity); // If we need to delete everything, // no need to bother with the count and the date DATABASE_TRANSACTION(*resourcesDatabase()); if (what == QStringLiteral("everything")) { // Instantiating these every time is not a big overhead // since this method is rarely executed. auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity)" ); auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); removeScoreCachesQuery.prepare( "DELETE FROM ResourceScoreCache " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity)"); Utils::exec(Utils::FailOnError, removeEventsQuery, ":usedActivity", usedActivity); Utils::exec(Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity); } else { // Deleting a specified length of time auto since = QDateTime::currentDateTime(); since = (what[0] == QLatin1Char('h')) ? since.addSecs(-count * 60 * 60) : (what[0] == QLatin1Char('d')) ? since.addDays(-count) : (what[0] == QLatin1Char('m')) ? since.addMonths(-count) : since; // Maybe we should decrease the scores for the previously // cached items. Thinking it is not that important - // if something was accessed before, and the user did not // remove the history, it is not really a secret. auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " "AND end > :since" ); auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); removeScoreCachesQuery.prepare( "DELETE FROM ResourceScoreCache " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " "AND firstUpdate > :since"); Utils::exec(Utils::FailOnError, removeEventsQuery, ":usedActivity", usedActivity, - ":since", since.toTime_t() + ":since", since.toSecsSinceEpoch() ); Utils::exec(Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity, - ":since", since.toTime_t() + ":since", since.toSecsSinceEpoch() ); } emit RecentStatsDeleted(activity, count, what); } void StatsPlugin::DeleteEarlierStats(const QString &activity, int months) { if (months == 0) { return; } // Deleting a specified length of time DATABASE_TRANSACTION(*resourcesDatabase()); const auto time = QDateTime::currentDateTime().addMonths(-months); const auto usedActivity = activity.isEmpty() ? QVariant() : QVariant(activity); auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " "AND start < :time" ); auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); removeScoreCachesQuery.prepare( "DELETE FROM ResourceScoreCache " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " "AND lastUpdate < :time"); Utils::exec(Utils::FailOnError, removeEventsQuery, ":usedActivity", usedActivity, - ":time", time.toTime_t() + ":time", time.toSecsSinceEpoch() ); Utils::exec(Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity, - ":time", time.toTime_t() + ":time", time.toSecsSinceEpoch() ); emit EarlierStatsDeleted(activity, months); } void StatsPlugin::DeleteStatsForResource(const QString &activity, const QString &client, const QString &resource) { Q_ASSERT_X(!client.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Agent should not be empty"); Q_ASSERT_X(!activity.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Activity should not be empty"); Q_ASSERT_X(!resource.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Resource should not be empty"); Q_ASSERT_X(client != CURRENT_AGENT_TAG, "StatsPlugin::DeleteStatsForResource", "We can not handle CURRENT_AGENT_TAG here"); DATABASE_TRANSACTION(*resourcesDatabase()); // Check against sql injection if (activity.contains('\'') || client.contains('\'')) return; const auto activityFilter = activity == ANY_ACTIVITY_TAG ? " 1 " : QStringLiteral(" usedActivity = '%1' ").arg( activity == CURRENT_ACTIVITY_TAG ? currentActivity() : activity ); const auto clientFilter = client == ANY_AGENT_TAG ? " 1 " : QStringLiteral(" initiatingAgent = '%1' ").arg(client); auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE " + activityFilter + " AND " + clientFilter + " AND " + "targettedResource LIKE :targettedResource ESCAPE '\\'" ); auto removeScoreCachesQuery = resourcesDatabase()->createQuery(); removeScoreCachesQuery.prepare( "DELETE FROM ResourceScoreCache " "WHERE " + activityFilter + " AND " + clientFilter + " AND " + "targettedResource LIKE :targettedResource ESCAPE '\\'" ); const auto pattern = Common::starPatternToLike(resource); Utils::exec(Utils::FailOnError, removeEventsQuery, ":targettedResource", pattern); Utils::exec(Utils::FailOnError, removeScoreCachesQuery, ":targettedResource", pattern); emit ResourceScoreDeleted(activity, client, resource); } bool StatsPlugin::isFeatureOperational(const QStringList &feature) const { if (feature[0] == "isOTR") { if (feature.size() != 2) return true; const auto activity = feature[1]; return activity == "activity" || activity == "current" || listActivities().contains(activity); return true; } return false; } // bool StatsPlugin::isFeatureEnabled(const QStringList &feature) const // { // if (feature[0] == "isOTR") { // if (feature.size() != 2) return false; // // auto activity = feature[1]; // // if (activity == "activity" || activity == "current") { // activity = currentActivity(); // } // // return m_otrActivities.contains(activity); // } // // return false; // } // // void StatsPlugin::setFeatureEnabled(const QStringList &feature, bool value) // { // if (feature[0] == "isOTR") { // if (feature.size() != 2) return; // // auto activity = feature[1]; // // if (activity == "activity" || activity == "current") { // activity = currentActivity(); // } // // if (!m_otrActivities.contains(activity)) { // m_otrActivities << activity; // config().writeEntry("off-the-record-activities", m_otrActivities); // config().sync(); // } // } // } QDBusVariant StatsPlugin::featureValue(const QStringList &feature) const { if (feature[0] == "isOTR") { if (feature.size() != 2) return QDBusVariant(false); auto activity = feature[1]; if (activity == "activity" || activity == "current") { activity = currentActivity(); } return QDBusVariant(m_otrActivities.contains(activity)); } return QDBusVariant(false); } void StatsPlugin::setFeatureValue(const QStringList &feature, const QDBusVariant &value) { if (feature[0] == "isOTR") { if (feature.size() != 2) return; auto activity = feature[1]; if (activity == "activity" || activity == "current") { activity = currentActivity(); } bool isOTR = value.variant().toBool(); if (isOTR && !m_otrActivities.contains(activity)) { m_otrActivities << activity; } else if (!isOTR && m_otrActivities.contains(activity)) { m_otrActivities.removeAll(activity); } config().writeEntry("off-the-record-activities", m_otrActivities); config().sync(); } } QStringList StatsPlugin::listFeatures(const QStringList &feature) const { if (feature.isEmpty() || feature[0].isEmpty()) { return { "isOTR/" }; } else if (feature[0] == "isOTR") { return listActivities(); } return QStringList(); } #include "StatsPlugin.moc"