diff --git a/src/common/database/Database.cpp b/src/common/database/Database.cpp index ad12a70..8b93342 100644 --- a/src/common/database/Database.cpp +++ b/src/common/database/Database.cpp @@ -1,291 +1,292 @@ /* * Copyright (C) 2014 - 2016 by 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) 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 14 of version 3 of the license. * * 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 "Database.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace Common { namespace { #ifdef QT_DEBUG QString lastExecutedQuery; #endif std::mutex databases_mutex; struct DatabaseInfo { Qt::HANDLE thread; Database::OpenMode openMode; }; bool operator<(const DatabaseInfo &left, const DatabaseInfo &right) { return left.thread < right.thread ? true : left.thread > right.thread ? false : left.openMode < right.openMode; } std::map> databases; } class QSqlDatabaseWrapper { private: QSqlDatabase m_database; bool m_open; QString m_connectionName; public: QSqlDatabaseWrapper(const DatabaseInfo &info) : m_open(false) { m_connectionName = "kactivities_db_resources_" // Adding the thread number to the database name + QString::number((quintptr)info.thread) // And whether it is read-only or read-write + (info.openMode == Database::ReadOnly ? "_readonly" : "_readwrite"); m_database = QSqlDatabase::contains(m_connectionName) ? QSqlDatabase::database(m_connectionName) : QSqlDatabase::addDatabase(QStringLiteral("QSQLITE"), m_connectionName); if (info.openMode == Database::ReadOnly) { m_database.setConnectOptions(QStringLiteral("QSQLITE_OPEN_READONLY")); } // We are allowing the database file to be overridden mostly for testing purposes m_database.setDatabaseName(ResourcesDatabaseSchema::path()); m_open = m_database.open(); if (!m_open) { qWarning() << "KActivities: Database is not open: " << m_database.connectionName() << m_database.databaseName() << m_database.lastError(); if (info.openMode == Database::ReadWrite) { qFatal("KActivities: Opening the database in RW mode should always succeed"); } } } ~QSqlDatabaseWrapper() { qDebug() << "Closing SQL connection: " << m_connectionName; } QSqlDatabase &get() { return m_database; } bool isOpen() const { return m_open; } QString connectionName() const { return m_connectionName; } }; class Database::Private { public: Private() { } QSqlQuery query(const QString &query) { return database ? QSqlQuery(query, database->get()) : QSqlQuery(); } QSqlQuery query() { return database ? QSqlQuery(database->get()) : QSqlQuery(); } QScopedPointer database; }; Database::Locker::Locker(Database &database) : m_database(database.d->database->get()) { m_database.transaction(); } Database::Locker::~Locker() { m_database.commit(); } Database::Ptr Database::instance(Source source, OpenMode openMode) { Q_UNUSED(source) // for the time being std::lock_guard lock(databases_mutex); // We are saving instances per thread and per read/write mode DatabaseInfo info; info.thread = QThread::currentThreadId(); info.openMode = openMode; // Do we have an instance matching the request? auto search = databases.find(info); if (search != databases.end()) { auto ptr = search->second.lock(); if (ptr) { return ptr; } } // Creating a new database instance auto ptr = std::make_shared(); ptr->d->database.reset(new QSqlDatabaseWrapper(info)); if (!ptr->d->database->isOpen()) { - return Q_NULLPTR; + return nullptr; } databases[info] = ptr; if (info.openMode == ReadOnly) { // From now on, only SELECT queries will work ptr->setPragma(QStringLiteral("query_only = 1")); // These should not make any difference ptr->setPragma(QStringLiteral("synchronous = 0")); } else { // Using the write-ahead log and sync = NORMAL for faster writes ptr->setPragma(QStringLiteral("synchronous = 1")); } // Maybe we should use the write-ahead log auto walResult = ptr->pragma(QStringLiteral("journal_mode = WAL")); if (walResult != "wal") { - qFatal("KActivities: Database can not be opened in WAL mode. Check the " - "SQLite version (required >3.7.0). And whether your filesystem " - "supports shared memory"); + qWarning() << "KActivities: Database can not be opened in WAL mode. Check the " + "SQLite version (required >3.7.0). And whether your filesystem " + "supports shared memory"; + return nullptr; } // We don't have a big database, lets flush the WAL when // it reaches 400k, not 4M as is default ptr->setPragma(QStringLiteral("wal_autocheckpoint = 100")); qDebug() << "KActivities: Database connection: " << ptr->d->database->connectionName() << "\n query_only: " << ptr->pragma(QStringLiteral("query_only")) << "\n journal_mode: " << ptr->pragma(QStringLiteral("journal_mode")) << "\n wal_autocheckpoint: " << ptr->pragma(QStringLiteral("wal_autocheckpoint")) << "\n synchronous: " << ptr->pragma(QStringLiteral("synchronous")) ; return ptr; } Database::Database() { } Database::~Database() { } QSqlQuery Database::createQuery() const { return d->query(); } QString Database::lastQuery() const { #ifdef QT_DEBUG return lastExecutedQuery; #endif return QString(); } QSqlQuery Database::execQuery(const QString &query, bool ignoreErrors) const { #ifdef QT_NO_DEBUG return d->query(query); #else auto result = d->query(query); lastExecutedQuery = query; if (!ignoreErrors && result.lastError().isValid()) { qWarning() << "SQL: " << "\n error: " << result.lastError() << "\n query: " << query; } return result; #endif } QSqlQuery Database::execQueries(const QStringList &queries) const { QSqlQuery result; for (const auto query: queries) { result = execQuery(query); } return result; } void Database::setPragma(const QString &pragma) { execQuery(QStringLiteral("PRAGMA ") + pragma); } QVariant Database::pragma(const QString &pragma) const { return value("PRAGMA " + pragma); } QVariant Database::value(const QString &query) const { auto result = execQuery(query); return result.next() ? result.value(0) : QVariant(); } } // namespace Common diff --git a/src/service/Application.cpp b/src/service/Application.cpp index a563785..7ae5d6f 100644 --- a/src/service/Application.cpp +++ b/src/service/Application.cpp @@ -1,418 +1,425 @@ /* * Copyright (C) 2010 - 2016 by 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) 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 14 of version 3 of the license. * * 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 . */ // Self #include #include "Application.h" // Qt #include #include #include #include #include #include // KDE // #include // #include // #include #include #include #include #include #include // Boost and utils #include #include #include // System #include #include #include #include // Local #include "Activities.h" #include "Resources.h" #include "Features.h" #include "Config.h" #include "Plugin.h" #include "Debug.h" #include "common/dbus/common.h" namespace { QList s_moduleThreads; } // Runs a QObject inside a QThread template T *runInQThread() { T *object = new T(); class Thread : public QThread { public: Thread(T *ptr = Q_NULLPTR) : QThread() , object(ptr) { } void run() Q_DECL_OVERRIDE { std::unique_ptr o(object); exec(); } private: T *object; } *thread = new Thread(object); s_moduleThreads << thread; object->moveToThread(thread); thread->start(); return object; } class Application::Private { public: Private() { } static inline bool isPluginEnabled(const KConfigGroup &config, const KPluginMetaData& plugin) { const auto pluginName = plugin.pluginId(); qCDebug(KAMD_LOG_APPLICATION) << "Plugin Name is " << pluginName << plugin.fileName(); if (pluginName == "org.kde.ActivityManager.ResourceScoring") { // SQLite plugin is necessary for the proper workspace behaviour return true; } else { return config.readEntry(pluginName + "Enabled", plugin.isEnabledByDefault()); } } bool loadPlugin(const KPluginMetaData& plugin); Resources *resources; Activities *activities; Features *features; QStringList pluginIds; QList plugins; static Application *s_instance; }; Application *Application::Private::s_instance = Q_NULLPTR; Application::Application(int &argc, char **argv) : QApplication(argc, argv) { } void Application::init() { if (!KDBusConnectionPool::threadConnection().registerService( KAMD_DBUS_SERVICE)) { QCoreApplication::exit(EXIT_SUCCESS); } // KAMD is a daemon, if it crashes it is not a problem as // long as it restarts properly // TODO: Restart on crash // KCrash::setFlags(KCrash::AutoRestart); d->resources = runInQThread(); d->activities = runInQThread(); d->features = runInQThread(); /* d->config */ new Config(this); // this does not need a separate thread QMetaObject::invokeMethod(this, "loadPlugins", Qt::QueuedConnection); QDBusConnection::sessionBus().registerObject("/ActivityManager", this, QDBusConnection::ExportAllSlots); } bool Application::Private::loadPlugin(const KPluginMetaData& plugin) { if (!plugin.isValid()) { qCWarning(KAMD_LOG_APPLICATION) << "[ FAILED ] plugin offer not valid"; return false; } if (pluginIds.contains(plugin.pluginId())) { qCDebug(KAMD_LOG_APPLICATION) << "[ OK ] already loaded: " << plugin.pluginId(); return true; } KPluginLoader loader(plugin.fileName()); KPluginFactory* factory = loader.factory(); if (!factory) { qCWarning(KAMD_LOG_APPLICATION) << "[ FAILED ] Could not load KPluginFactory for:" << plugin.pluginId() << loader.errorString(); return false; } auto pluginInstance = factory->create(); auto &modules = Module::get(); if (pluginInstance) { - pluginInstance->init(modules); + bool success = pluginInstance->init(modules); - pluginIds << plugin.pluginId(); - plugins << pluginInstance; + if (success) { + pluginIds << plugin.pluginId(); + plugins << pluginInstance; - qCDebug(KAMD_LOG_APPLICATION) << "[ OK ] loaded: " << plugin.pluginId(); - return true; + qCDebug(KAMD_LOG_APPLICATION) << "[ OK ] loaded: " << plugin.pluginId(); + return true; + } else { + qCWarning(KAMD_LOG_APPLICATION) << "[ FAILED ] init: " << plugin.pluginId() << loader.errorString(); + // TODO: Show a notification for a plugin that failed to load + delete pluginInstance; + return false; + } } else { qCWarning(KAMD_LOG_APPLICATION) << "[ FAILED ] loading: " << plugin.pluginId() << loader.errorString(); // TODO: Show a notification for a plugin that failed to load return false; } } void Application::loadPlugins() { using namespace std::placeholders; const auto config = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerdrc")) ->group("Plugins"); const auto offers = KPluginLoader::findPlugins(QStringLiteral(KAMD_PLUGIN_DIR), std::bind(Private::isPluginEnabled, config, _1)); qCDebug(KAMD_LOG_APPLICATION) << "Found" << offers.size() << "enabled plugins:"; for (const auto &offer : offers) { d->loadPlugin(offer); } } bool Application::loadPlugin(const QString &pluginId) { auto offers = KPluginLoader::findPluginsById(QStringLiteral(KAMD_PLUGIN_DIR), pluginId); if (offers.isEmpty()) { qCWarning(KAMD_LOG_APPLICATION) << "[ FAILED ] not found: " << pluginId; return false; } return d->loadPlugin(offers.first()); } Application::~Application() { qDebug() << "Cleaning up..."; // Waiting for the threads to finish for (const auto thread : s_moduleThreads) { thread->quit(); thread->wait(); delete thread; } // Deleting plugin objects for (const auto plugin : d->plugins) { delete plugin; } Private::s_instance = Q_NULLPTR; } int Application::newInstance() { //We don't want to show the mainWindow() return 0; } Activities &Application::activities() const { return *d->activities; } Resources &Application::resources() const { return *d->resources; } // void Application::quit() // { // if (Private::s_instance) { // Private::s_instance->exit(); // delete Private::s_instance; // } // } void Application::quit() { QApplication::quit(); } #include "Version.h" QString Application::serviceVersion() const { return KACTIVITIES_VERSION_STRING; } // Leaving object oriented world :) namespace { template Return callOnRunningService(const QString &method) { static QDBusInterface remote(KAMD_DBUS_SERVICE, "/ActivityManager", "org.kde.ActivityManager.Application"); QDBusReply reply = remote.call(method); return (Return)reply; } QString runningServiceVersion() { return callOnRunningService("serviceVersion"); } bool isServiceRunning() { return QDBusConnection::sessionBus().interface()->isServiceRegistered( KAMD_DBUS_SERVICE); } } int main(int argc, char **argv) { // Disable session management for this process qunsetenv("SESSION_MANAGER"); QGuiApplication::setDesktopSettingsAware(false); Application application(argc, argv); application.setApplicationName(QStringLiteral("ActivityManager")); application.setOrganizationDomain(QStringLiteral("kde.org")); // KAboutData about("kactivitymanagerd", Q_NULLPTR, ki18n("KDE Activity Manager"), "3.0", // ki18n("KDE Activity Management Service"), // KAboutData::License_GPL, // ki18n("(c) 2010, 2011, 2012 Ivan Cukic"), KLocalizedString(), // "http://www.kde.org/"); // KCmdLineArgs::init(argc, argv, &about); const auto arguments = application.arguments(); if (arguments.size() == 0) { QCoreApplication::exit(EXIT_FAILURE); } else if (arguments.size() != 1 && (arguments.size() != 2 || arguments[1] == "--help")) { QTextStream(stdout) << "start\tStarts the service\n" << "stop\tStops the server\n" << "status\tPrints basic server information\n" << "start-daemon\tStarts the service without forking (use with caution)\n" << "--help\tThis help message\n"; QCoreApplication::exit(EXIT_SUCCESS); } else if (arguments.size() == 1 || arguments[1] == "start") { // Checking whether the service is already running if (isServiceRunning()) { QTextStream(stdout) << "Already running\n"; QCoreApplication::exit(EXIT_SUCCESS); } // Creating the watcher, but not on the wall QDBusServiceWatcher watcher(KAMD_DBUS_SERVICE, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration); QObject::connect(&watcher, &QDBusServiceWatcher::serviceRegistered, [] (const QString &service) { QTextStream(stdout) << "Service started, version: " << runningServiceVersion() << "\n"; QCoreApplication::exit(EXIT_SUCCESS); }); // Starting the dameon QProcess::startDetached( KAMD_INSTALL_PREFIX "/bin/kactivitymanagerd", QStringList{"start-daemon"} ); return application.exec(); } else if (arguments[1] == "stop") { if (!isServiceRunning()) { QTextStream(stdout) << "Service not running\n"; QCoreApplication::exit(EXIT_SUCCESS); } callOnRunningService("quit"); QTextStream(stdout) << "Service stopped\n"; return EXIT_SUCCESS; } else if (arguments[1] == "status") { // Checking whether the service is already running if (isServiceRunning()) { QTextStream(stdout) << "The service is running, version: " << runningServiceVersion() << "\n"; } else { QTextStream(stdout) << "The service is not running\n"; } return EXIT_SUCCESS; } else if (arguments[1] == "start-daemon") { // Really starting the activity manager KDBusService service(KDBusService::Unique); application.init(); return application.exec(); } else { QTextStream(stdout) << "Unrecognized command: " << arguments[1] << '\n'; return EXIT_FAILURE; } } diff --git a/src/service/plugins/sqlite/Database.cpp b/src/service/plugins/sqlite/Database.cpp index 114e516..f63bd36 100644 --- a/src/service/plugins/sqlite/Database.cpp +++ b/src/service/plugins/sqlite/Database.cpp @@ -1,114 +1,87 @@ /* * 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 "Database.h" // Qt #include #include #include #include #include #include // KDE #include // Utils #include #include // System #include #include // Local #include "Debug.h" #include "Utils.h" #include #include class ResourcesDatabaseMigrator::Private { public: Common::Database::Ptr database; }; -Common::Database &resourcesDatabase() +Common::Database::Ptr resourcesDatabase() { static ResourcesDatabaseMigrator instance; - return *(instance.d->database); -} - -void ResourcesDatabaseMigrator::migrateDatabase(const QString &newDatabaseFile) const -{ - // Checking whether we need to transfer the KActivities/KDE4 - // sqlite database file to the new location. - if (QFile(newDatabaseFile).exists()) { - return; - } - - // Testing for kdehome - Kdelibs4Migration migration; - if (!migration.kdeHomeFound()) { - return; - } - - QString oldDatabaseFile( - migration.locateLocal("data", "activitymanager/resources/database")); - if (!oldDatabaseFile.isEmpty()) { - QFile(oldDatabaseFile).copy(newDatabaseFile); - } + return instance.d->database; } ResourcesDatabaseMigrator::ResourcesDatabaseMigrator() - : d() { const QString databaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/"); qDebug() << "Creating directory: " << databaseDir; auto created = QDir().mkpath(databaseDir); if (!created || !QDir(databaseDir).exists()) { qWarning() << "Database folder can not be created!"; } - const QString newDatabaseFile = databaseDir + QStringLiteral("database"); - - migrateDatabase(newDatabaseFile); - d->database = Common::Database::instance( Common::Database::ResourcesDatabase, Common::Database::ReadWrite); - Q_ASSERT_X(d->database, "SQLite plugin/Database constructor", - "Database could not be opened"); - - Common::ResourcesDatabaseSchema::initSchema(*d->database); + if (d->database) { + Common::ResourcesDatabaseSchema::initSchema(*d->database); + } } ResourcesDatabaseMigrator::~ResourcesDatabaseMigrator() { } diff --git a/src/service/plugins/sqlite/Database.h b/src/service/plugins/sqlite/Database.h index 7d44c8d..cfa21d2 100644 --- a/src/service/plugins/sqlite/Database.h +++ b/src/service/plugins/sqlite/Database.h @@ -1,64 +1,65 @@ /* * 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. */ #ifndef PLUGINS_SQLITE_RESOURCESDATABASE_H #define PLUGINS_SQLITE_RESOURCESDATABASE_H // Qt #include #include #include #include #include #include // Utils #include // Local #include +#include class QDateTime; class QSqlDatabase; class QSqlError; namespace Common { class Database; } // namespace Common class ResourcesDatabaseMigrator : public QObject { Q_OBJECT public: // static Database *self(); private: ResourcesDatabaseMigrator(); ~ResourcesDatabaseMigrator(); void migrateDatabase(const QString &newDatabaseFile) const; D_PTR; - friend Common::Database &resourcesDatabase(); + friend Common::Database::Ptr resourcesDatabase(); }; -Common::Database &resourcesDatabase(); +Common::Database::Ptr resourcesDatabase(); #endif // PLUGINS_SQLITE_RESOURCESDATABASE_H diff --git a/src/service/plugins/sqlite/ResourceLinking.cpp b/src/service/plugins/sqlite/ResourceLinking.cpp index 6a69b71..6d8fd47 100644 --- a/src/service/plugins/sqlite/ResourceLinking.cpp +++ b/src/service/plugins/sqlite/ResourceLinking.cpp @@ -1,316 +1,316 @@ /* * 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; } if (usedActivity == ":any") { usedActivity = ":global"; } 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, + Utils::prepare(*resourcesDatabase(), linkResourceToActivityQuery, QStringLiteral( "INSERT OR REPLACE INTO ResourceLink" " (usedActivity, initiatingAgent, targettedResource) " "VALUES ( " "COALESCE(:usedActivity,'')," "COALESCE(:initiatingAgent,'')," "COALESCE(:targettedResource,'')" ")" )); - DATABASE_TRANSACTION(resourcesDatabase()); + 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(QUrl(QStringLiteral("activities:/") + usedActivity)); if (usedActivity == StatsPlugin::self()->currentActivity()) { // qDebug() << "Sending link event added: activities:/current"; org::kde::KDirNotify::emitFilesAdded( 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"); QSqlQuery *query = nullptr; if (usedActivity == ":any") { - Utils::prepare(resourcesDatabase(), unlinkResourceFromAllActivitiesQuery, + Utils::prepare(*resourcesDatabase(), unlinkResourceFromAllActivitiesQuery, QStringLiteral( "DELETE FROM ResourceLink " "WHERE " "initiatingAgent = COALESCE(:initiatingAgent , '') AND " "targettedResource = COALESCE(:targettedResource, '') " )); query = unlinkResourceFromAllActivitiesQuery.get(); } else { - Utils::prepare(resourcesDatabase(), unlinkResourceFromActivityQuery, + Utils::prepare(*resourcesDatabase(), unlinkResourceFromActivityQuery, QStringLiteral( "DELETE FROM ResourceLink " "WHERE " "usedActivity = COALESCE(:usedActivity , '') AND " "initiatingAgent = COALESCE(:initiatingAgent , '') AND " "targettedResource = COALESCE(:targettedResource, '') " )); query = unlinkResourceFromActivityQuery.get(); } - DATABASE_TRANSACTION(resourcesDatabase()); + DATABASE_TRANSACTION(*resourcesDatabase()); Utils::exec(Utils::FailOnError, *query, ":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( { QUrl(QStringLiteral("activities:/") + usedActivity + '/' + mangled) }); if (usedActivity == StatsPlugin::self()->currentActivity()) { // qDebug() << "Sending link event removed: activities:/current/" << mangled; org::kde::KDirNotify::emitFilesRemoved({ 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, + 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.isEmpty()) { qDebug() << "Resource is invalid -- empty"; return false; } if (targettedResource.startsWith(QStringLiteral("file://"))) { targettedResource = QUrl(targettedResource).toLocalFile(); } if (targettedResource.startsWith(QStringLiteral("/"))) { QFileInfo file(targettedResource); if (!file.exists()) { qDebug() << "Resource is invalid -- the file does not exist"; 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() && usedActivity != ":global" && usedActivity != ":any" && !StatsPlugin::self()->listActivities().contains(usedActivity)) { qDebug() << "Activity is invalid, it does not exist"; 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(QUrl(QStringLiteral("activities:/"))); } void ResourceLinking::onActivityRemoved(const QString &activity) { // Notify KIO // qDebug() << "Removed: activities:/" << activity; org::kde::KDirNotify::emitFilesRemoved( { 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( { QUrl(QStringLiteral("activities:/current")) }); } diff --git a/src/service/plugins/sqlite/ResourceScoreCache.cpp b/src/service/plugins/sqlite/ResourceScoreCache.cpp index 255c0c7..32dd891 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 "Debug.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()) + : createResourceScoreCacheQuery(resourcesDatabase()->createQuery()) + , getResourceScoreCacheQuery(resourcesDatabase()->createQuery()) + , updateResourceScoreCacheQuery(resourcesDatabase()->createQuery()) + , getScoreAdditionQuery(resourcesDatabase()->createQuery()) { - Utils::prepare(resourcesDatabase(), + Utils::prepare(*resourcesDatabase(), createResourceScoreCacheQuery, QStringLiteral( "INSERT INTO ResourceScoreCache " "VALUES (:usedActivity, :initiatingAgent, :targettedResource, " "0, 0, " // type, score ":firstUpdate, " // lastUpdate ":firstUpdate)" )); - Utils::prepare(resourcesDatabase(), + Utils::prepare(*resourcesDatabase(), getResourceScoreCacheQuery, QStringLiteral( "SELECT cachedScore, lastUpdate, firstUpdate FROM ResourceScoreCache " "WHERE " ":usedActivity = usedActivity AND " ":initiatingAgent = initiatingAgent AND " ":targettedResource = targettedResource " )); - Utils::prepare(resourcesDatabase(), + Utils::prepare(*resourcesDatabase(), updateResourceScoreCacheQuery, QStringLiteral( "UPDATE ResourceScoreCache SET " "cachedScore = :cachedScore, " "lastUpdate = :lastUpdate " "WHERE " ":usedActivity = usedActivity AND " ":initiatingAgent = initiatingAgent AND " ":targettedResource = targettedResource " )); - Utils::prepare(resourcesDatabase(), + 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(QDateTime fromTime, 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 shoud not be empty"); Q_ASSERT_X(!d->activity.isEmpty(), "ResourceScoreCache::constructor", "Activity shoud not be empty"); Q_ASSERT_X(!d->resource.isEmpty(), "ResourceScoreCache::constructor", "Resource shoud not be empty"); } ResourceScoreCache::~ResourceScoreCache() { } void ResourceScoreCache::update() { QDateTime lastUpdate; QDateTime firstUpdate; QDateTime currentTime = QDateTime::currentDateTime(); qreal score = 0; - DATABASE_TRANSACTION(resourcesDatabase()); + DATABASE_TRANSACTION(*resourcesDatabase()); qDebug() << "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() ); // 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()); qDebug() << "Already in database? " << (!isCacheNew); qDebug() << " First update : " << firstUpdate; qDebug() << " 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 qDebug() << "After the adjustment"; qDebug() << " Current score : " << score; qDebug() << " First update : " << firstUpdate; qDebug() << " Last update : " << lastUpdate; Utils::exec(Utils::FailOnError, Queries::self().getScoreAdditionQuery, ":usedActivity", d->activity, ":initiatingAgent", d->application, ":targettedResource", d->resource, ":start", lastUpdate.toTime_t() ); uint lastEventStart = currentTime.toTime_t(); for (const auto &result: Queries::self().getScoreAdditionQuery) { lastEventStart = result["start"].toUInt(); const auto end = result["end"].toUInt(); const auto intervalLength = end - lastEventStart; qDebug() << "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 } else { score += d->timeFactor(QDateTime::fromTime_t(end), currentTime) * intervalLength / 60.0; } } qDebug() << " 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 qDebug() << "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()) ); } diff --git a/src/service/plugins/sqlite/StatsPlugin.cpp b/src/service/plugins/sqlite/StatsPlugin.cpp index 0e0ff31..7967110 100644 --- a/src/service/plugins/sqlite/StatsPlugin.cpp +++ b/src/service/plugins/sqlite/StatsPlugin.cpp @@ -1,723 +1,724 @@ /* * 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); + if (!resourcesDatabase()) { + return false; + } + 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( + 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( + 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) { 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( + 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( + 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()); + DATABASE_TRANSACTION(*resourcesDatabase()); - Utils::prepare(resourcesDatabase(), saveResourceTitleQuery, QStringLiteral( + 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()); + DATABASE_TRANSACTION(*resourcesDatabase()); - Utils::prepare(resourcesDatabase(), saveResourceMimetypeQuery, QStringLiteral( + 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()); + 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()); + 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(); + auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity)" ); - auto removeScoreCachesQuery = resourcesDatabase().createQuery(); + 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(); + auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " "AND end > :since" ); - auto removeScoreCachesQuery = resourcesDatabase().createQuery(); + 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()); + DATABASE_TRANSACTION(*resourcesDatabase()); const auto time = QDateTime::currentDateTime().addMonths(-months); const auto usedActivity = activity.isEmpty() ? QVariant() : QVariant(activity); - auto removeEventsQuery = resourcesDatabase().createQuery(); + auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE usedActivity = COALESCE(:usedActivity, usedActivity) " "AND start < :time" ); - auto removeScoreCachesQuery = resourcesDatabase().createQuery(); + 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()); + 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(); + auto removeEventsQuery = resourcesDatabase()->createQuery(); removeEventsQuery.prepare( "DELETE FROM ResourceEvent " "WHERE " + activityFilter + " AND " + clientFilter + " AND " + "targettedResource LIKE :targettedResource ESCAPE '\\'" ); - auto removeScoreCachesQuery = resourcesDatabase().createQuery(); + 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"