/* SPDX-FileCopyrightText: 2013-2016 Ivan Cukic SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL */ #include "activitiescache_p.h" #include "manager_p.h" #include #include #include "mainthreadexecutor_p.h" namespace KActivities { static QString nulluuid = QStringLiteral("00000000-0000-0000-0000-000000000000"); using kamd::utils::Mutable; std::shared_ptr ActivitiesCache::self() { static std::weak_ptr s_instance; static std::mutex singleton; std::lock_guard singleton_lock(singleton); auto result = s_instance.lock(); if (s_instance.expired()) { runInMainThread([&result] { result.reset(new ActivitiesCache()); s_instance = result; }); } return result; } ActivitiesCache::ActivitiesCache() : m_status(Consumer::NotRunning) { // qDebug() << "ActivitiesCache: Creating a new instance"; using org::kde::ActivityManager::Activities; auto activities = Manager::self()->activities(); connect(activities, &Activities::ActivityAdded, this, &ActivitiesCache::updateActivity); connect(activities, &Activities::ActivityChanged, this, &ActivitiesCache::updateActivity); connect(activities, &Activities::ActivityRemoved, this, &ActivitiesCache::removeActivity); connect(activities, &Activities::ActivityStateChanged, this, &ActivitiesCache::updateActivityState); connect(activities, &Activities::ActivityNameChanged, this, &ActivitiesCache::setActivityName); connect(activities, &Activities::ActivityDescriptionChanged, this, &ActivitiesCache::setActivityDescription); connect(activities, &Activities::ActivityIconChanged, this, &ActivitiesCache::setActivityIcon); connect(activities, &Activities::CurrentActivityChanged, this, &ActivitiesCache::setCurrentActivity); connect(Manager::self(), &Manager::serviceStatusChanged, this, &ActivitiesCache::setServiceStatus); // These are covered by ActivityStateChanged // signal void org.kde.ActivityManager.Activities.ActivityStarted(QString activity) // signal void org.kde.ActivityManager.Activities.ActivityStopped(QString activity) setServiceStatus(Manager::self()->serviceStatus()); } void ActivitiesCache::setServiceStatus(Manager::ServiceStatus status) { loadOfflineDefaults(); auto oldStatus = m_status; switch (status) { case Manager::NotRunning: m_status = Consumer::NotRunning; break; case Manager::Starting: m_status = Consumer::Unknown; break; case Manager::Running: // will become running once loaded m_status = Consumer::Unknown; updateAllActivities(); break; } if (m_status != oldStatus) { Q_EMIT serviceStatusChanged(m_status); } } void ActivitiesCache::loadOfflineDefaults() { m_activities.clear(); m_activities << ActivityInfo(nulluuid, QString(), QString(), QString(), Info::Running); m_currentActivity = nulluuid; Q_EMIT activityListChanged(); } ActivitiesCache::~ActivitiesCache() { // qDebug() << "ActivitiesCache: Destroying the instance"; } void ActivitiesCache::removeActivity(const QString &id) { // qDebug() << "Removing the activity"; // Since we are sorting the activities by name now, // we can not use lower_bound to search for an activity // with a specified id const auto where = find(id); if (where != m_activities.end() && where->id == id) { m_activities.erase(where); Q_EMIT activityRemoved(id); Q_EMIT activityListChanged(); } else { // qFatal("Requested to delete an non-existent activity"); } } void ActivitiesCache::updateAllActivities() { // Loading the current activity auto call = Manager::self()->activities()->asyncCall(QStringLiteral("CurrentActivity")); onCallFinished(call, SLOT(setCurrentActivityFromReply(QDBusPendingCallWatcher *))); // Loading all the activities call = Manager::self()->activities()->asyncCall(QStringLiteral("ListActivitiesWithInformation")); onCallFinished(call, SLOT(setAllActivitiesFromReply(QDBusPendingCallWatcher *))); } void ActivitiesCache::updateActivity(const QString &id) { // qDebug() << "Updating activity" << id; auto call = Manager::self()->activities()->asyncCall(QStringLiteral("ActivityInformation"), id); onCallFinished(call, SLOT(setActivityInfoFromReply(QDBusPendingCallWatcher *))); } void ActivitiesCache::updateActivityState(const QString &id, int state) { auto where = getInfo(id); if (where && where->state != state) { auto isInvalid = [](int state) { return state == Info::Invalid || state == Info::Unknown; }; auto isStopped = [](int state) { return state == Info::Stopped || state == Info::Starting; }; auto isRunning = [](int state) { return state == Info::Running || state == Info::Stopping; }; const bool runningStateChanged = (isInvalid(state) || isInvalid(where->state) || (isStopped(state) && isRunning(where->state)) || (isRunning(state) && isStopped(where->state))); where->state = state; if (runningStateChanged) { Q_EMIT runningActivityListChanged(); } Q_EMIT activityStateChanged(id, state); } else { // qFatal("Requested to update the state of a non-existent activity"); } } template void ActivitiesCache::passInfoFromReply(QDBusPendingCallWatcher *watcher, _Functor f) { QDBusPendingReply<_Result> reply = *watcher; if (!reply.isError()) { auto replyValue = reply.template argumentAt<0>(); // qDebug() << "Got some reply" << replyValue; ((*this).*f)(replyValue); } watcher->deleteLater(); } void ActivitiesCache::setActivityInfoFromReply(QDBusPendingCallWatcher *watcher) { // qDebug() << "reply..."; passInfoFromReply(watcher, &ActivitiesCache::setActivityInfo); } void ActivitiesCache::setAllActivitiesFromReply(QDBusPendingCallWatcher *watcher) { // qDebug() << "reply..."; passInfoFromReply(watcher, &ActivitiesCache::setAllActivities); } void ActivitiesCache::setCurrentActivityFromReply(QDBusPendingCallWatcher *watcher) { // qDebug() << "reply..."; passInfoFromReply(watcher, &ActivitiesCache::setCurrentActivity); } void ActivitiesCache::setActivityInfo(const ActivityInfo &info) { // qDebug() << "Setting activity info" << info.id; // Are we updating an existing activity, or adding a new one? const auto iter = find(info.id); const auto present = iter != m_activities.end(); bool runningChanged = true; // If there is an activity with the specified id, // we are going to remove it, temporarily. if (present) { runningChanged = (*iter).state != info.state; m_activities.erase(iter); } // Now, we need to find where to insert the activity // and keep the cache sorted by name const auto where = lower_bound(info); m_activities.insert(where, info); if (present) { Q_EMIT activityChanged(info.id); } else { Q_EMIT activityAdded(info.id); Q_EMIT activityListChanged(); if (runningChanged) { Q_EMIT runningActivityListChanged(); } } } // clang-format off #define CREATE_SETTER(WHAT, What) \ void ActivitiesCache::setActivity##WHAT(const QString &id, \ const QString &value) \ { \ auto where = getInfo(id); \ \ if (where) { \ where->What = value; \ Q_EMIT activity##WHAT##Changed(id, value); \ } \ } // clang-format on CREATE_SETTER(Name, name) CREATE_SETTER(Description, description) CREATE_SETTER(Icon, icon) #undef CREATE_SETTER void ActivitiesCache::setAllActivities(const ActivityInfoList &_activities) { // qDebug() << "Setting all activities"; m_activities.clear(); const ActivityInfoList activities = _activities; for (const ActivityInfo &info : activities) { m_activities << info; } std::sort(m_activities.begin(), m_activities.end(), &infoLessThan); m_status = Consumer::Running; Q_EMIT serviceStatusChanged(m_status); Q_EMIT activityListChanged(); } void ActivitiesCache::setCurrentActivity(const QString &activity) { // qDebug() << "Setting current activity to" << activity; if (m_currentActivity == activity) { return; } m_currentActivity = activity; Q_EMIT currentActivityChanged(activity); } } // namespace KActivities #include "moc_activitiescache_p.cpp"