Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions api/dbus/org.desktopspec.MimeManager1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,11 @@
<arg name="applications_and_properties" type="a{oa{sa{sv}}}" direction="out"/>
<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="ObjectMap"/>
</method>
<signal name="MimeInfoReloaded">
<annotation
name="org.freedesktop.DBus.Description"
value="Signal emitted when the mime infos are reloaded."
/>
</signal>
</interface>
</node>
12 changes: 9 additions & 3 deletions src/dbus/applicationmanager1service.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include "applicationadaptor.h"
Expand Down Expand Up @@ -274,6 +274,13 @@ void ApplicationManager1Service::scanMimeInfos() noexcept
}
}

void ApplicationManager1Service::reloadMimeInfos() noexcept
{
m_mimeManager->reset();
scanMimeInfos();
emit m_mimeManager->MimeInfoReloaded();
}

void ApplicationManager1Service::scanApplications() noexcept
{
const auto &desktopFileDirs = getDesktopFileDirs();
Expand Down Expand Up @@ -639,8 +646,7 @@ void ApplicationManager1Service::doReloadApplications()
removeOneApplication(appId);
}

m_mimeManager->reset();
scanMimeInfos();
reloadMimeInfos();
updateAutostartStatus();
}

Expand Down
3 changes: 2 additions & 1 deletion src/dbus/applicationmanager1service.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

Expand Down Expand Up @@ -41,6 +41,7 @@ class ApplicationManager1Service final : public QObject, protected QDBusContext
[[nodiscard]] QList<QDBusObjectPath> list() const;

void initService(QDBusConnection &connection) noexcept;
void reloadMimeInfos() noexcept;
QSharedPointer<ApplicationService> addApplication(DesktopFile desktopFileSource) noexcept;
void removeOneApplication(const QString &appId) noexcept;
void removeAllApplication() noexcept;
Expand Down
49 changes: 48 additions & 1 deletion src/dbus/mimemanager1service.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
#include <QProcess>
Expand All @@ -15,6 +15,21 @@ MimeManager1Service::MimeManager1Service(ApplicationManager1Service *parent)
if (adaptor == nullptr or !registerObjectToDBus(this, DDEApplicationManager1MimeManager1ObjectPath, MimeManager1Interface)) {
std::terminate();
}

// 监控用户配置目录下的 mimeapps.list 文件
connect(&m_mimeAppsWatcher, &QFileSystemWatcher::fileChanged, this, &MimeManager1Service::onMimeAppsFileChanged);

