diff --git a/CMakeLists.txt b/CMakeLists.txt index c05670d..292e48a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,74 +1,75 @@ # vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: cmake_minimum_required (VERSION 2.8.12) 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 (REQUIRED_QT_VERSION 5.3.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 5.18.0 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} ${ECM_KDE_MODULE_DIR}) include (KDEInstallDirs) include (KDECMakeSettings) include (KDECompilerSettings NO_POLICY_SCOPE) include (GenerateExportHeader) include (ECMGenerateHeaders) # Qt set (CMAKE_AUTOMOC ON) find_package (Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED COMPONENTS Core DBus) # KDE Frameworks set(KF5_VERSION "5.19.0") # handled by release scripts set(KF5_DEP_VERSION "5.18.0") # handled by release scripts find_package (KF5DBusAddons ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5I18n ${KF5_DEP_VERSION} CONFIG REQUIRED) # Basic includes include (CPack) include (ECMPackageConfigHelpers) include (ECMSetupVersion) # Adding local CMake modules set ( CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH} ) +add_definitions(-DQT_NO_URL_CAST_FROM_STRING) 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) # Write out the features feature_summary (WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/service/plugins/sqlite/ResourceLinking.cpp b/src/service/plugins/sqlite/ResourceLinking.cpp index 560e6a3..04c6368 100644 --- a/src/service/plugins/sqlite/ResourceLinking.cpp +++ b/src/service/plugins/sqlite/ResourceLinking.cpp @@ -1,289 +1,289 @@ /* * Copyright (C) 2011, 2012, 2013, 2014, 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. */ // Self #include #include "ResourceLinking.h" // Qt #include #include // KDE #include #include #include // Boost #include #include // Local #include "Debug.h" #include "Database.h" #include "Utils.h" #include "StatsPlugin.h" #include "resourcelinkingadaptor.h" ResourceLinking::ResourceLinking(QObject *parent) : QObject(parent) { new ResourcesLinkingAdaptor(this); KDBusConnectionPool::threadConnection().registerObject( QStringLiteral("/ActivityManager/Resources/Linking"), this); } void ResourceLinking::init() { auto activities = StatsPlugin::self()->activitiesInterface(); connect(activities, SIGNAL(CurrentActivityChanged(QString)), this, SLOT(onCurrentActivityChanged(QString))); connect(activities, SIGNAL(ActivityAdded(QString)), this, SLOT(onActivityAdded(QString))); connect(activities, SIGNAL(ActivityRemoved(QString)), this, SLOT(onActivityRemoved(QString))); } void ResourceLinking::LinkResourceToActivity(QString initiatingAgent, QString targettedResource, QString usedActivity) { // qDebug() << "Linking " << targettedResource << " to " << usedActivity << " from " << initiatingAgent; if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) { qWarning() << "Invalid arguments" << initiatingAgent << targettedResource << usedActivity; return; } Q_ASSERT_X(!initiatingAgent.isEmpty(), "ResourceLinking::LinkResourceToActivity", "Agent shoud not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "ResourceLinking::LinkResourceToActivity", "Activity shoud not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "ResourceLinking::LinkResourceToActivity", "Resource shoud not be empty"); Utils::prepare(resourcesDatabase(), linkResourceToActivityQuery, QStringLiteral( "INSERT OR REPLACE INTO ResourceLink" " (usedActivity, initiatingAgent, targettedResource) " "VALUES ( " "COALESCE(:usedActivity,'')," "COALESCE(:initiatingAgent,'')," "COALESCE(:targettedResource,'')" ")" )); DATABASE_TRANSACTION(resourcesDatabase()); Utils::exec(Utils::FailOnError, *linkResourceToActivityQuery, ":usedActivity" , usedActivity, ":initiatingAgent" , initiatingAgent, ":targettedResource" , targettedResource ); if (!usedActivity.isEmpty()) { // qDebug() << "Sending link event added: activities:/" << usedActivity; - org::kde::KDirNotify::emitFilesAdded(QStringLiteral("activities:/") - + usedActivity); + org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/") + + usedActivity)); if (usedActivity == StatsPlugin::self()->currentActivity()) { // qDebug() << "Sending link event added: activities:/current"; org::kde::KDirNotify::emitFilesAdded( - QStringLiteral("activities:/current")); + QUrl(QStringLiteral("activities:/current"))); } } emit ResourceLinkedToActivity(initiatingAgent, targettedResource, usedActivity); } void ResourceLinking::UnlinkResourceFromActivity(QString initiatingAgent, QString targettedResource, QString usedActivity) { // qDebug() << "Unlinking " << targettedResource << " from " << usedActivity << " from " << initiatingAgent; if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) { qWarning() << "Invalid arguments" << initiatingAgent << targettedResource << usedActivity; return; } Q_ASSERT_X(!initiatingAgent.isEmpty(), "ResourceLinking::UnlinkResourceFromActivity", "Agent shoud not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "ResourceLinking::UnlinkResourceFromActivity", "Activity shoud not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "ResourceLinking::UnlinkResourceFromActivity", "Resource shoud not be empty"); Utils::prepare(resourcesDatabase(), unlinkResourceFromActivityQuery, QStringLiteral( "DELETE FROM ResourceLink " "WHERE " "usedActivity = COALESCE(:usedActivity , '') AND " "initiatingAgent = COALESCE(:initiatingAgent , '') AND " "targettedResource = COALESCE(:targettedResource, '') " )); DATABASE_TRANSACTION(resourcesDatabase()); Utils::exec(Utils::FailOnError, *unlinkResourceFromActivityQuery, ":usedActivity" , usedActivity, ":initiatingAgent" , initiatingAgent, ":targettedResource" , targettedResource ); if (!usedActivity.isEmpty()) { // auto mangled = QString::fromUtf8(QUrl::toPercentEncoding(targettedResource)); auto mangled = QString::fromLatin1(targettedResource.toUtf8().toBase64( QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); // qDebug() << "Sending link event removed: activities:/" << usedActivity << '/' << mangled; org::kde::KDirNotify::emitFilesRemoved( - { QStringLiteral("activities:/") + usedActivity + '/' + mangled }); + { QUrl(QStringLiteral("activities:/") + usedActivity + '/' + mangled) }); if (usedActivity == StatsPlugin::self()->currentActivity()) { // qDebug() << "Sending link event removed: activities:/current/" << mangled; org::kde::KDirNotify::emitFilesRemoved({ - QStringLiteral("activities:/current/") + mangled}); + QUrl(QStringLiteral("activities:/current/") + mangled) }); } } emit ResourceUnlinkedFromActivity(initiatingAgent, targettedResource, usedActivity); } bool ResourceLinking::IsResourceLinkedToActivity(QString initiatingAgent, QString targettedResource, QString usedActivity) { if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) { return false; } Q_ASSERT_X(!initiatingAgent.isEmpty(), "ResourceLinking::IsResourceLinkedToActivity", "Agent shoud not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "ResourceLinking::IsResourceLinkedToActivity", "Activity shoud not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "ResourceLinking::IsResourceLinkedToActivity", "Resource shoud not be empty"); Utils::prepare(resourcesDatabase(), isResourceLinkedToActivityQuery, QStringLiteral( "SELECT * FROM ResourceLink " "WHERE " "usedActivity = COALESCE(:usedActivity , '') AND " "initiatingAgent = COALESCE(:initiatingAgent , '') AND " "targettedResource = COALESCE(:targettedResource, '') " )); Utils::exec(Utils::FailOnError, *isResourceLinkedToActivityQuery, ":usedActivity" , usedActivity, ":initiatingAgent" , initiatingAgent, ":targettedResource" , targettedResource ); return isResourceLinkedToActivityQuery->next(); } bool ResourceLinking::validateArguments(QString &initiatingAgent, QString &targettedResource, QString &usedActivity) { // Validating targetted resource if (targettedResource.startsWith(QStringLiteral("file://"))) { targettedResource = QUrl(targettedResource).toLocalFile(); } if (targettedResource.startsWith(QStringLiteral("/"))) { QFileInfo file(targettedResource); if (!file.exists()) { return false; } targettedResource = file.canonicalFilePath(); } // Handling special values for the agent if (initiatingAgent.isEmpty()) { initiatingAgent = ":global"; } // Handling special values for activities if (usedActivity == ":current") { usedActivity = StatsPlugin::self()->currentActivity(); } else if (usedActivity.isEmpty()) { usedActivity = ":global"; } // If the activity is not empty and the passed activity // does not exist, cancel the request if (!usedActivity.isEmpty() && !StatsPlugin::self()->listActivities().contains(usedActivity)) { return false; } // qDebug() << "agent" << initiatingAgent // << "resource" << targettedResource // << "activity" << usedActivity; return true; } void ResourceLinking::onActivityAdded(const QString &activity) { Q_UNUSED(activity); // Notify KIO // qDebug() << "Added: activities:/ (" << activity << ")"; - org::kde::KDirNotify::emitFilesAdded(QStringLiteral("activities:/")); + org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/"))); } void ResourceLinking::onActivityRemoved(const QString &activity) { // Notify KIO // qDebug() << "Removed: activities:/" << activity; org::kde::KDirNotify::emitFilesRemoved( - { QStringLiteral("activities:/") + activity }); + { QUrl(QStringLiteral("activities:/") + activity) }); // Remove statistics for the activity } void ResourceLinking::onCurrentActivityChanged(const QString &activity) { Q_UNUSED(activity); // Notify KIO // qDebug() << "Changed: activities:/current -> " << activity; org::kde::KDirNotify::emitFilesAdded( - { QStringLiteral("activities:/current") }); + { QUrl(QStringLiteral("activities:/current")) }); } diff --git a/src/service/plugins/sqlite/StatsPlugin.cpp b/src/service/plugins/sqlite/StatsPlugin.cpp index feb2f20..0e0ff31 100644 --- a/src/service/plugins/sqlite/StatsPlugin.cpp +++ b/src/service/plugins/sqlite/StatsPlugin.cpp @@ -1,727 +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 "Debug.h" #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 = Q_NULLPTR; StatsPlugin::StatsPlugin(QObject *parent, const QVariantList &args) : Plugin(parent) , m_activities(Q_NULLPTR) , m_resources(Q_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); m_activities = modules[QStringLiteral("activities")]; m_resources = modules[QStringLiteral("resources")]; m_resourceLinking->init(); // Initializing the database resourcesDatabase(); 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 shoud not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "StatsPlugin::openResourceEvent", "Activity shoud not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "StatsPlugin::openResourceEvent", "Resource shoud 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() ); } void StatsPlugin::closeResourceEvent(const QString &usedActivity, const QString &initiatingAgent, const QString &targettedResource, const QDateTime &end) { Q_ASSERT_X(!initiatingAgent.isEmpty(), "StatsPlugin::closeResourceEvent", "Agent shoud not be empty"); Q_ASSERT_X(!usedActivity.isEmpty(), "StatsPlugin::closeResourceEvent", "Activity shoud not be empty"); Q_ASSERT_X(!targettedResource.isEmpty(), "StatsPlugin::closeResourceEvent", "Resource shoud 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() ); } void StatsPlugin::detectResourceInfo(const QString &_uri) { - QString file = _uri; + const QUrl uri = QUrl::fromUserInput(_uri); - if (!file.startsWith('/')) { - QUrl uri(_uri); + if (!uri.isLocalFile()) return; - if (!uri.isLocalFile()) return; + const QString file = uri.toLocalFile(); - file = uri.toLocalFile(); + if (!QFile::exists(file)) return; - if (!QFile::exists(file)) return; - } - - KFileItem item(file); + 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() ); Utils::exec(Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity, ":since", since.toTime_t() ); } 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() ); Utils::exec(Utils::FailOnError, removeScoreCachesQuery, ":usedActivity", usedActivity, ":time", time.toTime_t() ); emit EarlierStatsDeleted(activity, months); } void StatsPlugin::DeleteStatsForResource(const QString &activity, const QString &client, const QString &resource) { Q_ASSERT_X(!client.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Agent shoud not be empty"); Q_ASSERT_X(!activity.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Activity shoud not be empty"); Q_ASSERT_X(!resource.isEmpty(), "StatsPlugin::DeleteStatsForResource", "Resource shoud 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"