diff --git a/api/dbus/org.desktopspec.ApplicationManager1.xml b/api/dbus/org.desktopspec.ApplicationManager1.xml index 082f181..5165298 100644 --- a/api/dbus/org.desktopspec.ApplicationManager1.xml +++ b/api/dbus/org.desktopspec.ApplicationManager1.xml @@ -3,7 +3,7 @@ - + (desktopFileDirs.begin(), desktopFileDirs.end()), [&AMService](const QFileInfo &info) -> bool { - ParseError err{ParseError::NoError}; - auto ret = DesktopFile::searchDesktopFile(info.absoluteFilePath(), err); + DesktopErrorCode err{DesktopErrorCode::NoError}; + auto ret = DesktopFile::searchDesktopFileByPath(info.absoluteFilePath(), err); if (!ret.has_value()) { qWarning() << "failed to search File:" << err; return false; diff --git a/src/dbus/applicationmanager1service.cpp b/src/dbus/applicationmanager1service.cpp index a9e4d86..6ae33c8 100644 --- a/src/dbus/applicationmanager1service.cpp +++ b/src/dbus/applicationmanager1service.cpp @@ -132,8 +132,10 @@ QList ApplicationManager1Service::list() const void ApplicationManager1Service::removeOneApplication(const QDBusObjectPath &application) { - unregisterObjectFromDBus(application.path()); - m_applicationList.remove(application); + if (auto it = m_applicationList.find(application); it != m_applicationList.cend()) { + unregisterObjectFromDBus(application.path()); + m_applicationList.remove(application); + } } void ApplicationManager1Service::removeAllApplication() @@ -221,80 +223,48 @@ QDBusObjectPath ApplicationManager1Service::Launch(const QString &id, return value->Launch(actions, fields, options); } -void ApplicationManager1Service::UpdateApplicationInfo(const QStringList &app_id) +void ApplicationManager1Service::updateApplication(const QSharedPointer &destApp, + const DesktopFile &desktopFile) noexcept { - auto XDGDataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts); - std::for_each(XDGDataDirs.begin(), XDGDataDirs.end(), [](QString &str) { - if (!str.endsWith(QDir::separator())) { - str.append(QDir::separator()); - } - str.append("applications"); - }); - - for (auto id : app_id) { - auto destApp = std::find_if(m_applicationList.begin(), - m_applicationList.end(), - [&id](const QSharedPointer &value) { return value->id() == id; }); - - if (destApp == m_applicationList.end()) { // new app - qInfo() << "add a new application:" << id; - do { - for (const auto &suffix : XDGDataDirs) { - QFileInfo info{suffix + id}; - if (info.exists()) { - ParseError err; - auto file = DesktopFile::searchDesktopFile(info.absoluteFilePath(), err); - - if (!file.has_value()) { - continue; - } - - if (!addApplication(std::move(file).value())) { - id.clear(); - break; - } - } - } - - if (id.isEmpty()) { - break; - } - - auto hyphenIndex = id.indexOf('-'); - if (hyphenIndex == -1) { - break; - } - - id[hyphenIndex] = QDir::separator(); - } while (true); - } else { // remove or update - if (!(*destApp)->m_isPersistence) [[unlikely]] { - continue; - } - - auto filePath = (*destApp)->m_desktopSource.m_file.filePath(); - if (QFileInfo::exists(filePath)) { // update - qInfo() << "update application:" << id; - struct stat buf; - if (auto ret = stat(filePath.toLatin1().data(), &buf); ret == -1) { - qWarning() << "get file" << filePath << "state failed:" << std::strerror(errno) << ", skip..."; - continue; - } - - if ((*destApp)->m_desktopSource.m_file.modified( - static_cast(buf.st_mtim.tv_sec * 1e9 + buf.st_mtim.tv_nsec))) { - auto newEntry = new DesktopEntry{}; - auto err = newEntry->parse((*destApp)->m_desktopSource.m_file); - if (err != ParseError::NoError and err != ParseError::EntryKeyInvalid) { - qWarning() << "update desktop file failed:" << err << ", content wouldn't change."; - continue; - } - (*destApp)->m_entry.reset(newEntry); - } - } else { // remove - qInfo() << "remove application:" << id; - removeOneApplication((*destApp)->m_applicationPath); - } + struct stat buf; + const auto *filePath = desktopFile.filePath().toLocal8Bit().data(); + if (auto ret = stat(filePath, &buf); ret == -1) { + qWarning() << "get file" << filePath << "state failed:" << std::strerror(errno); + return; + } + + constexpr std::size_t secToNano = 1e9; + + if (destApp->m_desktopSource.m_file.modified(buf.st_mtim.tv_sec * secToNano + buf.st_mtim.tv_nsec)) { + auto newEntry = new DesktopEntry{}; + auto err = newEntry->parse(destApp->m_desktopSource.m_file); + if (err != DesktopErrorCode::NoError and err != DesktopErrorCode::EntryKeyInvalid) { + qWarning() << "update desktop file failed:" << err << ", content wouldn't change."; + return; } + destApp->m_entry.reset(newEntry); + } +} + +void ApplicationManager1Service::UpdateApplicationInfo(const QStringList &appIdList) +{ + for (const auto &appId : appIdList) { + DesktopErrorCode err{DesktopErrorCode::NotFound}; + auto file = DesktopFile::searchDesktopFileById(appId, err); + auto destApp = std::find_if(m_applicationList.cbegin(), + m_applicationList.cend(), + [&appId](const QSharedPointer &app) { return appId == app->id(); }); + + if (err == DesktopErrorCode::NotFound) { + removeOneApplication(destApp.key()); + continue; + } + + if (destApp != m_applicationList.cend()) { + updateApplication(destApp.value(), file.value()); + continue; + } + + addApplication(std::move(file).value()); } } diff --git a/src/dbus/applicationmanager1service.h b/src/dbus/applicationmanager1service.h index dd35874..929a344 100644 --- a/src/dbus/applicationmanager1service.h +++ b/src/dbus/applicationmanager1service.h @@ -48,13 +48,15 @@ public: void removeOneApplication(const QDBusObjectPath &application); void removeAllApplication(); + void updateApplication(const QSharedPointer &deskApp, const DesktopFile &desktopFile) noexcept; + JobManager1Service &jobManager() noexcept { return *m_jobManager; } public Q_SLOTS: [[nodiscard]] QDBusObjectPath Application(const QString &id) const; QString Identify(const QDBusUnixFileDescriptor &pidfd, QDBusObjectPath &application, QDBusObjectPath &application_instance); QDBusObjectPath Launch(const QString &id, const QString &action, const QStringList &fields, const QVariantMap &options); - void UpdateApplicationInfo(const QStringList &app_id); + void UpdateApplicationInfo(const QStringList &appIdList); private: std::unique_ptr m_identifier; diff --git a/src/dbus/applicationservice.h b/src/dbus/applicationservice.h index b9a80a6..9a759a7 100644 --- a/src/dbus/applicationservice.h +++ b/src/dbus/applicationservice.h @@ -30,7 +30,7 @@ public: ApplicationService &operator=(const ApplicationService &) = delete; ApplicationService &operator=(ApplicationService &&) = delete; - Q_PROPERTY(QStringList Actions READ actions CONSTANT) + Q_PROPERTY(QStringList Actions READ actions) [[nodiscard]] QStringList actions() const noexcept; Q_PROPERTY(QString ID READ id CONSTANT) @@ -89,8 +89,8 @@ private: sourceStream.setString(&m_desktopSource.m_temp, QTextStream::ReadOnly | QTextStream::Text); } m_entry.reset(new DesktopEntry()); - if (auto error = m_entry->parse(sourceStream); error != ParseError::NoError) { - if (error != ParseError::EntryKeyInvalid) { + if (auto error = m_entry->parse(sourceStream); error != DesktopErrorCode::NoError) { + if (error != DesktopErrorCode::EntryKeyInvalid) { m_entry.reset(nullptr); return; } diff --git a/src/desktopentry.cpp b/src/desktopentry.cpp index 0763d2b..c747af8 100644 --- a/src/desktopentry.cpp +++ b/src/desktopentry.cpp @@ -23,10 +23,10 @@ auto DesktopEntry::parserGroupHeader(const QString &str) noexcept return it; } -ParseError DesktopEntry::parseEntry(const QString &str, decltype(m_entryMap)::iterator ¤tGroup) noexcept +DesktopErrorCode DesktopEntry::parseEntry(const QString &str, decltype(m_entryMap)::iterator ¤tGroup) noexcept { if (str.startsWith("#")) { - return ParseError::NoError; + return DesktopErrorCode::NoError; } auto splitCharIndex = str.indexOf(']'); @@ -57,7 +57,7 @@ ParseError DesktopEntry::parseEntry(const QString &str, decltype(m_entryMap)::it auto matcher = re.match(keyStr); if (!matcher.hasMatch()) { qWarning() << "invalid key: " << keyStr; - return ParseError::EntryKeyInvalid; + return DesktopErrorCode::EntryKeyInvalid; } key = matcher.captured("MainKey"); @@ -69,110 +69,122 @@ ParseError DesktopEntry::parseEntry(const QString &str, decltype(m_entryMap)::it auto cur = currentGroup->find(key); if (cur == currentGroup->end()) { currentGroup->insert(keyStr, {{valueKey, valueStr}}); - return ParseError::NoError; + return DesktopErrorCode::NoError; } auto value = cur->find(valueKey); if (value == cur->end()) { cur->insert(valueKey, valueStr); - return ParseError::NoError; + return DesktopErrorCode::NoError; } qWarning() << "duplicated postfix and this line will be aborted, maybe format is invalid.\n" << "exist: " << value.key() << "[" << value.value() << "]" << "current: " << str; - return ParseError::NoError; + return DesktopErrorCode::NoError; } -std::optional DesktopFile::searchDesktopFile(const QString &desktopFile, ParseError &err) noexcept +std::optional DesktopFile::searchDesktopFileByPath(const QString &desktopFile, DesktopErrorCode &err) noexcept { - if (auto tmp = desktopFile.split("."); tmp.last() != "desktop") { - qWarning() << "file isn't a desktop file"; - err = ParseError::MismatchedFile; + constexpr decltype(auto) desktopPostfix = ".desktop"; + + if (!desktopFile.endsWith(desktopPostfix)) { + qWarning() << "file isn't a desktop file:" << desktopFile; + err = DesktopErrorCode::MismatchedFile; return std::nullopt; } - QString path; + QFileInfo fileinfo{desktopFile}; + if (!fileinfo.isAbsolute() or !fileinfo.exists()) { + qWarning() << "desktop file not found."; + err = DesktopErrorCode::NotFound; + return std::nullopt; + } + + QString path{desktopFile}; QString id; - QFileInfo Fileinfo{desktopFile}; - if (Fileinfo.isAbsolute() and Fileinfo.exists()) { - path = desktopFile; - } else { - auto XDGDataDirs = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts); - std::for_each(XDGDataDirs.begin(), XDGDataDirs.end(), [](QString &str) { - str = QDir::cleanPath(str) + QDir::separator() + "applications"; - }); - auto fileName = Fileinfo.fileName(); + const auto &XDGDataDirs = getXDGDataDirs(); + auto idGen = std::any_of(XDGDataDirs.cbegin(), XDGDataDirs.cend(), [&desktopFile](const QString &suffixPath) { + return desktopFile.startsWith(suffixPath); + }); - applyIteratively(QList{XDGDataDirs.begin(), XDGDataDirs.end()}, [&fileName, &path](const QFileInfo &file) -> bool { - if (file.fileName() == fileName) { - path = file.absoluteFilePath(); - return true; - } - return false; - }); - } - - if (path.isEmpty()) { - qWarning() << "desktop file not found."; - err = ParseError::NotFound; - return std::nullopt; - } - auto tmp = path.chopped(8); // remove ".desktop" - auto components = tmp.split(QDir::separator()).toList(); - auto it = std::find(components.cbegin(), components.cend(), "applications"); - if (it == components.cend()) { - qWarning() << "custom location detected, Id wouldn't be generated."; - } else { + if (idGen) { + auto tmp = path.chopped(sizeof(desktopPostfix) - 1); + auto components = tmp.split(QDir::separator()).toList(); + auto it = std::find(components.cbegin(), components.cend(), "applications"); QString FileId; ++it; - while (it != components.cend()) + while (it != components.cend()) { FileId += (*(it++) + "-"); + } id = FileId.chopped(1); // remove extra "-"" } struct stat buf; if (auto ret = stat(path.toLatin1().data(), &buf); ret == -1) { - err = ParseError::OpenFailed; + err = DesktopErrorCode::OpenFailed; qWarning() << "get file" << path << "state failed:" << std::strerror(errno); return std::nullopt; } - err = ParseError::NoError; + err = DesktopErrorCode::NoError; constexpr std::size_t nanoToSec = 1e9; return DesktopFile{std::move(path), std::move(id), buf.st_mtim.tv_sec * nanoToSec + buf.st_mtim.tv_nsec}; } +std::optional DesktopFile::searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept +{ + auto XDGDataDirs = getXDGDataDirs(); + + for (const auto &dir : XDGDataDirs) { + auto app = QFileInfo{dir + QDir::separator() + appId}; + while (!app.exists()) { + auto filePath = app.absoluteFilePath(); + auto hyphenIndex = filePath.indexOf('-'); + if (hyphenIndex == -1) { + break; + } + filePath.replace(hyphenIndex, 1, QDir::separator()); + app.setFile(filePath); + } + + if (app.exists()) { + return searchDesktopFileByPath(app.absoluteFilePath(), err); + } + } + return std::nullopt; +} + bool DesktopFile::modified(std::size_t time) const noexcept { return time != m_mtime; } -ParseError DesktopEntry::parse(const DesktopFile &desktopFile) noexcept +DesktopErrorCode DesktopEntry::parse(const DesktopFile &appId) noexcept { - auto file = QFile(desktopFile.filePath()); + auto file = QFile(appId.filePath()); if (!file.open(QFile::ExistingOnly | QFile::ReadOnly | QFile::Text)) { - qWarning() << desktopFile.filePath() << "can't open."; - return ParseError::OpenFailed; + qWarning() << appId.filePath() << "can't open."; + return DesktopErrorCode::OpenFailed; } QTextStream in{&file}; return parse(in); } -ParseError DesktopEntry::parse(QTextStream &stream) noexcept +DesktopErrorCode DesktopEntry::parse(QTextStream &stream) noexcept { if (stream.atEnd()) { - return ParseError::OpenFailed; + return DesktopErrorCode::OpenFailed; } stream.setEncoding(QStringConverter::Utf8); decltype(m_entryMap)::iterator currentGroup; - ParseError err{ParseError::NoError}; + DesktopErrorCode err{DesktopErrorCode::NoError}; while (!stream.atEnd()) { auto line = stream.readLine().trimmed(); @@ -182,13 +194,13 @@ ParseError DesktopEntry::parse(QTextStream &stream) noexcept if (line.startsWith("[")) { if (!line.endsWith("]")) { - return ParseError::GroupHeaderInvalid; + return DesktopErrorCode::GroupHeaderInvalid; } currentGroup = parserGroupHeader(line); continue; } - if (auto error = parseEntry(line, currentGroup); error != ParseError::NoError) { + if (auto error = parseEntry(line, currentGroup); error != DesktopErrorCode::NoError) { err = error; qWarning() << "an error occurred,this line will be skipped:" << line; } @@ -329,36 +341,36 @@ QDebug operator<<(QDebug debug, const DesktopEntry::Value &v) return debug; } -QDebug operator<<(QDebug debug, const ParseError &v) +QDebug operator<<(QDebug debug, const DesktopErrorCode &v) { QDebugStateSaver saver{debug}; QString errMsg; switch (v) { - case ParseError::NoError: { + case DesktopErrorCode::NoError: { errMsg = "no error."; break; } - case ParseError::NotFound: { + case DesktopErrorCode::NotFound: { errMsg = "file not found."; break; } - case ParseError::MismatchedFile: { + case DesktopErrorCode::MismatchedFile: { errMsg = "file type is mismatched."; break; } - case ParseError::InvalidLocation: { + case DesktopErrorCode::InvalidLocation: { errMsg = "file location is invalid, please check $XDG_DATA_DIRS."; break; } - case ParseError::OpenFailed: { + case DesktopErrorCode::OpenFailed: { errMsg = "couldn't open the file."; break; } - case ParseError::GroupHeaderInvalid: { + case DesktopErrorCode::GroupHeaderInvalid: { errMsg = "groupHead syntax is invalid."; break; } - case ParseError::EntryKeyInvalid: { + case DesktopErrorCode::EntryKeyInvalid: { errMsg = "key syntax is invalid."; break; } diff --git a/src/desktopentry.h b/src/desktopentry.h index b065fb7..fb93ba9 100644 --- a/src/desktopentry.h +++ b/src/desktopentry.h @@ -12,7 +12,15 @@ constexpr static auto defaultKeyStr = "default"; -enum class ParseError { NoError, NotFound, MismatchedFile, InvalidLocation, OpenFailed, GroupHeaderInvalid, EntryKeyInvalid }; +enum class DesktopErrorCode { + NoError, + NotFound, + MismatchedFile, + InvalidLocation, + OpenFailed, + GroupHeaderInvalid, + EntryKeyInvalid +}; struct DesktopFile { @@ -25,7 +33,8 @@ struct DesktopFile [[nodiscard]] const QString &filePath() const { return m_filePath; } [[nodiscard]] const QString &desktopId() const { return m_desktopId; } - static std::optional searchDesktopFile(const QString &desktopFilePath, ParseError &err) noexcept; + static std::optional searchDesktopFileById(const QString &appId, DesktopErrorCode &err) noexcept; + static std::optional searchDesktopFileByPath(const QString &desktopFilePath, DesktopErrorCode &err) noexcept; [[nodiscard]] bool modified(std::size_t time) const noexcept; private: @@ -66,17 +75,17 @@ public: DesktopEntry &operator=(DesktopEntry &&) = default; ~DesktopEntry() = default; - [[nodiscard]] ParseError parse(const DesktopFile &file) noexcept; - [[nodiscard]] ParseError parse(QTextStream &stream) noexcept; + [[nodiscard]] DesktopErrorCode parse(const DesktopFile &file) noexcept; + [[nodiscard]] DesktopErrorCode parse(QTextStream &stream) noexcept; [[nodiscard]] std::optional> group(const QString &key) const noexcept; [[nodiscard]] std::optional value(const QString &key, const QString &valueKey) const noexcept; private: QMap> m_entryMap; auto parserGroupHeader(const QString &str) noexcept; - static ParseError parseEntry(const QString &str, decltype(m_entryMap)::iterator ¤tGroup) noexcept; + static DesktopErrorCode parseEntry(const QString &str, decltype(m_entryMap)::iterator ¤tGroup) noexcept; }; QDebug operator<<(QDebug debug, const DesktopEntry::Value &v); -QDebug operator<<(QDebug debug, const ParseError &v); +QDebug operator<<(QDebug debug, const DesktopErrorCode &v); diff --git a/src/global.h b/src/global.h index 963c329..0ad80c9 100644 --- a/src/global.h +++ b/src/global.h @@ -25,13 +25,13 @@ using IconMap = QMap> inline QString getApplicationLauncherBinary() { - static const QString bin = []() -> QString { + static const QString bin = []() { auto value = qgetenv("DEEPIN_APPLICATION_MANAGER_APP_LAUNCH_HELPER_BIN"); if (value.isEmpty()) { - return ApplicationLaunchHelperBinary; + return QString::fromLocal8Bit(ApplicationLaunchHelperBinary); } qWarning() << "Using app launch helper defined in environment variable DEEPIN_APPLICATION_MANAGER_APP_LAUNCH_HELPER_BIN."; - return value; + return QString::fromLocal8Bit(value); }(); return bin; } @@ -303,4 +303,29 @@ inline QString getRelativePathFromAppId(const QString &id) return path; } +inline QStringList getXDGDataDirs() +{ + const static auto XDGDataDirs = []() { + auto env = QString::fromLocal8Bit(qgetenv("XDG_DATA_DIRS")).split(':', Qt::SkipEmptyParts); + + if (env.isEmpty()) { + env.append(QString{qgetenv("HOME")} + QDir::separator() + ".local/share"); + env.append("/usr/local/share"); + env.append("/usr/share"); + qputenv("XDG_DATA_DIRS", env.join(':').toLocal8Bit()); + } + + std::for_each(env.begin(), env.end(), [](QString &str) { + if (!str.endsWith(QDir::separator())) { + str.append(QDir::separator()); + } + str.append("applications"); + }); + + return env; + }(); + + return XDGDataDirs; +} + #endif diff --git a/src/systemdsignaldispatcher.cpp b/src/systemdsignaldispatcher.cpp index cbbf5dd..db77e67 100644 --- a/src/systemdsignaldispatcher.cpp +++ b/src/systemdsignaldispatcher.cpp @@ -33,18 +33,20 @@ bool SystemdSignalDispatcher::connectToSignals() noexcept void SystemdSignalDispatcher::onUnitNew(QString unitName, QDBusObjectPath systemdUnitPath) { - if (!unitName.startsWith("app-")) { + constexpr decltype(auto) appPrefix = u8"app-"; + if (!unitName.startsWith(appPrefix)) { return; } - emit SystemdUnitNew(unitName.sliced(sizeof("app-") - 1), systemdUnitPath); + emit SystemdUnitNew(unitName.sliced(sizeof(appPrefix) - 1), systemdUnitPath); } void SystemdSignalDispatcher::onUnitRemoved(QString unitName, QDBusObjectPath systemdUnitPath) { - if (!unitName.startsWith("app-")) { + constexpr decltype(auto) appPrefix = u8"app-"; + if (!unitName.startsWith(appPrefix)) { return; } - emit SystemdUnitRemoved(unitName.sliced(sizeof("app-") - 1), systemdUnitPath); + emit SystemdUnitRemoved(unitName.sliced(sizeof(appPrefix) - 1), systemdUnitPath); } diff --git a/tests/ut_desktopentry.cpp b/tests/ut_desktopentry.cpp index ff3f697..583c8bd 100644 --- a/tests/ut_desktopentry.cpp +++ b/tests/ut_desktopentry.cpp @@ -17,8 +17,8 @@ public: { auto curDir = QDir::current(); QString path{curDir.absolutePath() + "/data/desktopExample.desktop"}; - ParseError err; - auto file = DesktopFile::searchDesktopFile(path, err); + DesktopErrorCode err; + auto file = DesktopFile::searchDesktopFileByPath(path, err); if (!file.has_value()) { qWarning() << "search " << path << "failed:" << err; return; @@ -54,7 +54,7 @@ TEST_F(TestDesktopEntry, prase) ASSERT_TRUE(in.open(QFile::ExistingOnly | QFile::ReadOnly | QFile::Text)); QTextStream fs{&in}; auto err = entry.parse(fs); - ASSERT_EQ(err, ParseError::NoError); + ASSERT_EQ(err, DesktopErrorCode::NoError); auto group = entry.group("Desktop Entry"); ASSERT_TRUE(group);