// 添加用户配置目录下的 mimeapps.list 文件到监控
QString userMimeAppsFile = getXDGConfigHome() + "/mimeapps.list";
if (QFileInfo::exists(userMimeAppsFile)) {
m_mimeAppsWatcher.addPath(userMimeAppsFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

必须要存在么?这个文件会不会第一次在am起来后才创建的?
另外,如果文件不存在,要不要提示个信息,那个定时器也不用刷了吧,

} else {
qWarning() << "User mimeapps.list file does not exist:" << userMimeAppsFile;
}

m_mimeAppsDebounceTimer.setSingleShot(true);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider replacing the shared m_internalWriteInProgress flag with scoped QSignalBlocker usage and an optional helper for debounce to localize state and simplify control flow.

You can simplify the control flow and remove the fragile m_internalWriteInProgress state entirely by scoping the suppression of fileChanged using QSignalBlocker on the watcher. That keeps the “ignore internal writes” behaviour but makes it local and non‑leaky.

1. Replace m_internalWriteInProgress with a scoped signal blocker

#include <QSignalBlocker>
void MimeManager1Service::setDefaultApplication(const ObjectMap &defaultApps) noexcept
{
    auto &app = m_infos.front().appsList();
    auto userConfig = std::find_if(app.begin(), app.end(), [](const MimeApps &config) {
        return !config.isDesktopSpecific();
    });

    if (userConfig == app.end()) {
        qWarning() << "couldn't find user mimeApps";
        safe_sendErrorReply(QDBusError::InternalError);
        return;
    }

    // Scoped suppression of QFileSystemWatcher notifications
    QSignalBlocker watcherBlocker(&m_mimeAppsWatcher);

    for (auto it = defaultApps.constKeyValueBegin(); it != defaultApps.constKeyValueEnd(); ++it) {
        userConfig->setDefaultApplication(it->first, it->second);
    }

    if (!userConfig->writeToFile()) {
        safe_sendErrorReply(QDBusError::Failed,
                            "set default app failed, these config will be reset after re-login.");
        // no extra state to reset; watcherBlocker destructor re‑enables signals
        return;
    }
}
void MimeManager1Service::unsetDefaultApplication(const QStringList &mimeTypes) noexcept
{
    auto &app = m_infos.front().appsList();
    auto userConfig = std::find_if(app.begin(), app.end(), [](const MimeApps &config) {
        return !config.isDesktopSpecific();
    });

    if (userConfig == app.end()) {
        qWarning() << "couldn't find user mimeApps";
        safe_sendErrorReply(QDBusError::InternalError);
        return;
    }

    QSignalBlocker watcherBlocker(&m_mimeAppsWatcher);

    for (const auto &mime : mimeTypes) {
        userConfig->unsetDefaultApplication(mime);
    }

    if (!userConfig->writeToFile()) {
        safe_sendErrorReply(QDBusError::Failed,
                            "unset default app failed, these config will be reset after re-login.");
        return;
    }
}

Then onMimeAppsFileChanged no longer needs to know about internal write state:

void MimeManager1Service::onMimeAppsFileChanged(const QString &path)
{
    if (!m_mimeAppsWatcher.files().contains(path) && QFileInfo::exists(path)) {
        m_mimeAppsWatcher.addPath(path);
    }

    m_mimeAppsDebounceTimer.start();
}

This:

  • Keeps the feature (watching and reloading mimeapps.list).
  • Ensures internal writes never trigger reloads.
  • Removes the cross‑method lifecycle of m_internalWriteInProgress and its hidden coupling.

2. Optional: isolate debounce logic into a helper

To reduce the cognitive load further, you can hide the timer in a small helper and keep onMimeAppsFileChanged high‑level:

void MimeManager1Service::scheduleMimeAppsReload()
{
    m_mimeAppsDebounceTimer.start();
}

void MimeManager1Service::onMimeAppsFileChanged(const QString &path)
{
    if (!m_mimeAppsWatcher.files().contains(path) && QFileInfo::exists(path)) {
        m_mimeAppsWatcher.addPath(path);
    }

    scheduleMimeAppsReload();
}

This keeps all existing behaviour but makes the state management explicit, scoped, and easier to reason about.

m_mimeAppsDebounceTimer.setInterval(50);
connect(&m_mimeAppsDebounceTimer, &QTimer::timeout, this, &MimeManager1Service::handleMimeAppsFileDebounced);
}

MimeManager1Service::~MimeManager1Service() = default;
Expand Down Expand Up @@ -106,12 +121,15 @@ void MimeManager1Service::setDefaultApplication(const QStringMap &defaultApps) n
return;
}

m_internalWriteInProgress = true;
for (auto it = defaultApps.constKeyValueBegin(); it != defaultApps.constKeyValueEnd(); ++it) {
userConfig->setDefaultApplication(it->first, it->second);
}

if (!userConfig->writeToFile()) {
safe_sendErrorReply(QDBusError::Failed, "set default app failed, these config will be reset after re-login.");
m_internalWriteInProgress = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

有定时器,是不是不需要这个m_internalWriteInProgress这个flag了,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

m_internalWriteInProgress这个是不是在写完就应该重置为false?而不是非要失败才重置为false,

return;
}
Comment on lines +124 to 133
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal write flag is not reset after writeToFile succeeds. While the design expects the file system watcher to consume this flag in onMimeAppsFileChanged, this creates a reliability issue. If the file change event doesn't fire for any reason (e.g., file system watcher malfunction, write doesn't trigger change event, or timing issues), the flag will remain true indefinitely, blocking all future external change detection. Consider resetting the flag using a timer-based fallback or RAII pattern to ensure it's always cleared, even if the file change event doesn't arrive.

