diff --git a/CMakeLists.txt b/CMakeLists.txt index 64d7812..ba3807b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,75 +1,76 @@ # 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) +include (ECMQtDeclareLoggingCategory) # Qt set (CMAKE_AUTOMOC ON) find_package (Qt5 ${REQUIRED_QT_VERSION} CONFIG REQUIRED COMPONENTS Core DBus Widgets) # 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/CMakeLists.txt b/src/CMakeLists.txt index 42fecdf..a19ca47 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,74 +1,90 @@ # vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: # Boosting us a bit find_package (Boost 1.49 REQUIRED) include_directories (${Boost_INCLUDE_DIR}) string (REGEX MATCH "1053.." BOOST_VERSION_BLACKLISTED ${Boost_VERSION}) if (BOOST_VERSION_BLACKLISTED AND NOT KACTIVITIES_ENABLE_EXCEPTIONS) message ( WARNING "Boost.Container 1.53 has issues when exceptions are disabled. " "We will set the KACTIVITIES_ENABLE_EXCEPTIONS option." ) set (KACTIVITIES_ENABLE_EXCEPTIONS ON) endif () if (KACTIVITIES_ENABLE_EXCEPTIONS) string (REPLACE "-fno-exceptions" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") add_definitions (-fexceptions) endif () # Testing for C++0x/C++11 features include (CheckCxxFeatures) cxx_check_feature ("c++11" "auto" "N2546" HAVE_CXX11_AUTO "${ADDITIONAL_DEFINITIONS}") cxx_check_feature ("c++11" "nullptr" "N2431" HAVE_CXX11_NULLPTR "${ADDITIONAL_DEFINITIONS}") cxx_check_feature ("c++11" "lambda" "N2927" HAVE_CXX11_LAMBDA "${ADDITIONAL_DEFINITIONS}") cxx_check_feature ("c++11" "override" "N3206" HAVE_CXX11_OVERRIDE "${ADDITIONAL_DEFINITIONS}") cxx_check_feature ("c++11" "unique_ptr" "none" HAVE_CXX11_UNIQUE_PTR "${ADDITIONAL_DEFINITIONS}") cxx_check_feature ("c++11" "variadic-templates" "N2242" HAVE_CXX11_VARIADIC_TEMPLATES "${ADDITIONAL_DEFINITIONS}") cxx_check_feature ("c++11" "initializer-lists" "N2672" HAVE_CXX11_INITIALIZER_LISTS "${ADDITIONAL_DEFINITIONS}") # ======================================================= # Starting the actual project definition # Config file set (KAMD_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") set (KAMD_DATA_DIR "${KDE_INSTALL_DATADIR_KF5}/kactivitymanagerd/") set (KAMD_PLUGIN_VERSION 1) set (KAMD_PLUGIN_DIR "kactivitymanagerd/${KAMD_PLUGIN_VERSION}") set (KAMD_FULL_PLUGIN_DIR "${CMAKE_INSTALL_FULL_PLUGINDIR}/${KAMD_PLUGIN_DIR}/") configure_file (kactivities-features.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kactivities-features.h) include_directories ( ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR} ) # Is the compiler modern enough to build the ActivityManager service # and accompanying workspace addons? string (COMPARE EQUAL "${CXX_FEATURES_UNSUPPORTED}" "" CXX_COMPILER_IS_MODERN) # The compiler is good enough if (CXX_COMPILER_IS_MODERN) message (STATUS "C++11 enabled compiler: Your compiler is state-of-the-art" ) else () message (STATUS "C++11 enabled compiler:" "Your compiler doesn't support the following features: ${CXX_FEATURES_UNSUPPORTED} but the list of the supported ones is sufficient for the build: ${CXX_FEATURES_SUPPORTED}" ) endif () configure_file(org.kde.activitymanager.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.activitymanager.service) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.activitymanager.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR}) +ecm_qt_declare_logging_category(debug_SRCS + HEADER DebugActivities.h + IDENTIFIER KAMD_LOG_ACTIVITIES + CATEGORY_NAME org.kde.kactivities.activities + DEFAULT_SEVERITY Warning) +ecm_qt_declare_logging_category(debug_SRCS + HEADER DebugResources.h + IDENTIFIER KAMD_LOG_RESOURCES + CATEGORY_NAME org.kde.kactivities.resources + DEFAULT_SEVERITY Warning) +ecm_qt_declare_logging_category(debug_SRCS + HEADER DebugApplication.h + IDENTIFIER KAMD_LOG_APPLICATION + CATEGORY_NAME org.kde.kactivities.application + DEFAULT_SEVERITY Warning) + add_subdirectory (service) diff --git a/src/common/database/Database.cpp b/src/common/database/Database.cpp index 5eacb98..7e929c9 100644 --- a/src/common/database/Database.cpp +++ b/src/common/database/Database.cpp @@ -1,294 +1,294 @@ /* * 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 -#include "Debug.h" +#include "DebugResources.h" 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) { qCWarning(KAMD_LOG_RESOURCES) << "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() { qCDebug(KAMD_LOG_RESOURCES) << "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 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") { qCWarning(KAMD_LOG_RESOURCES) << "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")); qCDebug(KAMD_LOG_RESOURCES) << "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()) { qCWarning(KAMD_LOG_RESOURCES) << "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/Activities.cpp b/src/service/Activities.cpp index e2504ec..b1a7af5 100644 --- a/src/service/Activities.cpp +++ b/src/service/Activities.cpp @@ -1,599 +1,599 @@ /* * 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 "Activities.h" #include "Activities_p.h" // Qt #include #include #include #include #include #include // KDE #include #include #include #include #include // Utils #include #include // Local -#include "Debug.h" +#include "DebugActivities.h" #include "activitiesadaptor.h" #include "ksmserver/KSMServer.h" #include "common/dbus/common.h" // Private #define ACTIVITY_MANAGER_CONFIG_FILE_NAME QStringLiteral("kactivitymanagerdrc") Activities::Private::KDE4ConfigurationTransitionChecker::KDE4ConfigurationTransitionChecker() { // Checking whether we need to transfer the KActivities/KDE4 // configuration file to the new location. const QString newConfigLocation = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation) + '/' + ACTIVITY_MANAGER_CONFIG_FILE_NAME; if (QFile(newConfigLocation).exists()) { return; } // Testing for kdehome Kdelibs4Migration migration; if (!migration.kdeHomeFound()) { return; } QString oldConfigFile(migration.locateLocal("config", "activitymanagerrc")); if (!oldConfigFile.isEmpty()) { QFile(oldConfigFile).copy(newConfigLocation); } } Activities::Private::Private(Activities *parent) : kde4ConfigurationTransitionChecker() , config(QStringLiteral("kactivitymanagerdrc")) , q(parent) { // qCDebug(KAMD_ACTIVITIES) << "Using this configuration file:" // << config.name() // << config.locationType() // << QStandardPaths::standardLocations(config.locationType()) // ; // Reading activities from the config file. // Saving only the running activities means that if we have any // errors in the config, we might end up with all activities // stopped const auto defaultState = !mainConfig().hasKey("runningActivities") ? Activities::Running : !mainConfig().hasKey("stoppedActivities") ? Activities::Stopped : Activities::Running; const auto runningActivities = mainConfig().readEntry("runningActivities", QStringList()).toSet(); const auto stoppedActivities = mainConfig().readEntry("stoppedActivities", QStringList()).toSet(); // Do we have a running activity? bool atLeastOneRunning = false; for (const auto &activity: activityNameConfig().keyList()) { auto state = runningActivities.contains(activity) ? Activities::Running : stoppedActivities.contains(activity) ? Activities::Stopped : defaultState; activities[activity] = state; if (state == Activities::Running) { atLeastOneRunning = true; } } // Is this our first start? if (activities.isEmpty()) { // We need to add this only after the service has been properly started KConfigGroup cg(KSharedConfig::openConfig("kdeglobals"), "Activities"); //NOTE: config key still singular for retrocompatibility const QStringList names = cg.readEntry("defaultActivityName", QStringList{i18n("Default")}); for (const auto &name : names) { QMetaObject::invokeMethod( q, "AddActivity", Qt::QueuedConnection, Q_ARG(QString, name)); } } else if (!atLeastOneRunning) { // If we have no running activities, but we have activities, // we are in a problem. This should not happen unless the // configuration file is in a big problem and told us there // are no running activities, and enlists all of them as stopped. // In that case, we will pretend all of them are running qCWarning(KAMD_LOG_ACTIVITIES) << "The config file enlisted all activities as stopped"; for (const auto &keys: activities.keys()) { activities[keys] = Activities::Running; } } } void Activities::Private::loadLastActivity() { // This is called from constructor, no need for locking // If there are no public activities, try to load the last used activity const auto lastUsedActivity = mainConfig().readEntry("currentActivity", QString()); setCurrentActivity( (lastUsedActivity.isEmpty() && activities.size() > 0) ? activities.keys().at(0) : lastUsedActivity); } Activities::Private::~Private() { configSync(); } bool Activities::Private::setCurrentActivity(const QString &activity) { { // There is nothing expensive in this block, not a problem to lock QWriteLocker lock(&activitiesLock); // Should we change the activity at all? if (currentActivity == activity) { return true; } // If the activity is empty, this means we are entering a limbo state if (activity.isEmpty()) { currentActivity.clear(); emit q->CurrentActivityChanged(currentActivity); return true; } // Does the requested activity exist? if (!activities.contains(activity)) { return false; } } // Start activity q->StartActivity(activity); // Saving the current activity, and notifying // clients of the change currentActivity = activity; mainConfig().writeEntry("currentActivity", activity); scheduleConfigSync(); emit q->CurrentActivityChanged(activity); return true; } QString Activities::Private::addActivity(const QString &name) { QString activity; if (name.isEmpty()) { Q_ASSERT(!name.isEmpty()); return activity; } int activitiesCount = 0; { QWriteLocker lock(&activitiesLock); // Ensuring a new Uuid. The loop should usually end after only // one iteration while (activity.isEmpty() || activities.contains(activity)) { activity = QUuid::createUuid().toString().mid(1, 36); } // Saves the activity info to the config activities[activity] = Invalid; activitiesCount = activities.size(); } setActivityState(activity, Running); q->SetActivityName(activity, name); emit q->ActivityAdded(activity); scheduleConfigSync(); if (activitiesCount == 1) { q->SetCurrentActivity(activity); } return activity; } void Activities::Private::removeActivity(const QString &activity) { Q_ASSERT(!activity.isEmpty()); // Sanity checks { QWriteLocker lock(&activitiesLock); if (!activities.contains(activity)) { return; } // Is somebody trying to remove the last activity? if (activities.size() == 1) { return; } } // If the activity is running, stash it q->StopActivity(activity); setActivityState(activity, Activities::Invalid); bool currentActivityDeleted = false; { QWriteLocker lock(&activitiesLock); // Removing the activity activities.remove(activity); // If the removed activity was the current one, // set another activity as current currentActivityDeleted = (currentActivity == activity); } activityNameConfig().deleteEntry(activity); activityDescriptionConfig().deleteEntry(activity); activityIconConfig().deleteEntry(activity); if (currentActivityDeleted) { ensureCurrentActivityIsRunning(); } emit q->ActivityRemoved(activity); QMetaObject::invokeMethod(q, "ActivityRemoved", Qt::QueuedConnection, Q_ARG(QString, activity)); QMetaObject::invokeMethod(this, "configSync", Qt::QueuedConnection); } void Activities::Private::scheduleConfigSync() { static const auto shortInterval = 1000; // If the timer is not running, or has a longer interval than we need, // start it // Note: If you want to add multiple different delays for different // events based on the importance of how quickly something needs // to be synced to the config, don't. Since the QTimer lives in a // separate thread, we have to communicate with it in via // queued connections, which means that we don't know whether // the timer was already started when this method was invoked, // we do not know whether the interval is properly set etc. if (!configSyncTimer.isActive()) { QMetaObject::invokeMethod( &configSyncTimer, "start", Qt::QueuedConnection, Q_ARG(int, shortInterval)); } } void Activities::Private::configSync() { // Stop the timer and reset the interval to zero QMetaObject::invokeMethod(&configSyncTimer, "stop", Qt::QueuedConnection); config.sync(); } void Activities::Private::setActivityState(const QString &activity, Activities::State state) { bool configNeedsUpdating = false; { QWriteLocker lock(&activitiesLock); Q_ASSERT(activities.contains(activity)); if (activities.value(activity) == state) { return; } // Treating 'Starting' as 'Running', and 'Stopping' as 'Stopped' // as far as the config file is concerned configNeedsUpdating = ((activities[activity] & 4) != (state & 4)); activities[activity] = state; } switch (state) { case Activities::Running: emit q->ActivityStarted(activity); break; case Activities::Stopped: emit q->ActivityStopped(activity); break; default: break; } emit q->ActivityStateChanged(activity, state); if (configNeedsUpdating) { QReadLocker lock(&activitiesLock); mainConfig().writeEntry("runningActivities", activities.keys(Activities::Running) + activities.keys(Activities::Starting)); mainConfig().writeEntry("stoppedActivities", activities.keys(Activities::Stopped) + activities.keys(Activities::Stopping)); scheduleConfigSync(); } } void Activities::Private::ensureCurrentActivityIsRunning() { // If the current activity is not running, // make some other activity current const auto runningActivities = q->ListActivities(Activities::Running); if (!runningActivities.contains(currentActivity) && runningActivities.size() > 0) { setCurrentActivity(runningActivities.first()); } } void Activities::Private::activitySessionStateChanged(const QString &activity, int status) { QString currentActivity = this->currentActivity; { QReadLocker lock(&activitiesLock); if (!activities.contains(activity)) { return; } } switch (status) { case KSMServer::Started: case KSMServer::FailedToStop: setActivityState(activity, Activities::Running); break; case KSMServer::Stopped: setActivityState(activity, Activities::Stopped); if (currentActivity == activity) { ensureCurrentActivityIsRunning(); } break; } QMetaObject::invokeMethod(this, "configSync", Qt::QueuedConnection); } // Main Activities::Activities(QObject *parent) : Module(QStringLiteral("activities"), parent) , d(this) { qCDebug(KAMD_LOG_ACTIVITIES) << "Starting the KDE Activity Manager daemon" << QDateTime::currentDateTime(); // Basic initialization //////////////////////////////////////////////////// // Initializing D-Bus service new ActivitiesAdaptor(this); KDBusConnectionPool::threadConnection().registerObject( KAMD_DBUS_OBJECT_PATH(Activities), this); // Initializing config qCDebug(KAMD_LOG_ACTIVITIES) << "Config timer connecting..."; d->connect(&d->configSyncTimer, SIGNAL(timeout()), SLOT(configSync()), Qt::QueuedConnection); d->configSyncTimer.setSingleShot(true); d->ksmserver = new KSMServer(this); d->connect(d->ksmserver, SIGNAL(activitySessionStateChanged(QString, int)), SLOT(activitySessionStateChanged(QString, int))); // Loading the last used activity, if possible d->loadLastActivity(); } Activities::~Activities() { } QString Activities::CurrentActivity() const { QReadLocker lock(&d->activitiesLock); return d->currentActivity; } bool Activities::SetCurrentActivity(const QString &activity) { // Public method can not put us in a limbo state if (activity.isEmpty()) { return false; } return d->setCurrentActivity(activity); } QString Activities::AddActivity(const QString &name) { // We do not care about authorization if this is the first start if (!d->activities.isEmpty() && !KAuthorized::authorize("plasma-desktop/add_activities")) { return QString(); } return d->addActivity(name); } void Activities::RemoveActivity(const QString &activity) { if (!KAuthorized::authorize("plasma-desktop/add_activities")) { return; } d->removeActivity(activity); } QStringList Activities::ListActivities() const { QReadLocker lock(&d->activitiesLock); return d->activities.keys(); } QStringList Activities::ListActivities(int state) const { QReadLocker lock(&d->activitiesLock); return d->activities.keys((State)state); } QList Activities::ListActivitiesWithInformation() const { using namespace kamd::utils; // Mapping activity ids to info return as_collection>( ListActivities() | transformed(&Activities::ActivityInformation, this) ); } ActivityInfo Activities::ActivityInformation(const QString &activity) const { return ActivityInfo { activity, ActivityName(activity), ActivityDescription(activity), ActivityIcon(activity), ActivityState(activity) }; } #define CREATE_GETTER_AND_SETTER(What) \ QString Activities::Activity##What(const QString &activity) const \ { \ QReadLocker lock(&d->activitiesLock); \ return d->activities.contains(activity) ? d->activity##What(activity) \ : QString(); \ } \ \ void Activities::SetActivity##What(const QString &activity, \ const QString &value) \ { \ { \ QReadLocker lock(&d->activitiesLock); \ if (value == d->activity##What(activity) \ || !d->activities.contains(activity)) { \ return; \ } \ } \ \ d->activity##What##Config().writeEntry(activity, value); \ d->scheduleConfigSync(); \ \ emit Activity##What##Changed(activity, value); \ emit ActivityChanged(activity); \ } CREATE_GETTER_AND_SETTER(Name) CREATE_GETTER_AND_SETTER(Description) CREATE_GETTER_AND_SETTER(Icon) #undef CREATE_GETTE_AND_SETTERR // Main void Activities::StartActivity(const QString &activity) { { QReadLocker lock(&d->activitiesLock); if (!d->activities.contains(activity) || d->activities[activity] != Stopped) { return; } } d->setActivityState(activity, Starting); d->ksmserver->startActivitySession(activity); } void Activities::StopActivity(const QString &activity) { { QReadLocker lock(&d->activitiesLock); if (!d->activities.contains(activity) || d->activities[activity] == Stopped || d->activities.size() == 1 || d->activities.keys(Activities::Running).size() <= 1 ) { return; } } d->setActivityState(activity, Stopping); d->ksmserver->stopActivitySession(activity); } int Activities::ActivityState(const QString &activity) const { QReadLocker lock(&d->activitiesLock); return d->activities.contains(activity) ? d->activities[activity] : Invalid; } diff --git a/src/service/Application.cpp b/src/service/Application.cpp index 8c332cb..d11ae28 100644 --- a/src/service/Application.cpp +++ b/src/service/Application.cpp @@ -1,425 +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 "DebugApplication.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) { bool success = pluginInstance->init(modules); if (success) { pluginIds << plugin.pluginId(); plugins << pluginInstance; 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() { qCDebug(KAMD_LOG_APPLICATION) << "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/CMakeLists.txt b/src/service/CMakeLists.txt index 847327a..09d1b4a 100644 --- a/src/service/CMakeLists.txt +++ b/src/service/CMakeLists.txt @@ -1,90 +1,90 @@ # vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: project (ActivityManager) # General find_package (ECM 0.0.8 REQUIRED NO_MODULE) set (CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH}) find_package (Qt5 REQUIRED NO_MODULE COMPONENTS Sql Gui Widgets) find_package (KF5Config ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5CoreAddons ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5I18n ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5WindowSystem ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5GlobalAccel ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5XmlGui ${KF5_DEP_VERSION} CONFIG REQUIRED) find_package (KF5KIO ${KF5_DEP_VERSION} CONFIG REQUIRED) # Standard stuff set (CMAKE_INCLUDE_CURRENT_DIR ON) -add_library(kactivitymanagerd_plugin SHARED Plugin.cpp Module.cpp Event.cpp Debug.cpp) +add_library(kactivitymanagerd_plugin SHARED Plugin.cpp Module.cpp Event.cpp ${debug_SRCS}) generate_export_header(kactivitymanagerd_plugin) target_link_libraries(kactivitymanagerd_plugin PUBLIC Qt5::Core Qt5::DBus KF5::CoreAddons KF5::ConfigCore) add_subdirectory (plugins) set (kactivitymanager_SRCS Application.cpp ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.Activities.cpp - Debug.cpp + ${debug_SRCS} Activities.cpp Resources.cpp Features.cpp Config.cpp ksmserver/KSMServer.cpp ) qt5_add_dbus_adaptor ( kactivitymanager_SRCS ../common/dbus/org.kde.ActivityManager.Activities.xml Activities.h Activities ) qt5_add_dbus_adaptor ( kactivitymanager_SRCS ../common/dbus/org.kde.ActivityManager.Resources.xml Resources.h Resources ) qt5_add_dbus_adaptor ( kactivitymanager_SRCS ../common/dbus/org.kde.ActivityManager.Features.xml Features.h Features ) add_executable (kactivitymanagerd ${kactivitymanager_SRCS}) target_link_libraries (kactivitymanagerd Qt5::Core Qt5::DBus Qt5::Gui Qt5::Widgets KF5::DBusAddons KF5::CoreAddons KF5::ConfigCore KF5::I18n KF5::WindowSystem kactivitymanagerd_plugin ) ########### install application ############### install (FILES files/kactivitymanagerd.desktop DESTINATION ${KDE_INSTALL_KSERVICES5DIR} ) install (TARGETS kactivitymanagerd kactivitymanagerd_plugin ${KF5_INSTALL_TARGETS_DEFAULT_ARGS} ) install (FILES files/kactivitymanagerd-plugin.desktop DESTINATION ${KDE_INSTALL_KSERVICETYPES5DIR} ) diff --git a/src/service/Debug.cpp b/src/service/Debug.cpp deleted file mode 100644 index da91d77..0000000 --- a/src/service/Debug.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2013 - 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 "Debug.h" - -#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) -// logging category for this framework, default: log stuff >= warning -Q_LOGGING_CATEGORY(KAMD_LOG_ACTIVITIES, "org.kde.kactivities.activities", QtWarningMsg) -Q_LOGGING_CATEGORY(KAMD_LOG_RESOURCES, "org.kde.kactivities.resources", QtWarningMsg) -Q_LOGGING_CATEGORY(KAMD_LOG_APPLICATION, "org.kde.kactivities.application", QtWarningMsg) -#else -Q_LOGGING_CATEGORY(KAMD_LOG_ACTIVITIES, "org.kde.kactivities.activities") -Q_LOGGING_CATEGORY(KAMD_LOG_RESOURCES, "org.kde.kactivities.resources") -Q_LOGGING_CATEGORY(KAMD_LOG_APPLICATION, "org.kde.kactivities.application") -#endif diff --git a/src/service/Debug.h b/src/service/Debug.h deleted file mode 100644 index c62eaed..0000000 --- a/src/service/Debug.h +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2013 - 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 . - */ - -#ifndef ACTIVITIES_DEBUG_P_H -#define ACTIVITIES_DEBUG_P_H - -#include - -Q_DECLARE_LOGGING_CATEGORY(KAMD_LOG_ACTIVITIES) -Q_DECLARE_LOGGING_CATEGORY(KAMD_LOG_RESOURCES) -Q_DECLARE_LOGGING_CATEGORY(KAMD_LOG_APPLICATION) - -#endif /* ACTIVITIES_DEBUG_P_H */ - diff --git a/src/service/Event.cpp b/src/service/Event.cpp index c54b4f0..96695c3 100644 --- a/src/service/Event.cpp +++ b/src/service/Event.cpp @@ -1,86 +1,86 @@ /* * Copyright (C) 2011 - 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 "Event.h" // Local -#include "Debug.h" +#include Event::Event() : wid(0) , type(Accessed) , timestamp(QDateTime::currentDateTime()) { } Event::Event(const QString &vApplication, quintptr vWid, const QString &vUri, int vType) : application(vApplication) , wid(vWid) , uri(vUri) , type(vType) , timestamp(QDateTime::currentDateTime()) { Q_ASSERT(!vApplication.isEmpty()); Q_ASSERT(!vUri.isEmpty()); } Event Event::deriveWithType(Type type) const { Event result(*this); result.type = type; return result; } bool Event::operator==(const Event &other) const { return application == other.application && wid == other.wid && uri == other.uri && type == other.type && timestamp == other.timestamp; } QString Event::typeName() const { switch (type) { case Accessed: return QStringLiteral("Accessed"); case Opened: return QStringLiteral("Opened"); case Modified: return QStringLiteral("Modified"); case Closed: return QStringLiteral("Closed"); case FocussedIn: return QStringLiteral("FocussedIn"); case FocussedOut: return QStringLiteral("FocussedOut"); default: return QStringLiteral("Other"); } } QDebug operator<<(QDebug dbg, const Event &e) { #ifndef QT_NO_DEBUG_OUTPUT dbg << "Event(" << e.application << e.wid << e.typeName() << e.uri << ":" << e.timestamp << ")"; #else Q_UNUSED(e); #endif return dbg.space(); } diff --git a/src/service/Module.cpp b/src/service/Module.cpp index ba3a801..18409a3 100644 --- a/src/service/Module.cpp +++ b/src/service/Module.cpp @@ -1,110 +1,110 @@ /* * Copyright (C) 2012 - 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 "Module.h" // Qt #include #include #include // Utils #include // Local -#include "Debug.h" +#include "DebugApplication.h" class Module::Private { public: static QHash s_modules; }; QHash Module::Private::s_modules; Module::Module(const QString &name, QObject *parent) : QObject(parent) , d() { if (!name.isEmpty()) { Private::s_modules[name] = this; } } Module::~Module() { } QObject *Module::get(const QString &name) { Q_ASSERT(!name.isEmpty()); if (Private::s_modules.contains(name)) { qCDebug(KAMD_LOG_APPLICATION) << "Returning a valid module object for:" << name; return Private::s_modules[name]; } qCWarning(KAMD_LOG_APPLICATION) << "The requested module doesn't exist:" << name; return Q_NULLPTR; } QHash &Module::get() { return Private::s_modules; } bool Module::isFeatureEnabled(const QStringList &feature) const { Q_UNUSED(feature); return false; } bool Module::isFeatureOperational(const QStringList &feature) const { Q_UNUSED(feature); return false; } void Module::setFeatureEnabled(const QStringList &feature, bool value) { Q_UNUSED(feature); Q_UNUSED(value); } QStringList Module::listFeatures(const QStringList &feature) const { Q_UNUSED(feature); return QStringList(); } QDBusVariant Module::featureValue(const QStringList &property) const { Q_UNUSED(property); return QDBusVariant(); } void Module::setFeatureValue(const QStringList &property, const QDBusVariant &value) { Q_UNUSED(property); Q_UNUSED(value); } diff --git a/src/service/Plugin.cpp b/src/service/Plugin.cpp index 323cc77..269bd31 100644 --- a/src/service/Plugin.cpp +++ b/src/service/Plugin.cpp @@ -1,91 +1,91 @@ /* * Copyright (C) 2011 - 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 "Plugin.h" // KDE #include // Utils #include // Local -#include "Debug.h" +#include "DebugApplication.h" class Plugin::Private { public: Private() : config(Q_NULLPTR) { } QString name; KSharedConfig::Ptr config; }; Plugin::Plugin(QObject *parent) : Module(QString(), parent) , d() { } Plugin::~Plugin() { } KConfigGroup Plugin::config() const { if (d->name.isEmpty()) { qCWarning(KAMD_LOG_APPLICATION) << "The plugin needs a name in order to have a config section"; return KConfigGroup(); } if (!d->config) { d->config = KSharedConfig::openConfig(QStringLiteral("kactivitymanagerd-pluginsrc")); } return d->config->group(QStringLiteral("Plugin-") + d->name); } void Plugin::setName(const QString &name) { Q_ASSERT_X(d->name.isEmpty(), "Plugin::setName", "The name can not be set twice"); Q_ASSERT_X(!name.isEmpty(), "Plugin::setName", "The name can not be empty"); qCDebug(KAMD_LOG_APPLICATION) << "Setting the name of " << (void*)this << " to " << name; d->name = name; } QString Plugin::name() const { return d->name; } bool Plugin::init(QHash &modules) { if (!name().isEmpty()) { modules[name()] = this; } return true; } diff --git a/src/service/Resources.cpp b/src/service/Resources.cpp index ff79cc8..4b61e5d 100644 --- a/src/service/Resources.cpp +++ b/src/service/Resources.cpp @@ -1,320 +1,319 @@ /* * 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 "Resources.h" #include "Resources_p.h" // Qt #include #include #include #include // KDE #include #include // Utils #include #include // System #include // Local -#include "Debug.h" #include "Application.h" #include "Activities.h" #include "resourcesadaptor.h" #include "common/dbus/common.h" Resources::Private::Private(Resources *parent) : QThread(parent) , focussedWindow(0) , q(parent) { } Resources::Private::~Private() { requestInterruption(); wait(1500); // Enough time for the sleep(1) + processing in run() } namespace { EventList events; QMutex events_mutex; } void Resources::Private::run() { while (!isInterruptionRequested()) { // initial delay before processing the events sleep(1); EventList currentEvents; { QMutexLocker locker(&events_mutex); if (events.count() == 0) { return; } std::swap(currentEvents, events); } emit q->ProcessedResourceEvents(currentEvents); } } void Resources::Private::insertEvent(const Event &newEvent) { if (lastEvent == newEvent) { return; } lastEvent = newEvent; { QMutexLocker locker(&events_mutex); events << newEvent; } emit q->RegisteredResourceEvent(newEvent); } void Resources::Private::addEvent(const QString &application, WId wid, const QString &uri, int type) { Event newEvent(application, wid, uri, type); addEvent(newEvent); } void Resources::Private::addEvent(const Event &newEvent) { // And now, for something completely delayed { QMutexLocker locker(&events_mutex); // Deleting previously registered Accessed events if // the current one has the same application and uri if (newEvent.type != Event::Accessed) { kamd::utils::remove_if(events, [&newEvent](const Event &event)->bool { return event.application == newEvent.application && event.uri == newEvent.uri ; }); } } // Process the windowing // Essentially, this is the brain of SLC. We need to track the // window focus changes to be able to generate the potential // missing events like FocussedOut before Closed and similar. // So, there is no point in having the same logic in SLC plugin // as well. if (newEvent.wid != 0) { WindowData &window = windows[newEvent.wid]; const QString &uri = newEvent.uri; window.application = newEvent.application; switch (newEvent.type) { case Event::Opened: insertEvent(newEvent); if (window.focussedResource.isEmpty()) { // This window haven't had anything focused, // assuming the new document is focused window.focussedResource = newEvent.uri; insertEvent(newEvent.deriveWithType(Event::FocussedIn)); } break; case Event::FocussedIn: if (!window.resources.contains(uri)) { // This window did not contain this resource before, // sending Opened event insertEvent(newEvent.deriveWithType(Event::Opened)); } window.focussedResource = newEvent.uri; insertEvent(newEvent); break; case Event::Closed: if (window.focussedResource == uri) { // If we are closing a document that is in focus, // release focus first insertEvent(newEvent.deriveWithType(Event::FocussedOut)); window.focussedResource.clear(); } insertEvent(newEvent); break; case Event::FocussedOut: if (window.focussedResource == uri) { window.focussedResource.clear(); } insertEvent(newEvent); break; default: insertEvent(newEvent); break; } } else { // If we haven't got a window, just pass the event on, // but only if it is not a focus event if (newEvent.type != Event::FocussedIn && newEvent.type != Event::FocussedOut) { insertEvent(newEvent); } } start(); } void Resources::Private::windowClosed(WId windowId) { // Testing whether the window is a registered one if (!windows.contains(windowId)) { return; } if (focussedWindow == windowId) { focussedWindow = 0; } // Closing all the resources that the window registered for (const QString &uri: windows[windowId].resources) { q->RegisterResourceEvent(windows[windowId].application, windowId, uri, Event::Closed); } windows.remove(windowId); } void Resources::Private::activeWindowChanged(WId windowId) { // If the focused window has changed, we need to create a // FocussedOut event for the resource it contains, // and FocussedIn for the resource of the new active window. // The windows can do this manually, but if they are // SDI, we can do it on our own. if (windowId == focussedWindow) { return; } if (windows.contains(focussedWindow)) { const WindowData &data = windows[focussedWindow]; if (!data.focussedResource.isEmpty()) { insertEvent(Event(data.application, focussedWindow, data.focussedResource, Event::FocussedOut)); } } focussedWindow = windowId; if (windows.contains(focussedWindow)) { const WindowData &data = windows[focussedWindow]; if (!data.focussedResource.isEmpty()) { insertEvent(Event(data.application, windowId, data.focussedResource, Event::FocussedIn)); } } } Resources::Resources(QObject *parent) : Module(QStringLiteral("resources"), parent) , d(this) { qRegisterMetaType("Event"); qRegisterMetaType("EventList"); qRegisterMetaType("WId"); new ResourcesAdaptor(this); KDBusConnectionPool::threadConnection().registerObject( KAMD_DBUS_OBJECT_PATH(Resources), this); connect(KWindowSystem::self(), &KWindowSystem::windowRemoved, d.operator->(), &Resources::Private::windowClosed); connect(KWindowSystem::self(), &KWindowSystem::activeWindowChanged, d.operator->(), &Resources::Private::activeWindowChanged); } Resources::~Resources() { } void Resources::RegisterResourceEvent(QString application, uint _windowId, const QString &uri, uint event) { if (event > Event::LastEventType || uri.isEmpty() || application.isEmpty()) { return; } WId windowId = (WId)_windowId; d->addEvent(application, windowId, uri, (Event::Type)event); } void Resources::RegisterResourceMimetype(const QString &uri, const QString &mimetype) { if (!mimetype.isEmpty()) { return; } emit RegisteredResourceMimetype(uri, mimetype); } void Resources::RegisterResourceTitle(const QString &uri, const QString &title) { // A dirty saninty check for the title if (title.length() < 3) { return; } emit RegisteredResourceTitle(uri, title); } diff --git a/src/service/ksmserver/KSMServer.cpp b/src/service/ksmserver/KSMServer.cpp index 6d59af5..a0f3cbe 100644 --- a/src/service/ksmserver/KSMServer.cpp +++ b/src/service/ksmserver/KSMServer.cpp @@ -1,236 +1,236 @@ /* * Copyright (C) 2012 - 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 "KSMServer.h" #include "KSMServer_p.h" // Qt #include #include #include #include #include #include // KDE #include // Utils #include // Local -#include +#include "DebugActivities.h" #define KWIN_SERVICE QStringLiteral("org.kde.KWin") KSMServer::Private::Private(KSMServer *parent) : serviceWatcher(new QDBusServiceWatcher(this)) , kwin(Q_NULLPTR) , processing(false) , q(parent) { serviceWatcher->setConnection(KDBusConnectionPool::threadConnection()); serviceWatcher->addWatchedService(KWIN_SERVICE); connect(serviceWatcher.get(), &QDBusServiceWatcher::serviceOwnerChanged, this, &Private::serviceOwnerChanged); serviceOwnerChanged(KWIN_SERVICE, QString(), QString()); } void KSMServer::Private::serviceOwnerChanged(const QString &service, const QString &oldOwner, const QString &newOwner) { Q_UNUSED(oldOwner); Q_UNUSED(newOwner); if (service == KWIN_SERVICE) { // Delete the old object, just in case delete kwin; kwin = Q_NULLPTR; if (KDBusConnectionPool::threadConnection().interface()->isServiceRegistered(KWIN_SERVICE)) { // Creating the new dbus interface // TODO: in multi-head environment there are multiple kwin instances // running and they will export different dbus name on different // root window. We have no support for that currently. // In future, the session management for Wayland may also need to be // reimplemented in some way. kwin = new QDBusInterface(KWIN_SERVICE, QStringLiteral("/KWin"), QStringLiteral("org.kde.KWin")); // If the service is valid, initialize it // otherwise delete the object if (kwin->isValid()) { kwin->setParent(this); } else { delete kwin; kwin = Q_NULLPTR; } } } } KSMServer::KSMServer(QObject *parent) : QObject(parent) , d(this) { } KSMServer::~KSMServer() { } void KSMServer::startActivitySession(const QString &activity) { d->processLater(activity, true); } void KSMServer::stopActivitySession(const QString &activity) { d->processLater(activity, false); } void KSMServer::Private::processLater(const QString &activity, bool start) { if (kwin) { for (const auto &item: queue) { if (item.first == activity) { return; } } queue << qMakePair(activity, start); if (!processing) { processing = true; QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection); } } else { // We don't have kwin. No way to invoke the session stuff subSessionSendEvent(start ? KSMServer::Started : KSMServer::Stopped); } } void KSMServer::Private::process() { // If the queue is empty, we have nothing more to do if (queue.isEmpty()) { processing = false; return; } const auto item = queue.takeFirst(); processingActivity = item.first; makeRunning(item.second); // Calling process again for the rest of the list QMetaObject::invokeMethod(this, "process", Qt::QueuedConnection); } void KSMServer::Private::makeRunning(bool value) { if (!kwin) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: No kwin, marking activity as: " << value; subSessionSendEvent(value ? KSMServer::Started : KSMServer::Stopped); return; } const auto call = kwin->asyncCall( value ? QLatin1String("startActivity") : QLatin1String("stopActivity"), processingActivity); const auto watcher = new QDBusPendingCallWatcher(call, this); qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Telling kwin to start/stop activity : " << processingActivity << value; QObject::connect( watcher, SIGNAL(finished(QDBusPendingCallWatcher *)), this, value ? SLOT(startCallFinished(QDBusPendingCallWatcher *)) : SLOT(stopCallFinished(QDBusPendingCallWatcher *))); } void KSMServer::Private::startCallFinished(QDBusPendingCallWatcher *call) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Start call is finished"; QDBusPendingReply reply = *call; if (reply.isError()) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Error in getting a reply for start, marking as started"; subSessionSendEvent(KSMServer::Started); } else { // If we got false, it means something is going on with ksmserver // and it didn't start our activity const auto retval = reply.argumentAt<0>(); if (!retval) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Error starting, marking as stopped"; subSessionSendEvent(KSMServer::Stopped); } else { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: All OK starting, marking as starting"; subSessionSendEvent(KSMServer::Started); } } call->deleteLater(); } void KSMServer::Private::stopCallFinished(QDBusPendingCallWatcher *call) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Stop call is finished"; QDBusPendingReply reply = *call; if (reply.isError()) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Error in getting a reply for stop, marking as stopped"; subSessionSendEvent(KSMServer::Stopped); } else { // If we got false, it means something is going on with ksmserver // and it didn't stop our activity const auto retval = reply.argumentAt<0>(); if (!retval) { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: Error stopping, marking as started"; subSessionSendEvent(KSMServer::FailedToStop); } else { qCDebug(KAMD_LOG_ACTIVITIES) << "Activities KSM: All OK stopping, marking as stopped"; subSessionSendEvent(KSMServer::Stopped); } } call->deleteLater(); } void KSMServer::Private::subSessionSendEvent(int event) { if (processingActivity.isEmpty()) { return; } emit q->activitySessionStateChanged(processingActivity, event); processingActivity.clear(); } diff --git a/src/service/plugins/runapplication/CMakeLists.txt b/src/service/plugins/runapplication/CMakeLists.txt index fcafccb..630cb69 100644 --- a/src/service/plugins/runapplication/CMakeLists.txt +++ b/src/service/plugins/runapplication/CMakeLists.txt @@ -1,43 +1,43 @@ # vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: project (kactivitymanagerd-runapplication) include_directories ( ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${CMAKE_CURRENT_BINARY_DIR} ${KConfig_INCLUDE_DIR} ) set ( runapplication_SRCS RunApplicationPlugin.cpp - ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/service/Debug.cpp + ${debug_SRCS} ${plugin_implementation_SRCS} ) kcoreaddons_add_plugin ( kactivitymanagerd_plugin_runapplication JSON kactivitymanagerd-plugin-runapplication.json SOURCES ${runapplication_SRCS} INSTALL_NAMESPACE ${KAMD_PLUGIN_DIR} ) target_link_libraries ( kactivitymanagerd_plugin_runapplication Qt5::Core Qt5::DBus Qt5::Gui KF5::CoreAddons KF5::ConfigCore KF5::Service kactivitymanagerd_plugin ) set_target_properties ( kactivitymanagerd_plugin_runapplication PROPERTIES PREFIX "" ) diff --git a/src/service/plugins/runapplication/RunApplicationPlugin.cpp b/src/service/plugins/runapplication/RunApplicationPlugin.cpp index 4c88b0d..3e1cd7d 100644 --- a/src/service/plugins/runapplication/RunApplicationPlugin.cpp +++ b/src/service/plugins/runapplication/RunApplicationPlugin.cpp @@ -1,137 +1,137 @@ /* * 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 . */ #include "RunApplicationPlugin.h" #include #include #include #include #include #include -#include +#include "DebugApplication.h" #include namespace { enum ActivityState { Running = 2, Stopped = 4 }; } KAMD_EXPORT_PLUGIN(runapplicationplugin, RunApplicationPlugin, "kactivitymanagerd-plugin-runapplication.json") RunApplicationPlugin::RunApplicationPlugin(QObject *parent, const QVariantList &args) : Plugin(parent) , m_activitiesService(Q_NULLPTR) { Q_UNUSED(args); setName("org.kde.ActivityManager.RunApplication"); } RunApplicationPlugin::~RunApplicationPlugin() { } bool RunApplicationPlugin::init(QHash &modules) { Plugin::init(modules); m_activitiesService = modules["activities"]; connect(m_activitiesService, SIGNAL(CurrentActivityChanged(QString)), this, SLOT(currentActivityChanged(QString))); connect(m_activitiesService, SIGNAL(ActivityStateChanged(QString, int)), this, SLOT(activityStateChanged(QString, int))); const auto currentActivity = Plugin::retrieve( m_activitiesService, "CurrentActivity", "QString"); currentActivityChanged(currentActivity); return true; } QString RunApplicationPlugin::activityDirectory(const QString &activity) const { return QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/activities/") + activity + '/'; } void RunApplicationPlugin::currentActivityChanged(const QString &activity) { if (m_currentActivity == activity) { return; } if (!m_currentActivity.isEmpty()) { executeIn(activityDirectory(activity) + "deactivated"); } m_currentActivity = activity; executeIn(activityDirectory(activity) + "activated"); if (!m_previousActivities.contains(activity)) { // This is the first time we have switched // to this activity in the current session, // pretending it has just been started activityStateChanged(activity, Running); m_previousActivities << activity; } } void RunApplicationPlugin::activityStateChanged(const QString &activity, int state) { auto directory = (state == Running) ? QStringLiteral("started") : (state == Stopped) ? QStringLiteral("stopped") : QString(); if (directory == "") { return; } executeIn(activityDirectory(activity) + directory); } void RunApplicationPlugin::executeIn(const QString &path) const { QDir directory(path); for (const auto& item: directory.entryList(QDir::Files)) { QString filePath = directory.filePath(item); KService service(filePath); if (service.isValid() && service.isApplication()) { qCDebug(KAMD_LOG_APPLICATION) << "Starting: " << service.exec(); QProcess::startDetached(service.exec()); } else { qCDebug(KAMD_LOG_APPLICATION) << "Openning file: " << QUrl::fromLocalFile(filePath); QDesktopServices::openUrl(QUrl::fromLocalFile(filePath)); } } } // void RunApplicationPlugin::activityRemoved(const QString &activity) // { // // TODO: Clean up the directory // } #include "RunApplicationPlugin.moc" diff --git a/src/service/plugins/sqlite/CMakeLists.txt b/src/service/plugins/sqlite/CMakeLists.txt index f50e71b..a33f401 100644 --- a/src/service/plugins/sqlite/CMakeLists.txt +++ b/src/service/plugins/sqlite/CMakeLists.txt @@ -1,65 +1,65 @@ # vim:set softtabstop=3 shiftwidth=3 tabstop=3 expandtab: project (kactivitymanagerd-plugin-sqlite) set ( sqliteplugin_SRCS Database.cpp StatsPlugin.cpp ResourceScoreCache.cpp ResourceScoreMaintainer.cpp ResourceLinking.cpp - ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/service/Debug.cpp + ${debug_SRCS} ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/database/Database.cpp ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/database/schema/ResourcesDatabaseSchema.cpp ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/utils/qsqlquery_iterator.cpp ) qt5_add_dbus_adaptor ( sqliteplugin_SRCS ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesLinking.xml ResourceLinking.h ResourceLinking resourcelinkingadaptor ) qt5_add_dbus_adaptor ( sqliteplugin_SRCS ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src/common/dbus/org.kde.ActivityManager.ResourcesScoring.xml StatsPlugin.h StatsPlugin resourcescoringadaptor ) include_directories ( ${KACTIVITIES_CURRENT_ROOT_SOURCE_DIR}/src ${CMAKE_BINARY_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/../.. ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_BINARY_DIR}/../.. ${KConfig_INCLUDE_DIR} ) kcoreaddons_add_plugin( kactivitymanagerd_plugin_sqlite JSON kactivitymanagerd-plugin-sqlite.json SOURCES ${sqliteplugin_SRCS} INSTALL_NAMESPACE ${KAMD_PLUGIN_DIR} ) target_link_libraries ( kactivitymanagerd_plugin_sqlite Qt5::Core Qt5::Sql KF5::ConfigCore KF5::KIOCore KF5::DBusAddons KF5::CoreAddons kactivitymanagerd_plugin ) set_target_properties ( kactivitymanagerd_plugin_sqlite PROPERTIES PREFIX "" ) diff --git a/src/service/plugins/sqlite/Database.cpp b/src/service/plugins/sqlite/Database.cpp index 7182c3b..58a730d 100644 --- a/src/service/plugins/sqlite/Database.cpp +++ b/src/service/plugins/sqlite/Database.cpp @@ -1,87 +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 "DebugResources.h" #include "Utils.h" #include #include class ResourcesDatabaseMigrator::Private { public: Common::Database::Ptr database; }; Common::Database::Ptr resourcesDatabase() { static ResourcesDatabaseMigrator instance; return instance.d->database; } ResourcesDatabaseMigrator::ResourcesDatabaseMigrator() { const QString databaseDir = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/kactivitymanagerd/resources/"); qCDebug(KAMD_LOG_RESOURCES) << "Creating directory: " << databaseDir; auto created = QDir().mkpath(databaseDir); if (!created || !QDir(databaseDir).exists()) { qCWarning(KAMD_LOG_RESOURCES) << "Database folder can not be created!"; } d->database = Common::Database::instance( Common::Database::ResourcesDatabase, Common::Database::ReadWrite); 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 cfa21d2..b7c6f5f 100644 --- a/src/service/plugins/sqlite/Database.h +++ b/src/service/plugins/sqlite/Database.h @@ -1,65 +1,64 @@ /* * 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::Ptr 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 1ad64df..9483db6 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 "DebugResources.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) { qCDebug(KAMD_LOG_RESOURCES) << "Linking " << targettedResource << " to " << usedActivity << " from " << initiatingAgent; if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) { qCWarning(KAMD_LOG_RESOURCES) << "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, 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()) { // qCDebug(KAMD_LOG_RESOURCES) << "Sending link event added: activities:/" << usedActivity; org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/") + usedActivity)); if (usedActivity == StatsPlugin::self()->currentActivity()) { // qCDebug(KAMD_LOG_RESOURCES) << "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) { // qCDebug(KAMD_LOG_RESOURCES) << "Unlinking " << targettedResource << " from " << usedActivity << " from " << initiatingAgent; if (!validateArguments(initiatingAgent, targettedResource, usedActivity)) { qCWarning(KAMD_LOG_RESOURCES) << "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, QStringLiteral( "DELETE FROM ResourceLink " "WHERE " "initiatingAgent = COALESCE(:initiatingAgent , '') AND " "targettedResource = COALESCE(:targettedResource, '') " )); query = unlinkResourceFromAllActivitiesQuery.get(); } else { 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()); 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)); // qCDebug(KAMD_LOG_RESOURCES) << "Sending link event removed: activities:/" << usedActivity << '/' << mangled; org::kde::KDirNotify::emitFilesRemoved( { QUrl(QStringLiteral("activities:/") + usedActivity + '/' + mangled) }); if (usedActivity == StatsPlugin::self()->currentActivity()) { // qCDebug(KAMD_LOG_RESOURCES) << "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, 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()) { qCDebug(KAMD_LOG_RESOURCES) << "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()) { qCDebug(KAMD_LOG_RESOURCES) << "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)) { qCDebug(KAMD_LOG_RESOURCES) << "Activity is invalid, it does not exist"; return false; } // qCDebug(KAMD_LOG_RESOURCES) << "agent" << initiatingAgent // << "resource" << targettedResource // << "activity" << usedActivity; return true; } void ResourceLinking::onActivityAdded(const QString &activity) { Q_UNUSED(activity); // Notify KIO // qCDebug(KAMD_LOG_RESOURCES) << "Added: activities:/ (" << activity << ")"; org::kde::KDirNotify::emitFilesAdded(QUrl(QStringLiteral("activities:/"))); } void ResourceLinking::onActivityRemoved(const QString &activity) { // Notify KIO // qCDebug(KAMD_LOG_RESOURCES) << "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 // qCDebug(KAMD_LOG_RESOURCES) << "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 d6304d1..7229908 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 "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(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()); 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() ); // 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()); 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() ); 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; 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 } else { score += d->timeFactor(QDateTime::fromTime_t(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()) ); } diff --git a/src/service/plugins/sqlite/StatsPlugin.cpp b/src/service/plugins/sqlite/StatsPlugin.cpp index 7967110..738322d 100644 --- a/src/service/plugins/sqlite/StatsPlugin.cpp +++ b/src/service/plugins/sqlite/StatsPlugin.cpp @@ -1,724 +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); 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 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) { 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() ); 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" diff --git a/src/service/plugins/sqlite/Utils.h b/src/service/plugins/sqlite/Utils.h index b6c8adb..5a41659 100644 --- a/src/service/plugins/sqlite/Utils.h +++ b/src/service/plugins/sqlite/Utils.h @@ -1,85 +1,87 @@ /* * Copyright (C) 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. */ #ifndef PLUGINS_SQLITE_DATABASE_UTILS_H #define PLUGINS_SQLITE_DATABASE_UTILS_H #include #include #include +#include "DebugResources.h" + namespace Utils { static unsigned int errorCount = 0; inline bool prepare(Common::Database &database, QSqlQuery &query, const QString &queryString) { Q_UNUSED(database); return query.prepare(queryString); } inline bool prepare(Common::Database &database, std::unique_ptr &query, const QString &queryString) { if (query) { return true; } query.reset(new QSqlQuery(database.createQuery())); return prepare(database, *query, queryString); } enum ErrorHandling { IgnoreError, FailOnError }; inline bool exec(ErrorHandling eh, QSqlQuery &query) { bool success = query.exec(); if (eh == FailOnError) { if ((!success) && (errorCount++ < 2)) { qCDebug(KAMD_LOG_RESOURCES) << query.lastQuery(); qCDebug(KAMD_LOG_RESOURCES) << query.lastError(); } Q_ASSERT_X(success, "Uils::exec", "Query failed"); } return success; } template inline bool exec(ErrorHandling eh, QSqlQuery &query, const T1 &variable, const T2 &value, Ts... ts) { query.bindValue(variable, value); return exec(eh, query, ts...); } } // namespace Utils #endif /* !PLUGINS_SQLITE_DATABASE_UTILS_H */ diff --git a/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp b/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp index 0fe886a..a34f701 100644 --- a/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp +++ b/src/service/plugins/virtualdesktopswitch/VirtualDesktopSwitchPlugin.cpp @@ -1,85 +1,84 @@ /* * 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 . */ #include "VirtualDesktopSwitchPlugin.h" #include -#include #include KAMD_EXPORT_PLUGIN(virtualdesktopswitchplugin, VirtualDesktopSwitchPlugin, "kactivitymanagerd-plugin-virtualdesktopswitch.json") const auto configPattern = QStringLiteral("desktop-for-%1"); VirtualDesktopSwitchPlugin::VirtualDesktopSwitchPlugin(QObject *parent, const QVariantList &args) : Plugin(parent) , m_activitiesService(Q_NULLPTR) { Q_UNUSED(args); setName("org.kde.ActivityManager.VirtualDesktopSwitch"); } VirtualDesktopSwitchPlugin::~VirtualDesktopSwitchPlugin() { } bool VirtualDesktopSwitchPlugin::init(QHash &modules) { Plugin::init(modules); m_activitiesService = modules["activities"]; m_currentActivity = Plugin::retrieve( m_activitiesService, "CurrentActivity", "QString"); connect(m_activitiesService, SIGNAL(CurrentActivityChanged(QString)), this, SLOT(currentActivityChanged(QString))); connect(m_activitiesService, SIGNAL(ActivityRemoved(QString)), this, SLOT(activityRemoved(QString))); return true; } void VirtualDesktopSwitchPlugin::currentActivityChanged(const QString &activity) { if (m_currentActivity == activity) { return; } config().writeEntry( configPattern.arg(m_currentActivity), QString::number(KWindowSystem::currentDesktop())); m_currentActivity = activity; const auto desktopId = config().readEntry(configPattern.arg(m_currentActivity), -1); if (desktopId <= KWindowSystem::numberOfDesktops() && desktopId >= 0) { KWindowSystem::setCurrentDesktop(desktopId); } } void VirtualDesktopSwitchPlugin::activityRemoved(const QString &activity) { config().deleteEntry(configPattern.arg(activity)); config().sync(); } #include "VirtualDesktopSwitchPlugin.moc"