diff --git a/apps/dde-application-manager/src/main.cpp b/apps/dde-application-manager/src/main.cpp index 2b134cc..d90d294 100644 --- a/apps/dde-application-manager/src/main.cpp +++ b/apps/dde-application-manager/src/main.cpp @@ -8,6 +8,7 @@ #include #include "dbus/applicationmanager1service.h" #include "cgroupsidentifier.h" +#include "applicationmanagerstorage.h" #include #include @@ -42,7 +43,10 @@ int main(int argc, char *argv[]) auto &AMBus = bus.globalServerBus(); registerComplexDbusType(); - ApplicationManager1Service AMService{std::make_unique(), AMBus}; + auto storageDir = getXDGDataHome() + QDir::separator() + "deepin" + QDir::separator() + "ApplicationManager"; + auto storage = ApplicationManager1Storage::createApplicationManager1Storage(storageDir); + + ApplicationManager1Service AMService{std::make_unique(), AMBus, storage}; #ifdef PROFILING_MODE auto end = std::chrono::high_resolution_clock::now(); diff --git a/src/applicationmanagerstorage.cpp b/src/applicationmanagerstorage.cpp new file mode 100644 index 0000000..d4161e9 --- /dev/null +++ b/src/applicationmanagerstorage.cpp @@ -0,0 +1,205 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#include "applicationmanagerstorage.h" +#include "constant.h" +#include +#include +#include +#include + +std::shared_ptr +ApplicationManager1Storage::createApplicationManager1Storage(const QString &storageDir) noexcept +{ + QDir dir; + auto dirPath = QDir::cleanPath(storageDir); + if (!dir.mkpath(dirPath)) { + qCritical() << "can't create directory"; + return nullptr; + } + + dir.setPath(dirPath); + auto storagePath = dir.filePath("storage.json"); + auto obj = std::shared_ptr(new (std::nothrow) ApplicationManager1Storage{storagePath}); + + if (!obj) { + qCritical() << "new ApplicationManager1Storage failed."; + return nullptr; + } + + if (!obj->m_file->open(QFile::ReadWrite)) { + qCritical() << "can't open file:" << storagePath; + return nullptr; + } + + auto content = obj->m_file->readAll(); + if (content.isEmpty()) { // new file + obj->setVersion(STORAGE_VERSION); + return obj; + } + + // TODO: support migrate from lower storage version. + + QJsonParseError err; + auto json = QJsonDocument::fromJson(content, &err); + if (err.error != QJsonParseError::NoError) { + qDebug() << "parse json failed:" << err.errorString() << "clear this file content."; + obj->m_file->resize(0); + } else { + obj->m_data = json.object(); + } + + return obj; +} + +ApplicationManager1Storage::ApplicationManager1Storage(const QString &storagePath) + : m_file(std::make_unique(storagePath)) +{ +} + +void ApplicationManager1Storage::writeToFile() const noexcept +{ + if (!m_file) { + qCritical() << "file is nullptr"; + return; + } + + if (!m_file->resize(0)) { + qCritical() << "failed to clear file:" << m_file->errorString(); + return; + } + + auto content = QJsonDocument{m_data}.toJson(QJsonDocument::Compact); + auto bytes = m_file->write(content, content.size()); + if (bytes != content.size()) { + qWarning() << "Incomplete file writes:" << m_file->errorString(); + } + + if (!m_file->flush()) { + qCritical() << "io error."; + } +} + +void ApplicationManager1Storage::setVersion(uint8_t version) noexcept +{ + m_data["version"] = version; + writeToFile(); +} + +uint8_t ApplicationManager1Storage::version() const noexcept +{ + return m_data["version"].toInt(0); +} + +void ApplicationManager1Storage::createApplicationValue(const QString &appId, + const QString &groupName, + const QString &valueKey, + const QVariant &value) noexcept +{ + if (appId.isEmpty() or groupName.isEmpty() or valueKey.isEmpty()) { + return; + } + + QJsonObject appObj; + if (m_data.contains(appId)) { + appObj = m_data[appId].toObject(); + } + + QJsonObject groupObj; + if (appObj.contains(groupName)) { + groupObj = appObj[groupName].toObject(); + } + + if (groupObj.contains(valueKey)) { + return; + } + + groupObj.insert(valueKey, value.toJsonValue()); + appObj.insert(groupName, groupObj); + m_data.insert(appId, appObj); + + writeToFile(); +} + +void ApplicationManager1Storage::updateApplicationValue(const QString &appId, + const QString &groupName, + const QString &valueKey, + const QVariant &value) noexcept +{ + if (appId.isEmpty() or groupName.isEmpty() or valueKey.isEmpty()) { + return; + } + + if (!m_data.contains(appId)) { + return; + } + auto appObj = m_data[appId].toObject(); + + if (!appObj.contains(groupName)) { + return; + } + auto groupObj = appObj[groupName].toObject(); + + if (!groupObj.contains(valueKey)) { + return; + } + + groupObj.insert(valueKey, value.toJsonValue()); + appObj.insert(groupName, groupObj); + m_data.insert(appId, appObj); + + writeToFile(); +} + +QVariant ApplicationManager1Storage::readApplicationValue(const QString &appId, + const QString &groupName, + const QString &valueKey) const noexcept +{ + return m_data[appId][groupName][valueKey].toVariant(); +} + +void ApplicationManager1Storage::deleteApplicationValue(const QString &appId, + const QString &groupName, + const QString &valueKey) noexcept +{ + if (appId.isEmpty()) { + auto empty = QJsonObject{}; + m_data.swap(empty); + return; + } + + auto app = m_data.find(appId).value(); + if (app.isNull()) { + return; + } + auto appObj = app.toObject(); + + if (groupName.isEmpty()) { + m_data.remove(appId); + return; + } + + auto group = appObj.find(groupName).value(); + if (group.isNull()) { + return; + } + auto groupObj = group.toObject(); + + if (valueKey.isEmpty()) { + appObj.remove(groupName); + m_data.insert(appId, appObj); + return; + } + + auto val = groupObj.find(valueKey).value(); + if (val.isNull()) { + return; + } + + groupObj.remove(valueKey); + appObj.insert(groupName, groupObj); + m_data.insert(appId, appObj); + + writeToFile(); +} diff --git a/src/applicationmanagerstorage.h b/src/applicationmanagerstorage.h new file mode 100644 index 0000000..9f6b188 --- /dev/null +++ b/src/applicationmanagerstorage.h @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: LGPL-3.0-or-later + +#ifndef APPLICATIONMANAGERSTORAGE_H +#define APPLICATIONMANAGERSTORAGE_H + +#include +#include +#include + +enum class ModifyMode { Create, Update }; + +class ApplicationManager1Storage +{ +public: + ApplicationManager1Storage(const ApplicationManager1Storage &) = delete; + ApplicationManager1Storage(ApplicationManager1Storage &&) = default; + ApplicationManager1Storage &operator=(const ApplicationManager1Storage &) = delete; + ApplicationManager1Storage &operator=(ApplicationManager1Storage &&) = default; + ~ApplicationManager1Storage() = default; + + void createApplicationValue(const QString &appId, + const QString &groupName, + const QString &valueKey, + const QVariant &value) noexcept; + void updateApplicationValue(const QString &appId, + const QString &groupName, + const QString &valueKey, + const QVariant &value) noexcept; + [[nodiscard]] QVariant + readApplicationValue(const QString &appId, const QString &groupName, const QString &valueKey) const noexcept; + void deleteApplicationValue(const QString &appId = "", const QString &groupName = "", const QString &valueKey = "") noexcept; + + void setVersion(uint8_t version) noexcept; + [[nodiscard]] uint8_t version() const noexcept; + + static std::shared_ptr createApplicationManager1Storage(const QString &storageDir) noexcept; + +private: + void writeToFile() const noexcept; + explicit ApplicationManager1Storage(const QString &storagePath); + std::unique_ptr m_file; + QJsonObject m_data; +}; + +#endif diff --git a/src/constant.h b/src/constant.h index 528661a..5e52656 100644 --- a/src/constant.h +++ b/src/constant.h @@ -47,4 +47,8 @@ constexpr auto systemdOption = u8"systemd"; constexpr auto splitOption = u8"split"; constexpr auto AppExecOption = u8"appExec"; +constexpr auto STORAGE_VERSION = 1; +constexpr auto ApplicationPropertiesGroup = u8"Application Properties"; +constexpr auto LastLaunchedTime = u8"LastLaunchedTime"; + #endif diff --git a/src/dbus/applicationmanager1service.cpp b/src/dbus/applicationmanager1service.cpp index ff9f060..1d5b9cc 100644 --- a/src/dbus/applicationmanager1service.cpp +++ b/src/dbus/applicationmanager1service.cpp @@ -13,8 +13,11 @@ ApplicationManager1Service::~ApplicationManager1Service() = default; -ApplicationManager1Service::ApplicationManager1Service(std::unique_ptr ptr, QDBusConnection &connection) noexcept +ApplicationManager1Service::ApplicationManager1Service(std::unique_ptr ptr, + QDBusConnection &connection, + std::weak_ptr storage) noexcept : m_identifier(std::move(ptr)) + , m_storage(std::move(storage)) { if (!connection.registerService(DDEApplicationManager1ServiceName)) { qFatal("%s", connection.lastError().message().toLocal8Bit().data()); @@ -116,6 +119,17 @@ void ApplicationManager1Service::addInstanceToApplication(const QString &unitNam return; } + if (sender() != nullptr) { // activate by signal + auto timestamp = QDateTime::currentMSecsSinceEpoch(); + + if (auto ptr = m_storage.lock(); ptr) { + ptr->updateApplicationValue((*appIt)->m_desktopSource.desktopId(), + ApplicationPropertiesGroup, + LastLaunchedTime, + QVariant::fromValue(timestamp)); + } + } + const auto &applicationPath = (*appIt)->applicationPath().path(); if (!(*appIt)->addOneInstance(instanceId, applicationPath, systemdUnitPath.path())) [[likely]] { @@ -229,7 +243,7 @@ QList ApplicationManager1Service::list() const bool ApplicationManager1Service::addApplication(DesktopFile desktopFileSource) noexcept { QSharedPointer application = - ApplicationService::createApplicationService(std::move(desktopFileSource), this); + ApplicationService::createApplicationService(std::move(desktopFileSource), this, m_storage); if (!application) { return false; } @@ -251,6 +265,18 @@ bool ApplicationManager1Service::addApplication(DesktopFile desktopFileSource) n return false; } m_applicationList.insert(application->applicationPath(), application); + + if (auto storagePtr = m_storage.lock(); storagePtr) { + auto appId = ptr->id(); + auto value = storagePtr->readApplicationValue(appId, ApplicationPropertiesGroup, LastLaunchedTime); + if (value.isNull()) { + storagePtr->createApplicationValue( + appId, ApplicationPropertiesGroup, LastLaunchedTime, QVariant::fromValue(0)); + } else { + ptr->m_lastLaunch = value.toInt(); + } + } + emit listChanged(); emit InterfacesAdded(application->applicationPath(), getChildInterfacesAndPropertiesFromObject(ptr)); @@ -261,8 +287,12 @@ void ApplicationManager1Service::removeOneApplication(const QDBusObjectPath &app { if (auto it = m_applicationList.find(application); it != m_applicationList.cend()) { emit InterfacesRemoved(application, getChildInterfacesFromObject(it->data())); + if (auto ptr = m_storage.lock(); ptr) { + ptr->deleteApplicationValue((*it)->id()); + } unregisterObjectFromDBus(application.path()); m_applicationList.remove(application); + emit listChanged(); } } diff --git a/src/dbus/applicationmanager1service.h b/src/dbus/applicationmanager1service.h index f8516ff..cfe8fcb 100644 --- a/src/dbus/applicationmanager1service.h +++ b/src/dbus/applicationmanager1service.h @@ -12,6 +12,7 @@ #include #include #include +#include "applicationmanagerstorage.h" #include "dbus/jobmanager1service.h" #include "desktopentry.h" #include "identifier.h" @@ -22,7 +23,9 @@ class ApplicationManager1Service final : public QObject { Q_OBJECT public: - explicit ApplicationManager1Service(std::unique_ptr ptr, QDBusConnection &connection) noexcept; + explicit ApplicationManager1Service(std::unique_ptr ptr, + QDBusConnection &connection, + std::weak_ptr storage) noexcept; ~ApplicationManager1Service() override; ApplicationManager1Service(const ApplicationManager1Service &) = delete; ApplicationManager1Service(ApplicationManager1Service &&) = delete; @@ -52,6 +55,7 @@ Q_SIGNALS: private: std::unique_ptr m_identifier; + std::weak_ptr m_storage; QScopedPointer m_jobManager{nullptr}; QMap> m_applicationList; diff --git a/src/dbus/applicationservice.cpp b/src/dbus/applicationservice.cpp index d1f2d2d..c467cc0 100644 --- a/src/dbus/applicationservice.cpp +++ b/src/dbus/applicationservice.cpp @@ -6,6 +6,7 @@ #include "APPobjectmanager1adaptor.h" #include "applicationchecker.h" #include "applicationmanager1service.h" +#include "applicationmanagerstorage.h" #include "propertiesForwarder.h" #include "dbus/instanceadaptor.h" #include "launchoptions.h" @@ -18,10 +19,14 @@ #include #include #include +#include #include -ApplicationService::ApplicationService(DesktopFile source, ApplicationManager1Service *parent) +ApplicationService::ApplicationService(DesktopFile source, + ApplicationManager1Service *parent, + std::weak_ptr storage) : QObject(parent) + , m_storage(std::move(storage)) , m_desktopSource(std::move(source)) { } @@ -36,10 +41,10 @@ ApplicationService::~ApplicationService() } } -QSharedPointer ApplicationService::createApplicationService(DesktopFile source, - ApplicationManager1Service *parent) noexcept +QSharedPointer ApplicationService::createApplicationService( + DesktopFile source, ApplicationManager1Service *parent, std::weak_ptr storage) noexcept { - QSharedPointer app{new (std::nothrow) ApplicationService{std::move(source), parent}}; + QSharedPointer app{new (std::nothrow) ApplicationService{std::move(source), parent, std::move(storage)}}; if (!app) { qCritical() << "new application service failed."; return nullptr; @@ -90,6 +95,12 @@ QSharedPointer ApplicationService::createApplicationService( return nullptr; } + auto ptr = app->m_storage.lock(); + if (!ptr) { + qWarning() << "runtime storage doesn't exists."; + return app; + } + return app; } @@ -176,7 +187,7 @@ QDBusObjectPath ApplicationService::Launch(const QString &action, const QStringL auto &jobManager = static_cast(parent())->jobManager(); return jobManager.addJob( m_applicationPath.path(), - [this, binary = std::move(bin), commands = std::move(cmds)](QVariant variantValue) mutable -> QVariant { + [this, binary = std::move(bin), commands = std::move(cmds)](const QVariant &variantValue) mutable -> QVariant { auto resourceFile = variantValue.toString(); auto instanceRandomUUID = QUuid::createUuid().toString(QUuid::Id128); auto objectPath = m_applicationPath.path() + "/" + instanceRandomUUID; diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h index e2b7f82..f08fa5f 100644 --- a/src/dbus/applicationservice.h +++ b/src/dbus/applicationservice.h @@ -17,6 +17,7 @@ #include #include #include +#include "applicationmanagerstorage.h" #include "dbus/instanceservice.h" #include "global.h" #include "desktopentry.h" @@ -110,10 +111,13 @@ Q_SIGNALS: private: friend class ApplicationManager1Service; - explicit ApplicationService(DesktopFile source, ApplicationManager1Service *parent); - static QSharedPointer createApplicationService(DesktopFile source, - ApplicationManager1Service *parent) noexcept; - qlonglong m_lastLaunch{0}; + explicit ApplicationService(DesktopFile source, + ApplicationManager1Service *parent, + std::weak_ptr storage); + static QSharedPointer createApplicationService( + DesktopFile source, ApplicationManager1Service *parent, std::weak_ptr storage) noexcept; + qint64 m_lastLaunch{0}; + std::weak_ptr m_storage; QDBusObjectPath m_applicationPath; QString m_launcher{getApplicationLauncherBinary()}; DesktopFile m_desktopSource; diff --git a/src/global.h b/src/global.h index 950b1de..1f20817 100644 --- a/src/global.h +++ b/src/global.h @@ -363,6 +363,15 @@ inline QString getRelativePathFromAppId(const QString &id) return path; } +inline QString getXDGDataHome() +{ + auto XDGDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); + if (XDGDataHome.isEmpty()) { + XDGDataHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".local" + QDir::separator() + "share"; + } + return XDGDataHome; +} + inline QStringList getDesktopFileDirs() { auto XDGDataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts); @@ -372,12 +381,7 @@ inline QStringList getDesktopFileDirs() XDGDataDirs.append("/usr/share"); } - auto XDGDataHome = QString::fromLocal8Bit(qgetenv("XDG_DATA_HOME")); - if (XDGDataHome.isEmpty()) { - XDGDataHome = QString::fromLocal8Bit(qgetenv("HOME")) + QDir::separator() + ".local" + QDir::separator() + "share"; - } - - XDGDataDirs.push_front(std::move(XDGDataHome)); + XDGDataDirs.push_front(getXDGDataHome()); std::for_each(XDGDataDirs.begin(), XDGDataDirs.end(), [](QString &str) { if (!str.endsWith(QDir::separator())) { diff --git a/tests/ut_applicationmanager.cpp b/tests/ut_applicationmanager.cpp index c917bf0..301c5e2 100644 --- a/tests/ut_applicationmanager.cpp +++ b/tests/ut_applicationmanager.cpp @@ -33,11 +33,11 @@ public: auto &bus = ApplicationManager1DBus::instance(); bus.initGlobalServerBus(DBusType::Session); bus.setDestBus(); - - m_am = new ApplicationManager1Service{std::make_unique(), bus.globalServerBus()}; + std::shared_ptr tmp{nullptr}; + m_am = new ApplicationManager1Service{std::make_unique(), bus.globalServerBus(), tmp}; auto ptr = std::make_unique(QString{"/usr/share/applications/test-Application.desktop"}); DesktopFile file{std::move(ptr), "test-Application", 0, 0}; - QSharedPointer app = QSharedPointer::create(std::move(file), nullptr); + QSharedPointer app = QSharedPointer::create(std::move(file), nullptr, tmp); QSharedPointer instance = QSharedPointer::create(InstancePath.path().split('/').last(), ApplicationPath.path(), QString{"/"}); app->m_Instances.insert(InstancePath, instance);