Copilot uses AI. Check for mistakes.
}

Expand All @@ -126,12 +144,15 @@ void MimeManager1Service::unsetDefaultApplication(const QStringList &mimeTypes)
return;
}

m_internalWriteInProgress = true;
for (const auto &mime : mimeTypes) {
userConfig->unsetDefaultApplication(mime);
}

if (!userConfig->writeToFile()) {
safe_sendErrorReply(QDBusError::Failed, "unset default app failed, these config will be reset after re-login.");
m_internalWriteInProgress = false;
return;
}
Comment on lines +147 to 156
Copy link

Copilot AI Jan 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The internal write flag is not reset after writeToFile succeeds. While the design expects the file system watcher to consume this flag in onMimeAppsFileChanged, this creates a reliability issue. If the file change event doesn't fire for any reason (e.g., file system watcher malfunction, write doesn't trigger change event, or timing issues), the flag will remain true indefinitely, blocking all future external change detection. Consider resetting the flag using a timer-based fallback or RAII pattern to ensure it's always cleared, even if the file change event doesn't arrive.

Copilot uses AI. Check for mistakes.
}

Expand All @@ -156,3 +177,29 @@ void MimeManager1Service::updateMimeCache(QString dir) noexcept
}

}

void MimeManager1Service::onMimeAppsFileChanged(const QString &path)
{
if (!m_mimeAppsWatcher.files().contains(path) && QFileInfo::exists(path)) {
m_mimeAppsWatcher.addPath(path);
Comment on lines +183 to +184
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): File-based watching may miss changes when mimeapps.list is deleted and later recreated

QFileSystemWatcher will stop watching the path once mimeapps.list is removed, but your handler only re-adds it if QFileInfo::exists(path) is true at notification time. If the file is deleted and later recreated, you may never reattach the watcher and will miss subsequent changes. Consider also watching the parent directory and re-adding the file when it is (re)created, or otherwise handling the case where the file doesn’t exist at the moment of the change signal.

Suggested implementation:

void MimeManager1Service::onMimeAppsFileChanged(const QString &path)
{
    const QString userMimeAppsFile = getXDGConfigHome() + "/mimeapps.list";

    // Ensure we (re)attach a file watcher whenever mimeapps.list exists but is not watched.
    // QFileSystemWatcher stops watching a file once it is removed, so this covers the
    // "delete + recreate" case when combined with a directory watcher on the parent dir.
    if (!m_mimeAppsWatcher.files().contains(userMimeAppsFile) && QFileInfo::exists(userMimeAppsFile)) {
        m_mimeAppsWatcher.addPath(userMimeAppsFile);
    }

    // Debounce downstream handling of mimeapps.list changes
    m_mimeAppsDebounceTimer.setSingleShot(true);
    if (!m_mimeAppsDebounceTimer.isActive()) {
        m_mimeAppsDebounceTimer.start();
    }
}

To fully implement the suggestion and cover the delete + recreate scenario, you should also:

  1. Watch the parent directory (likely in the MimeManager1Service constructor or initialization code):

    • Determine the XDG config directory (same getXDGConfigHome() you use for the file).
    • Add it to the watcher if not already present:
      const QString userConfigDir = getXDGConfigHome();
      if (!m_mimeAppsWatcher.directories().contains(userConfigDir) && QFileInfo::exists(userConfigDir)) {
          m_mimeAppsWatcher.addPath(userConfigDir);
      }
    • Connect the directoryChanged signal to a new slot:
      connect(&m_mimeAppsWatcher, &QFileSystemWatcher::directoryChanged,
              this, &MimeManager1Service::onMimeAppsDirectoryChanged);
  2. Implement the onMimeAppsDirectoryChanged slot to re-attach the file watcher when mimeapps.list is (re)created:

    void MimeManager1Service::onMimeAppsDirectoryChanged(const QString &dirPath)
    {
        const QString userConfigDir = getXDGConfigHome();
        if (dirPath != userConfigDir) {
            return;
        }
    
        const QString userMimeAppsFile = userConfigDir + "/mimeapps.list";
    
        // If the file now exists but isn't being watched, re-add it.
        if (QFileInfo::exists(userMimeAppsFile) &&
            !m_mimeAppsWatcher.files().contains(userMimeAppsFile)) {
            m_mimeAppsWatcher.addPath(userMimeAppsFile);
    
            // Optionally treat this as a "file changed" event so existing logic runs:
            onMimeAppsFileChanged(userMimeAppsFile);
        }
    }
  3. Ensure the declaration of onMimeAppsDirectoryChanged is added to the corresponding header (mimemanager1service.h), e.g.:

    private slots:
        void onMimeAppsFileChanged(const QString &path);
        void onMimeAppsDirectoryChanged(const QString &dirPath);

With these changes, the parent directory is always watched, and the file watcher is automatically reattached when mimeapps.list is deleted and later recreated.

}

// 如果是内部写入导致的文件变化,忽略
if (m_internalWriteInProgress) {
m_internalWriteInProgress = false;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

忽略了为什么还要重置它呀,

return;
}

m_mimeAppsDebounceTimer.start();
}

void MimeManager1Service::handleMimeAppsFileDebounced()
{
auto *parentService = qobject_cast<ApplicationManager1Service *>(parent());
if (!parentService) {
return;
}

qInfo() << "Reloading MIME info due to external configuration change.";
parentService->reloadMimeInfos();
}
16 changes: 15 additions & 1 deletion src/dbus/mimemanager1service.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
// SPDX-FileCopyrightText: 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later

Expand All @@ -8,6 +8,9 @@
#include <QObject>
#include <QDBusContext>
#include <QDBusObjectPath>
#include <QFileSystemWatcher>
#include <QTimer>

#include "global.h"
#include "applicationmimeinfo.h"

Expand All @@ -32,9 +35,20 @@ public Q_SLOTS:
void setDefaultApplication(const QStringMap &defaultApps) noexcept;
void unsetDefaultApplication(const QStringList &mimeTypes) noexcept;

Q_SIGNALS:
void MimeInfoReloaded();

private Q_SLOTS:
void onMimeAppsFileChanged(const QString &path);
void handleMimeAppsFileDebounced();

private:
QMimeDatabase m_database;
std::vector<MimeInfo> m_infos;
QFileSystemWatcher m_mimeAppsWatcher;
// 内部写入标志,用于避免触发外部修改的处理
bool m_internalWriteInProgress{false};
QTimer m_mimeAppsDebounceTimer;
};

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ class MimeManager1Adaptor: public QDBusAbstractAdaptor
" <arg direction=\"out\" type=\"a{oa{sa{sv}}}\" name=\"applications_and_properties\"/>\n"
" <annotation value=\"ObjectMap\" name=\"org.qtproject.QtDBus.QtTypeName.Out0\"/>\n"
" </method>\n"
" <signal name=\"MimeInfoReloaded\">\n"
" <annotation value=\"Signal emitted when the mime infos are reloaded.\" name=\"org.freedesktop.DBus.Description\"/>\n"
" </signal>\n"
" </interface>\n"
"")
public:
Expand All @@ -64,6 +67,7 @@ public Q_SLOTS: // METHODS
void setDefaultApplication(const QStringMap &defaultApps);
void unsetDefaultApplication(const QStringList &mimeTypes);
Q_SIGNALS: // SIGNALS
void MimeInfoReloaded();
};

#endif
Loading