diff -Nru dtkcore-5.5.23/debian/changelog dtkcore-5.5.32/debian/changelog --- dtkcore-5.5.23/debian/changelog 2022-03-17 13:09:03.000000000 +0000 +++ dtkcore-5.5.32/debian/changelog 2022-07-29 18:59:34.000000000 +0000 @@ -1,15 +1,47 @@ -dtkcore (5.5.23-1ubuntu2) jammy; urgency=medium +dtkcore (5.5.32-1ubuntu2) jammy; urgency=medium - * No-change rebuild against Qt 5.15.3. + * Upload to ubuntudde PPA. - -- Dmitry Shachnev Thu, 17 Mar 2022 16:09:03 +0300 + -- Arun Kumar Pariyar Sat, 30 Jul 2022 00:44:34 +0545 -dtkcore (5.5.23-1ubuntu1) jammy; urgency=medium +dtkcore (5.5.32-1ubuntu1) kinetic; urgency=medium * Merge from Debian unstable. Remaining changes: - - Make new build time tests not fatal for now. + - Make new build time tests non-fatal. - -- Rik Mills Thu, 24 Feb 2022 17:38:32 +0000 + -- Rik Mills Wed, 13 Jul 2022 11:35:55 +0100 + +dtkcore (5.5.32-1) unstable; urgency=medium + + * New upstream version. + + -- Boyuan Yang Wed, 15 Jun 2022 13:26:45 -0400 + +dtkcore (5.5.31-1) unstable; urgency=medium + + * New upstream version. + * debian/libdtkcore5.shlibs: update version to 5.5.31. + + -- Boyuan Yang Tue, 24 May 2022 23:35:19 -0400 + +dtkcore (5.5.30-1ubuntu2) kinetic; urgency=medium + + * No-change rebuild against Qt 5.15.4. + + -- Dmitry Shachnev Mon, 16 May 2022 13:37:47 +0300 + +dtkcore (5.5.30-1ubuntu1) kinetic; urgency=medium + + * Again make new build time tests non fatal. + + -- Rik Mills Sun, 15 May 2022 20:33:46 +0100 + +dtkcore (5.5.30-1) unstable; urgency=medium + + * New upstream version. + * debian/libdtkcore5.shlibs: update version to 5.5.30. + + -- Boyuan Yang Tue, 12 Apr 2022 16:10:36 -0400 dtkcore (5.5.23-1) unstable; urgency=medium @@ -19,12 +51,6 @@ -- Clay Stan Mon, 21 Feb 2022 15:21:41 +0800 -dtkcore (5.5.17.1-1ubuntu1) jammy; urgency=medium - - * Make new build time tests not fatal for now. - - -- Rik Mills Fri, 24 Dec 2021 11:45:44 +0000 - dtkcore (5.5.17.1-1) unstable; urgency=medium * Upload to unstable. diff -Nru dtkcore-5.5.23/debian/control dtkcore-5.5.32/debian/control --- dtkcore-5.5.23/debian/control 2022-02-21 06:03:52.000000000 +0000 +++ dtkcore-5.5.32/debian/control 2022-07-13 10:34:50.000000000 +0000 @@ -1,7 +1,8 @@ Source: dtkcore Section: libs Priority: optional -Maintainer: Debian Deepin Packaging Team +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian Deepin Packaging Team Uploaders: Boyuan Yang , Yanhao Mo , @@ -19,7 +20,7 @@ libgtest-dev, libdtkcommon-dev, Rules-Requires-Root: no -Standards-Version: 4.6.0 +Standards-Version: 4.6.1 Homepage: https://github.com/linuxdeepin/dtkcore Vcs-Git: https://salsa.debian.org/pkg-deepin-team/dtkcore.git Vcs-Browser: https://salsa.debian.org/pkg-deepin-team/dtkcore diff -Nru dtkcore-5.5.23/debian/libdtkcore5.shlibs dtkcore-5.5.32/debian/libdtkcore5.shlibs --- dtkcore-5.5.23/debian/libdtkcore5.shlibs 2022-02-21 07:21:41.000000000 +0000 +++ dtkcore-5.5.32/debian/libdtkcore5.shlibs 2022-06-15 17:26:42.000000000 +0000 @@ -1 +1 @@ -libdtkcore 5 libdtkcore5 (>= 5.5.23~) +libdtkcore 5 libdtkcore5 (>= 5.5.32~) diff -Nru dtkcore-5.5.23/.github/workflows/backup-to-gitlab.yml dtkcore-5.5.32/.github/workflows/backup-to-gitlab.yml --- dtkcore-5.5.23/.github/workflows/backup-to-gitlab.yml 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/.github/workflows/backup-to-gitlab.yml 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,52 @@ +name: backup to gitlab +on: [push] + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + backup-to-gitlab: + if: github.repository_owner == 'linuxdeepin' + name: backup-to-gitlab + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + repository: "linuxdeepin/jenkins-bridge-client" + path: jenkins-bridge-client + + - name: Install Client + run: | + cd $GITHUB_WORKSPACE/jenkins-bridge-client + go build . + sudo install -Dvm755 jenkins-bridge-client -t /usr/bin/ + - name: Trigger sync + id: generate-runid + run: | + echo "::set-output name=RUN_ID::$(jenkins-bridge-client -triggerSync -token '${{ secrets.BRIDGETOKEN }}')" + - name: Print log + run: | + jenkins-bridge-client -printlog -token "${{ secrets.BRIDGETOKEN }}" -runid "${{ steps.generate-runid.outputs.RUN_ID }}" + + backup-to-gitee: + if: github.repository_owner == 'linuxdeepin' + runs-on: ubuntu-latest + steps: + - name: create-repo + run: | + repo=${{ github.event.repository.name }} + homepage="https://github.com/linuxdeepin/${repo}" + description="mirror of ${homepage}" + # remove '.' prefix + repo=${repo#"."} + curl -X POST --header 'Content-Type: application/json;charset=UTF-8' 'https://gitee.com/api/v5/enterprises/linuxdeepin/repos' -d '{"private": 1,"access_token":"${{ secrets.GITEE_SYNC_TOKEN }}","name":"'"$repo"'","description":"'"$description"'","homepage":"'"$homepage"'","has_issues":"false","has_wiki":"false","can_comment":"false"}' || true + - name: push + run: | + git clone --bare https://github.com/linuxdeepin/${{ github.event.repository.name }}.git .git + repo=${{ github.event.repository.name }} + # remove '.' prefix + repo=${repo#"."} + git remote set-url origin https://myml:${{ secrets.GITEE_SYNC_TOKEN }}@gitee.com/linuxdeepin/${repo}.git + git push -f --all --prune origin + git push --tags origin diff -Nru dtkcore-5.5.23/.github/workflows/call-build-deb.yml dtkcore-5.5.32/.github/workflows/call-build-deb.yml --- dtkcore-5.5.23/.github/workflows/call-build-deb.yml 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/.github/workflows/call-build-deb.yml 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,15 @@ +name: Call build-deb +on: + pull_request_target: + paths-ignore: + - ".github/workflows/**" + +concurrency: + group: ${{ github.workflow }}-pull/${{ github.event.number }} + cancel-in-progress: true + +jobs: + check_job: + uses: linuxdeepin/.github/.github/workflows/build-deb.yml@master + secrets: + BridgeToken: ${{ secrets.BridgeToken }} diff -Nru dtkcore-5.5.23/.github/workflows/call-build-distribution.yml dtkcore-5.5.32/.github/workflows/call-build-distribution.yml --- dtkcore-5.5.23/.github/workflows/call-build-distribution.yml 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/.github/workflows/call-build-distribution.yml 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,17 @@ +name: Call build-distribution +on: + push: + paths-ignore: + - ".github/workflows/**" + pull_request_target: + paths-ignore: + - ".github/workflows/**" + +jobs: + check_job: + uses: linuxdeepin/.github/.github/workflows/build-distribution.yml@master + secrets: + BUILD_GPG_PRIVATE_KEY: ${{ secrets.BUILD_GPG_PRIVATE_KEY }} + BUILD_SSH_PRIVATE_KEY: ${{ secrets.BUILD_SSH_PRIVATE_KEY }} + WEBDAV_PASSWD: ${{ secrets.WEBDAV_PASSWD }} + WEBDAV_USER: ${{ secrets.WEBDAV_USER }} diff -Nru dtkcore-5.5.23/.github/workflows/call-chatOps.yml dtkcore-5.5.32/.github/workflows/call-chatOps.yml --- dtkcore-5.5.23/.github/workflows/call-chatOps.yml 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/.github/workflows/call-chatOps.yml 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,10 @@ +name: chatOps +on: + issue_comment: + types: [created] + +jobs: + chatopt: + uses: linuxdeepin/.github/.github/workflows/chatOps.yml@master + secrets: + APP_PRIVATE_KEY: ${{ secrets.APP_PRIVATE_KEY }} diff -Nru dtkcore-5.5.23/.github/workflows/call-commitlint.yml dtkcore-5.5.32/.github/workflows/call-commitlint.yml --- dtkcore-5.5.23/.github/workflows/call-commitlint.yml 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/.github/workflows/call-commitlint.yml 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,11 @@ +name: Call commitlint +on: + pull_request_target: + +concurrency: + group: ${{ github.workflow }}-pull/${{ github.event.number }} + cancel-in-progress: true + +jobs: + check_job: + uses: linuxdeepin/.github/.github/workflows/commitlint.yml@master diff -Nru dtkcore-5.5.23/.github/workflows/cppcheck.yml dtkcore-5.5.32/.github/workflows/cppcheck.yml --- dtkcore-5.5.23/.github/workflows/cppcheck.yml 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/.github/workflows/cppcheck.yml 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,26 @@ +name: cppcheck +on: + pull_request_target: + paths-ignore: + - ".github/workflows/**" + +concurrency: + group: ${{ github.workflow }}-pull/${{ github.event.number }} + cancel-in-progress: true + +jobs: + cppchceck: + name: cppcheck + runs-on: ubuntu-latest + steps: + - run: export + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + persist-credentials: false + - uses: linuxdeepin/action-cppcheck@main + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + repository: ${{ github.repository }} + pull_request_id: ${{ github.event.pull_request.number }} + allow_approve: false diff -Nru dtkcore-5.5.23/README.md dtkcore-5.5.32/README.md --- dtkcore-5.5.23/README.md 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/README.md 2022-05-25 07:33:48.000000000 +0000 @@ -8,7 +8,7 @@ ### Build dependencies -* Qt >= 5.6 +* Qt >= 5.10 ## Installation diff -Nru dtkcore-5.5.23/src/base/dsingleton.h dtkcore-5.5.32/src/base/dsingleton.h --- dtkcore-5.5.23/src/base/dsingleton.h 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/base/dsingleton.h 2022-05-25 07:33:48.000000000 +0000 @@ -58,17 +58,26 @@ class LIBDTKCORESHARED_EXPORT DSingleton { public: + QT_DEPRECATED_X("Use ref") static inline T *instance() { static T *_instance = new T; return _instance; } + static T& ref() + { + static T instance; + return instance; + } + + DSingleton(T&&) = delete; + DSingleton(const T&) = delete; + void operator= (const T&) = delete; + protected: - DSingleton(void) {} - ~DSingleton(void) {} - DSingleton(const DSingleton &) {} - DSingleton &operator= (const DSingleton &) {} + DSingleton() = default; + virtual ~DSingleton() = default; }; DCORE_END_NAMESPACE diff -Nru dtkcore-5.5.23/src/dbus/org.desktopspec.ConfigManager.Manager.xml dtkcore-5.5.32/src/dbus/org.desktopspec.ConfigManager.Manager.xml --- dtkcore-5.5.23/src/dbus/org.desktopspec.ConfigManager.Manager.xml 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/dbus/org.desktopspec.ConfigManager.Manager.xml 2022-05-25 07:33:48.000000000 +0000 @@ -9,6 +9,9 @@ + + + diff -Nru dtkcore-5.5.23/src/dconfig.cpp dtkcore-5.5.32/src/dconfig.cpp --- dtkcore-5.5.23/src/dconfig.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/dconfig.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -41,12 +41,12 @@ Q_DECLARE_LOGGING_CATEGORY(cfLog) inline static QString getAppId() { - // TODO: 应该使用更可靠的接口获取 appid + // TODO: 应该使用更可靠的接口获取 appId return QCoreApplication::applicationName(); } /*! - \class DTK::Core::DConfigBackend + \class Dtk::Core::DConfigBackend \inmodule dtkcore \brief 配置后端的抽象接口. @@ -55,43 +55,49 @@ */ /*! - \fn bool load(const QString &/appid/) = 0 + \fn bool DConfigBackend::load(const QString &) = 0 \brief 初始化后端 - \a appid 管理的配置信息key值,默认为应用程序名称 + \a appId 管理的配置信息key值,默认为应用程序名称 */ /*! - \fn bool isValid() const = 0 + \fn bool DConfigBackend::isValid() const = 0 \sa DConfig::isValid(). */ /*! - \fn QStringList keyList() const = 0 + \fn QStringList DConfigBackend::keyList() const = 0 \sa DConfig::keyList() */ /*! - \fn QVariant value(const QString &key, const QVariant &fallback = QVariant()) const = 0 + \fn QVariant DConfigBackend::value(const QString &key, const QVariant &fallback = QVariant()) const = 0 \sa DConfig::value() */ /*! - \fn void setValue(const QString &key, const QVariant &value) = 0 + \fn void DConfigBackend::setValue(const QString &key, const QVariant &value) = 0 \sa DConfig::setValue() */ /*! - \fn QString name() const = 0 + \fn void DConfigBackend::reset(const QString &key) - \breaf 后端配置的唯一标识 + \sa DConfig::reset() + */ + +/*! + \fn QString DConfigBackend::name() const = 0 + + \brief 后端配置的唯一标识 */ @@ -102,16 +108,33 @@ class Q_DECL_HIDDEN DConfigPrivate : public DObjectPrivate { public: - explicit DConfigPrivate(DConfig *qq) + explicit DConfigPrivate(DConfig *qq, + const QString &appId, + const QString &name, + const QString &subpath) : DObjectPrivate(qq) + , appId(appId.isEmpty() ? getAppId() : appId) + , name(name) + , subpath(subpath) { } virtual ~DConfigPrivate() override; + inline bool invalid() const + { + const bool valid = backend && backend->isValid(); + if (!valid) + qCWarning(cfLog, "DConfig is invalid of appid=%s name=%s, subpath=%s", + qPrintable(appId), qPrintable(name), qPrintable(subpath)); + + return !valid; + } + DConfigBackend *getOrCreateBackend(); DConfigBackend *createBackendByEnv(); + QString appId; QString name; QString subpath; QScopedPointer backend; @@ -137,12 +160,12 @@ return configFile && configFile->isValid(); } - virtual bool load(const QString &appid) override + virtual bool load(const QString &appId) override { if (configFile) return true; - configFile.reset(new DConfigFile(appid,owner->name, owner->subpath)); + configFile.reset(new DConfigFile(appId,owner->name, owner->subpath)); configCache.reset(configFile->createUserCache(getuid())); const QString &prefix = localPrefix(); @@ -168,6 +191,12 @@ } } + virtual void reset(const QString &key) override + { + const auto &originValue = configFile->meta()->value(key); + setValue(key, originValue); + } + virtual QString name() const override { return QString("FileBackend"); @@ -222,6 +251,17 @@ return QDBusConnection::systemBus().interface()->isServiceRegistered(DSG_CONFIG); } + static bool isServiceActivatable() + { + const QDBusReply activatableNames = QDBusConnection::systemBus().interface()-> + callWithArgumentList(QDBus::AutoDetect, + QLatin1String("ListActivatableNames"), + QList()); +// qInfo() << activatableNames.value() << activatableNames.value().contains(DSG_CONFIG); + + return activatableNames.value().contains(DSG_CONFIG); + } + virtual bool isValid() const override { return config && config->isValid(); @@ -265,11 +305,45 @@ return config->keyList(); } + static QVariant decodeQDBusArgument(const QVariant &v) + { + if (v.canConvert()) { + // we use QJsonValue to resolve all data type in DConfigInfo class, so it's type is equal QJsonValue::Type, + // now we parse Map and Array type to QVariant explicitly. + const QDBusArgument &complexType = v.value(); + switch (complexType.currentType()) { + case QDBusArgument::MapType: { + QVariantMap list; + complexType >> list; + QVariantMap res; + for (auto iter = list.begin(); iter != list.end(); iter++) { + res[iter.key()] = decodeQDBusArgument(iter.value()); + } + return res; + } + case QDBusArgument::ArrayType: { + QVariantList list; + complexType >> list; + QVariantList res; + res.reserve(list.size()); + for (const auto &item : qAsConst(list)) { + res << decodeQDBusArgument(item); + } + return res; + } + default: + qWarning("Can't parse the type, it maybe need user to do it, " + "QDBusArgument::ElementType: %d.", complexType.currentType()); + } + } + return v; + } + virtual QVariant value(const QString &key, const QVariant &fallback) const override { const QDBusVariant &dv = config->value(key); const QVariant &v = dv.variant(); - return v.isValid() ? v : fallback; + return v.isValid() ? decodeQDBusArgument(v) : fallback; } virtual void setValue(const QString &key, const QVariant &value) override @@ -277,6 +351,11 @@ config->setValue(key, QDBusVariant(value)); } + virtual void reset(const QString &key) override + { + config->reset(key); + } + virtual QString name() const override { return QString("DBusBackend"); @@ -379,10 +458,11 @@ } #ifndef D_DISABLE_DCONFIG #ifndef D_DISABLE_DBUS_CONFIG - if (DBusBackend::isServiceRegistered()) { + if (DBusBackend::isServiceRegistered() || DBusBackend::isServiceActivatable()) { qCDebug(cfLog, "Fallback to DBus mode"); backend.reset(new DBusBackend(this)); - } else { + } + if (!backend) { qCDebug(cfLog, "Can't use DBus config service, fallback to DConfigFile mode"); backend.reset(new FileBackend(this)); } @@ -411,7 +491,7 @@ #ifndef D_DISABLE_DCONFIG #ifndef D_DISABLE_DBUS_CONFIG - if (DBusBackend::isServiceRegistered()) { + if (DBusBackend::isServiceRegistered() || DBusBackend::isServiceActivatable()) { qCDebug(cfLog, "Fallback to DBus mode"); return new DBusBackend(this); } @@ -436,7 +516,7 @@ } /*! - \class DTK::Core::DConfig + \class Dtk::Core::DConfig \inmodule dtkcore \brief 配置策略提供的接口类 @@ -457,34 +537,55 @@ { } +DConfig::DConfig(DConfigBackend *backend, const QString &name, const QString &subpath, QObject *parent) + : DConfig(backend, QString(), name, subpath, parent) +{ + +} +/*! + * \brief 构造配置策略提供的对象, 指定配置所属的应用Id + * \a appId + * \a name + * \a subpath + * \a parent + * \return 构造的配置策略对象,由调用者释放 + */ +DConfig *DConfig::create(const QString &appId, const QString &name, const QString &subpath, QObject *parent) +{ + return new DConfig(nullptr, appId, name, subpath, parent); +} + +DConfig *DConfig::create(DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath, QObject *parent) +{ + return new DConfig(backend, appId, name, subpath, parent); +} + /*! * \brief 使用自定义的配置策略后端构造对象 - * \a name 配置文件名 * \a backend 调用者继承于DConfigBackend的配置策略后端 + * \a appId 配置文件所属的应用Id,为空时默认为本应用Id + * \a name 配置文件名 * \a subpath 配置文件对应的子目录 * \a parent 父对象 * \note 调用者只构造backend,由DConfig释放。 */ -DConfig::DConfig(DConfigBackend *backend, const QString &name, const QString &subpath, QObject *parent) +DConfig::DConfig(DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath, QObject *parent) : QObject(parent) - , DObject(*new DConfigPrivate(this)) + , DObject(*new DConfigPrivate(this, appId, name, subpath)) { D_D(DConfig); - d->name = name; - d->subpath = subpath; - const auto &appid = getAppId(); - Q_ASSERT(!appid.isEmpty()); + Q_ASSERT(!d->appId.isEmpty()); qCDebug(cfLog, "Load config of appid=%s name=%s, subpath=%s", - qPrintable(appid), qPrintable(d->name), qPrintable(d->subpath)); + qPrintable(d->appId), qPrintable(d->name), qPrintable(d->subpath)); if (backend) { d->backend.reset(backend); } if (auto backend = d->getOrCreateBackend()) { - backend->load(appid); + backend->load(d->appId); } } @@ -496,6 +597,9 @@ QString DConfig::backendName() const { D_DC(DConfig); + if (d->invalid()) + return QString(); + return d->backend->name(); } @@ -506,6 +610,9 @@ QStringList DConfig::keyList() const { D_DC(DConfig); + if (d->invalid()) + return QStringList(); + return d->backend->keyList(); } @@ -516,7 +623,7 @@ bool DConfig::isValid() const { D_DC(DConfig); - return d->backend->isValid(); + return !d->invalid(); } /*! @@ -528,6 +635,9 @@ QVariant DConfig::value(const QString &key, const QVariant &fallback) const { D_DC(DConfig); + if (d->invalid()) + return fallback; + return d->backend->value(key, fallback); } @@ -539,10 +649,26 @@ void DConfig::setValue(const QString &key, const QVariant &value) { D_D(DConfig); + if (d->invalid()) + return; + d->backend->setValue(key, value); } /*! + * \brief 设置其配置项对应的默认值,此值为经过override机制覆盖后的值,不一定为此配置文件中meta中定义的值 + * \param 配置项名称 + */ +void DConfig::reset(const QString &key) +{ + D_D(DConfig); + if (d->invalid()) + return; + + d->backend->reset(key); +} + +/*! * \brief 返回配置文件名称 * \return */ diff -Nru dtkcore-5.5.23/src/dconfigfile.cpp dtkcore-5.5.32/src/dconfigfile.cpp --- dtkcore-5.5.23/src/dconfigfile.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/dconfigfile.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -57,8 +57,8 @@ \brief 按子目录查找机制查找配置文件 - 在\a baseDir目录下,查找名称为\a name的文件, - 若存在 \a subpath,则从\a subpath叶子目录逐级向上查找名称为\a name的文件, + 在 \a baseDir目录下,查找名称为 \a name的文件, + 若存在 \a subpath,则从 \a subpath叶子目录逐级向上查找名称为 \a name的文件, 若不存在此文件,则返回无效路径. */ inline QString getFile(const QString &baseDir, const QString &subpath, const QString &name, @@ -180,9 +180,15 @@ } /*! - \enum DConfigFile::Flag + \class Dtk::Core::DConfigFile \inmodule dtkcore + \brief 规范配置文件读写的相关接口的配置文件实现. + */ + +/*! + \enum DConfigFile::Flag + \value NoOverride 存在此标记时,将表明则此配置项不可被覆盖(详见下述 override 机制)。 反之,不存在此标记时表明此配置项允许被覆盖,对于此类配置项, 如若其有界面设置入口,则当此项不可写时,应当隐藏或禁用界面的设置入口. @@ -192,7 +198,6 @@ /*! \enum DConfigFile::Permissions - \inmodule dtkcore \value ReadOnly 将配置项覆盖为只读, \value ReadWrite 将配置项覆盖为可读可写. @@ -200,7 +205,6 @@ /*! \enum DConfigFile::Visibility - \inmodule dtkcore \value Private 仅限程序内部使用, 对外不可见。此类配置项完全由程序自己读写,可随意增删改写其含义,无需做兼容性考虑, @@ -212,9 +216,8 @@ */ /*! - \class DConfigFile::Version + \struct Dtk::Core::DConfigFile::Version \inmodule dtkcore - \brief 版本信息 此文件的内容格式的版本。版本号使用两位数字描述, @@ -424,17 +427,17 @@ /*! - \class DConfigMeta + \class Dtk::Core::DConfigMeta \inmodule dtkcore - \brief 提供配置文件的原型和覆盖机制的访问接口 + \brief 提供配置文件的原型和覆盖机制的访问接口. */ /*! \fn DConfigFile::Version DConfigMeta::version() const = 0; - \breaf 返回配置版本信息 + \brief 返回配置版本信息. \return */ @@ -449,7 +452,7 @@ /*! \fn bool DConfigMeta::load(const QString &localPrefix = QString()) = 0; - \breaf 解析配置文件 + \brief 解析配置文件 \a localPrefix 为目录前缀 \return */ @@ -457,7 +460,7 @@ /*! \fn bool DConfigMeta::load(QIODevice *meta, const QList &overrides) = 0; - \breaf 解析配置文件流 + \brief 解析配置文件流 \a meta 为原型流 \a overrides 为覆盖机制查找的文件流 \return @@ -466,14 +469,14 @@ /*! \fn QStringList DConfigMeta::keyList() const = 0; - \breaf 返回配置内容的所有配置项 + \brief 返回配置内容的所有配置项 \return */ /*! \fn DConfigFile::Flags DConfigMeta::flags(const QString &key) const = 0; - \breaf 返回指定配置项的特性 + \brief 返回指定配置项的特性 \a key 配置项名称, NoOverride为此配置项不可被覆盖, Global为忽略用户身份 \return */ @@ -481,7 +484,7 @@ /*! \fn DConfigFile::Permissions DConfigMeta::permissions(const QString &key) const = 0; - \breaf 返回指定配置项的权限 + \brief 返回指定配置项的权限 \a key 配置项名称 \return @@ -490,7 +493,7 @@ /*! \fn DConfigFile::Visibility DConfigMeta::visibility(const QString &key) const = 0; - \breaf 返回指定配置项的可见性 + \brief 返回指定配置项的可见性 \a key 配置项名称 \return @@ -499,7 +502,7 @@ /*! \fn int DConfigMeta::serial(const QString &key) const = 0; - \breaf 返回配置项的单调递增值 + \brief 返回配置项的单调递增值 \a key 配置项名称 \return -1为无效值,表明没有配置此项 */ @@ -507,7 +510,7 @@ /*! \fn QString DConfigMeta::displayName(const QString &key, const QLocale &locale) = 0; - \breaf 返回指定配置项的显示名 + \brief 返回指定配置项的显示名 \a key 配置项名称 \a locale 为语言版本 \return @@ -516,7 +519,7 @@ /*! \fn QString DConfigMeta::description(const QString &key, const QLocale &locale) = 0; - \breaf 返回指定配置项的描述信息 + \brief 返回指定配置项的描述信息 \a key 配置项名称 \a locale 为语言版本 \return @@ -526,7 +529,7 @@ /*! \fn QString DConfigMeta::metaPath(const QString &localPrefix = QString(), bool *useAppId = nullptr) const = 0; - \breaf 返回描述文件的路径 + \brief 返回描述文件的路径 \a localPrefix 目录的所有需要查找的覆盖机制目录 \return */ @@ -534,7 +537,7 @@ /*! \fn QStringList DConfigMeta::allOverrideDirs(const bool useAppId, const QString &prefix = QString()) const = 0; - \breaf 获得前缀为\a prefix目录的所有需要查找的覆盖机制目录 + \brief 获得前缀为 \a prefix 目录的所有需要查找的覆盖机制目录 \a userAppId 是否不使用通用目录 \return */ @@ -542,7 +545,7 @@ /*! \fn QVariant DConfigMeta::value(const QString &key) const = 0; - \breaf meta初始值经过覆盖机制覆盖后的原始值 + \brief meta初始值经过覆盖机制覆盖后的原始值 \a key 配置项名称 \return */ @@ -595,30 +598,46 @@ return values.value(key); } - inline QString applicationMetaDir(const QString &prefix, const bool useOptDir = false) const + inline QStringList applicationMetaDirs(const QString &prefix) const { - if (useOptDir) - return QString("%1/opt/apps/%2/files/schemas/configs").arg(prefix, configKey.appId); - - return QString("%1/usr/share/dsg/apps/%2/configs").arg(prefix, configKey.appId); + QStringList paths; + // lower priority is higher. + const auto &dataPaths = DStandardPaths::paths(DStandardPaths::DSG::DataDir); + paths.reserve(dataPaths.size()); + for (auto item : dataPaths) { + paths.prepend(QString("%1/%2/configs/%3").arg(prefix, item, configKey.appId)); + } + return paths; } - inline static QString genericMetaDir(const QString &prefix) { - return prefix + DStandardPaths::filePath(DStandardPaths::DSG::DataDir, - QString("configs")); + inline static QStringList genericMetaDirs(const QString &prefix) { + QStringList paths; + for (auto item: DStandardPaths::paths(DStandardPaths::DSG::DataDir)) { + paths.prepend(QString("%1/%2/configs").arg(prefix, item)); + } + return paths; } QString metaPath(const QString &localPrefix, bool *useAppId) const override { bool useAppIdForOverride = true; - QString path = getFile(applicationMetaDir(localPrefix), configKey.subpath, configKey.fileName + FILE_SUFFIX); - if (path.isEmpty()) - path = getFile(applicationMetaDir(localPrefix, true), configKey.subpath, configKey.fileName + FILE_SUFFIX); + QString path; + const QStringList &applicationMetas = applicationMetaDirs(localPrefix); + for (auto iter = applicationMetas.rbegin(); iter != applicationMetas.rend(); iter++) { + path = getFile(*iter, configKey.subpath, configKey.fileName + FILE_SUFFIX); + if (!path.isEmpty()) + break; + } if (path.isEmpty()) { useAppIdForOverride = false; - path = getFile(genericMetaDir(localPrefix), configKey.subpath, configKey.fileName + FILE_SUFFIX); + const QStringList &genericnMetas = genericMetaDirs(localPrefix); + for (auto iter = genericnMetas.rbegin(); iter != genericnMetas.rend(); iter++) { + path = getFile(*iter, configKey.subpath, configKey.fileName + FILE_SUFFIX); + if (!path.isEmpty()) + break; + } } if (useAppId) { *useAppId = useAppIdForOverride; @@ -733,12 +752,17 @@ const QString &path2 = QString("%1/etc/dsg/configs/overrides/%2/%3") .arg(prefix, useAppId ? configKey.appId : QString(), configKey.fileName); - const QString &path1 = QString("%1%2/configs/overrides/%3/%4") - .arg(prefix, DStandardPaths::path(DStandardPaths::DSG::DataDir), - useAppId ? configKey.appId : QString(), configKey.fileName); - + QStringList paths; + const QStringList &dataPaths = DStandardPaths::paths(DStandardPaths::DSG::DataDir); + paths.reserve(dataPaths.size() + 1); + for (auto path: dataPaths) { + // reverse `DataDir`'s paths, previous `DataDir`'s value has high priority + paths.prepend(QString("%1%2/configs/overrides/%3/%4") + .arg(prefix, path, useAppId ? configKey.appId : QString(), configKey.fileName)); + } // 在后面的优先级更高 - return {path1, path2}; + paths.append(path2); + return paths; } inline QStringList allOverrideDirs(const bool useAppId, const QString &prefix) const override @@ -832,81 +856,74 @@ } /*! - \class DConfigCache + \class Dtk::Core::DConfigCache \inmodule dtkcore - \brief 提供配置文件的用户和全局运行缓存访问接口 + \brief 提供配置文件的用户和全局运行缓存访问接口. */ -/* +/*! \fn bool DConfigCache::load(const QString &localPrefix = QString()) = 0; - \breaf 解析缓存配置文件 + \brief 解析缓存配置文件 \return */ -/* +/*! \fn bool DConfigCache::save(const QString &localPrefix = QString(), QJsonDocument::JsonFormat format = QJsonDocument::Indented, bool sync = false) = 0; - \breaf 保存缓存的值到磁盘中 + \brief 保存缓存的值到磁盘中 \a localPrefix 为目录前缀 \a format 保存格式 \a sync 是否立即刷新 \return */ -/* +/*! \fn bool DConfigCache::isGlobal() const = 0; \brief 是否是全局缓存 \return */ -/* - \fn void DConfigCache::resetMeta(DConfigMeta *meta) = 0; - \breaf 重置配置描述对象 - \a meta 描述对象 - \return -*/ - -/* +/*! \fn void DConfigCache::remove(const QString &key) = 0; - \breaf 删除缓存中的配置项 + \brief 删除缓存中的配置项 \a key 配置项名称 \return */ -/* +/*! \fn QStringList DConfigCache::keyList() const = 0; - \breaf 返回配置内容的所有配置项 + \brief 返回配置内容的所有配置项 \return */ -/* - \fn bool DConfigCache::setValue(const QString &key, const QVariant &value, const uint uid, const QString &appid) = 0; - \breaf 设置缓存中的值 +/*! + \fn bool DConfigCache::setValue(const QString &key, const QVariant &value, const int serial, const uint uid, const QString &callerAppid) = 0; + \brief 设置缓存中的值 \a key 配置项名称 \a value 需要设置的值 \a uid 设置时的用户id - \a appid 设置时的应用id + \a callerAppid 设置时的应用id \return 为true时表示重新设置了新值,false表示没有设置 */ -/* - \fn QVariant DConfigCache::value(const QString &key) = 0; - \breaf 获取缓存中的值 +/*! + \fn QVariant DConfigCache::value(const QString &key) const = 0; + \brief 获取缓存中的值 \a key 配置项名称 \return */ -/* +/*! \fn int DConfigCache::serial(const QString &key) const = 0; - \breaf 返回配置项的单调递增值 + \brief 返回配置项的单调递增值 \a key 配置项名称 \return -1为无效值,表明没有配置此项 */ -/* +/*! \fn uint DConfigCache::uid() const = 0; - \breaf 用户标识,为全局缓存时,uid为非用户标识的特定值 + \brief 用户标识,为全局缓存时,uid为非用户标识的特定值 \return */ @@ -937,7 +954,7 @@ if (homePath.isEmpty()) { return QString(); } - const QString userHomeConfigDir = homePath + QStringLiteral("/.config"); + const QString userHomeConfigDir = homePath + QStringLiteral("/.config/dsg/configs/"); return prefix + userHomeConfigDir + "/" + configKey.appId; } @@ -948,11 +965,11 @@ inline QString globalCacheDir(const QString &prefix) const { // TODO `DSG_APP_DATA` is not set and `appid` is not captured in `DStandardPaths::path`. - if (DStandardPaths::path(DStandardPaths::DSG::AppData).isEmpty()) - return prefix + QString("/var/dsg/appdata/%1/configs").arg(configKey.appId); + QString appDataDir = DStandardPaths::path(DStandardPaths::DSG::AppData); + if (appDataDir.isEmpty()) + appDataDir = QString("/var/dsg/appdata"); - return prefix + DStandardPaths::filePath(DStandardPaths::DSG::AppData, - QString("configs")); + return QString("%1/%2/configs/%3").arg(prefix, appDataDir, configKey.appId); } QString getCacheDir(const QString &localPrefix = QString()) @@ -1126,7 +1143,20 @@ cache->remove(key); return true; } else { - return cache->setValue(key, value, configMeta->serial(key), cache->uid(), appid); + const auto &metaValue = configMeta->value(key); + // sample judgement to reduce a copy of convert. + if (metaValue.type() == value.type()) + return cache->setValue(key, value, configMeta->serial(key), cache->uid(), appid); + + // convert copy to meta's type, it promises `setValue` don't change meta's type. + auto copy = value; + if (!copy.convert(metaValue.userType())) { + qCWarning(cfLog) << "check type error, meta type is " << metaValue.type() + << ", and now type is " << value.type(); + return false; + } + + return cache->setValue(key, copy, configMeta->serial(key), cache->uid(), appid); } } return false; @@ -1175,14 +1205,6 @@ } /*! - \class DTK::Core::DConfigFile - \inmodule dtkcore - - \brief 规范配置文件读写的相关接口的配置文件实现 - - */ - -/*! \brief 支持的版本 \return */ @@ -1215,8 +1237,8 @@ d->globalCache = cache; } -/* - \breaf 解析配置文件 +/*! + \brief 解析配置文件 \a localPrefix 为目录前缀 \return */ @@ -1226,8 +1248,8 @@ return d->load(localPrefix); } -/* - \breaf 解析配置文件流 +/*! + \brief 解析配置文件流 \a meta 为原型流 \a overrides 为覆盖机制查找的文件流 \return @@ -1237,8 +1259,8 @@ return this->meta()->load(meta, overrides); } -/* - \breaf 保存缓存的值到磁盘中 +/*! + \brief 保存缓存的值到磁盘中 \a format 保存格式 \a sync 是否立即刷新 \return @@ -1265,7 +1287,7 @@ } /*! - \breaf 设置缓存中的值 + \brief 设置缓存中的值 \a key 配置项名称 \a value 需要设置的值 \a uid 设置时的用户id diff -Nru dtkcore-5.5.23/src/dconfig.h dtkcore-5.5.32/src/dconfig.h --- dtkcore-5.5.23/src/dconfig.h 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/dconfig.h 2022-05-25 07:33:48.000000000 +0000 @@ -32,10 +32,11 @@ public: virtual ~DConfigBackend(); virtual bool isValid() const = 0; - virtual bool load(const QString &/*appid*/) = 0; + virtual bool load(const QString &/*appId*/) = 0; virtual QStringList keyList() const = 0; virtual QVariant value(const QString &/*key*/, const QVariant &/*fallback*/) const = 0; virtual void setValue(const QString &/*key*/, const QVariant &/*value*/) = 0; + virtual void reset(const QString &key) { setValue(key, QVariant());} virtual QString name() const {return QString("");} }; @@ -54,6 +55,11 @@ explicit DConfig(DConfigBackend *backend, const QString &name, const QString &subpath = QString(), QObject *parent = nullptr); + static DConfig *create(const QString &appId, const QString &name, const QString &subpath = QString(), + QObject *parent = nullptr); + static DConfig *create(DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath = QString(), + QObject *parent = nullptr); + QString backendName() const; QStringList keyList() const; @@ -61,12 +67,17 @@ bool isValid() const; QVariant value(const QString &key, const QVariant &fallback = QVariant()) const; void setValue(const QString &key, const QVariant &value); + void reset(const QString &key); QString name() const; QString subpath() const; Q_SIGNALS: void valueChanged(const QString &key); + +private: + explicit DConfig(DConfigBackend *backend, const QString &appId, const QString &name, const QString &subpath, + QObject *parent = nullptr); }; DCORE_END_NAMESPACE diff -Nru dtkcore-5.5.23/src/ddesktopentry.cpp dtkcore-5.5.32/src/ddesktopentry.cpp --- dtkcore-5.5.23/src/ddesktopentry.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/ddesktopentry.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -339,6 +339,7 @@ } } + setStatus(DDesktopEntry::NoError); return true; } diff -Nru dtkcore-5.5.23/src/dsysinfo.cpp dtkcore-5.5.32/src/dsysinfo.cpp --- dtkcore-5.5.23/src/dsysinfo.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/dsysinfo.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -240,24 +240,40 @@ DDesktopEntry entry(OS_VERSION_FILE); bool ok = false; +#define D_ASSET_EXIT(con, msg) do { \ + if (!(con)) { \ + qWarning() << __func__ << msg; \ + return false; \ + } \ +} while (false) + + D_ASSET_EXIT(entry.status() == DDesktopEntry::NoError, entry.status()); + // 先获取版本信息 // ABCDE.xyz QString osb = entry.stringValue("OsBuild", "Version"); QStringList osbs = osb.split("."); - Q_ASSERT(osbs.size() == 2 && osbs.value(0).size() == 5); - uint left = osbs.value(0).trimmed().toUInt(&ok); - Q_ASSERT(ok); - if (ok) { - osBuild.E = left % 10; - left /= 10; - osBuild.D = left % 10; - left /= 10; - osBuild.C = left % 10; // default C is 0 - left /= 10; - osBuild.B = left % 10; - left /= 10; - osBuild.A = left % 10; + ok = (osbs.size() == 2 && osbs.value(0).size() == 5); + D_ASSET_EXIT(ok, "OsBuild version invalid!"); + + const QStringList &left = osbs.value(0).split(QString(), QString::SkipEmptyParts); + D_ASSET_EXIT(left.size() == 5, "OsBuild version(ls) invalid!"); + + int idx = 0; + osBuild.A = left.value(idx++, "0").toUInt(&ok); + D_ASSET_EXIT(ok, "OsBuild version(a) invalid!"); + osBuild.B = left.value(idx++, "0").toUInt(&ok); + D_ASSET_EXIT(ok, "OsBuild version(b) invalid!"); + osBuild.C = left.value(idx++, "0").toUInt(&ok); + if (!ok) { + auto c = left.value(idx-1, "0").toLatin1(); + D_ASSET_EXIT(c.size()>0, "OsBuild version(c) invalid!"); + osBuild.C = uint(c.at(0)); } + osBuild.D = left.value(idx++, "0").toUInt(&ok); + D_ASSET_EXIT(ok, "OsBuild version(d) invalid!"); + osBuild.E = left.value(idx++, "0").toUInt(&ok); + D_ASSET_EXIT(ok, "OsBuild version(e) invalid!"); // xyz osBuild.xyz = osbs.value(1).trimmed().toUInt(&ok); diff -Nru dtkcore-5.5.23/src/filesystem/dfilesystemwatcher_linux.cpp dtkcore-5.5.32/src/filesystem/dfilesystemwatcher_linux.cpp --- dtkcore-5.5.23/src/filesystem/dfilesystemwatcher_linux.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/filesystem/dfilesystemwatcher_linux.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -438,8 +438,11 @@ fd = inotify_init1(O_NONBLOCK); } - if (fd != -1) + if (fd != -1) { d_d_ptr.reset(new DFileSystemWatcherPrivate(fd, this)); + } else { + qCritical() << "inotify_init1 failed, and the DFileSystemWatcher is invalid." << strerror(errno); + } } /*! diff -Nru dtkcore-5.5.23/src/filesystem/dstandardpaths.cpp dtkcore-5.5.32/src/filesystem/dstandardpaths.cpp --- dtkcore-5.5.23/src/filesystem/dstandardpaths.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/filesystem/dstandardpaths.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -39,7 +39,7 @@ switch (type) { case QStandardPaths::GenericDataLocation: { QString snapRoot = env.value("SNAP"); - QString genericDataDir = snapRoot + "/usr/share/"; + QString genericDataDir = snapRoot + PREFIX"/share/"; return QStringList() << genericDataDir; } default: @@ -169,20 +169,30 @@ QString DStandardPaths::path(DStandardPaths::DSG type) { - switch (type) { - case DSG::AppData: { + const auto list = paths(type); + return list.isEmpty() ? nullptr : list.first(); +} + +QStringList DStandardPaths::paths(DSG type) +{ + QStringList paths; + + if (type == DSG::DataDir) { + const QByteArray &path = qgetenv("DSG_DATA_DIRS"); + if (path.isEmpty()) { + return {QLatin1String(PREFIX"/share/dsg")}; + } + const auto list = path.split(':'); + paths.reserve(list.size()); + for (const auto &i : list) + paths.push_back(QString::fromLocal8Bit(i)); + } else if (type == DSG::AppData) { const QByteArray &path = qgetenv("DSG_APP_DATA"); - //TODO 应用数据目录规范:`/deepin/appdata/{appid}`, now `appid` is not captured. - return QString::fromLocal8Bit(path); + //TODO 应用数据目录规范:`/persistent/appdata/{appid}`, now `appid` is not captured. + paths.push_back(QString::fromLocal8Bit(path)); } - case DSG::DataDir: { - const QByteArray &path = qgetenv("DSG_DATA_DIR"); - if (!path.isEmpty()) - return QString::fromLocal8Bit(path); - return QStringLiteral("/usr/share/dsg"); - } - } - return QString(); + + return paths; } QString DStandardPaths::filePath(DStandardPaths::XDG type, QString fileName) diff -Nru dtkcore-5.5.23/src/filesystem/dstandardpaths.h dtkcore-5.5.32/src/filesystem/dstandardpaths.h --- dtkcore-5.5.23/src/filesystem/dstandardpaths.h 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/filesystem/dstandardpaths.h 2022-05-25 07:33:48.000000000 +0000 @@ -58,6 +58,7 @@ static QString homePath(const uint uid); static QString path(XDG type); static QString path(DSG type); + static QStringList paths(DSG type); static QString filePath(XDG type, QString fileName); static QString filePath(DSG type, QString fileName); diff -Nru dtkcore-5.5.23/src/filesystem/filesystem.pri dtkcore-5.5.32/src/filesystem/filesystem.pri --- dtkcore-5.5.23/src/filesystem/filesystem.pri 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/filesystem/filesystem.pri 2022-05-25 07:33:48.000000000 +0000 @@ -2,6 +2,10 @@ INCLUDEPATH += $$PWD/../base +INSTALL_PREFIX=$$QT_INSTALL_PREFIX +isEmpty(INSTALL_PREFIX): INSTALL_PREFIX=$$[QT_INSTALL_PREFIX] +DEFINES += PREFIX=\\\"$$INSTALL_PREFIX\\\" + HEADERS += \ $$PWD/dbasefilewatcher.h \ $$PWD/dfilesystemwatcher.h \ diff -Nru dtkcore-5.5.23/src/settings/backend/gsettingsbackend.cpp dtkcore-5.5.32/src/settings/backend/gsettingsbackend.cpp --- dtkcore-5.5.23/src/settings/backend/gsettingsbackend.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/settings/backend/gsettingsbackend.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -5,7 +5,12 @@ #include #include #include + +#if QT_HAS_INCLUDE() #include +#else +#include +#endif #include diff -Nru dtkcore-5.5.23/src/settings/dsettings.cpp dtkcore-5.5.32/src/settings/dsettings.cpp --- dtkcore-5.5.23/src/settings/dsettings.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/settings/dsettings.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -281,6 +281,14 @@ this, [ = ](const QString & key, const QVariant & value) { option(key)->setValue(value); }); + // exit and delete thread + connect(this, &DSettings::destroyed, this, [backendWriteThread](){ + if (backendWriteThread->isRunning()) { + backendWriteThread->quit(); + backendWriteThread->wait(); + } + backendWriteThread->deleteLater(); + }); backendWriteThread->start(); diff -Nru dtkcore-5.5.23/src/src.pro dtkcore-5.5.32/src/src.pro --- dtkcore-5.5.23/src/src.pro 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/src/src.pro 2022-05-25 07:33:48.000000000 +0000 @@ -56,11 +56,6 @@ include($$PWD/filesystem/filesystem.pri) include($$PWD/settings/settings.pri) -DTK_MODULE_NAME = $$TARGET -load(dtk_build) - -# ---------------------------------------------- -# install config includes.files += \ $$PWD/*.h \ $$PWD/dtkcore_config.h \ @@ -71,6 +66,12 @@ $$PWD/DConfigFile \ $$PWD/DConfig +# ---------------------------------------------- +# install config + +DTK_MODULE_NAME = $$TARGET +load(dtk_build) + INSTALLS += includes target isEmpty(DTK_STATIC_LIB){ diff -Nru dtkcore-5.5.23/tests/data/dconf-example.meta.json dtkcore-5.5.32/tests/data/dconf-example.meta.json --- dtkcore-5.5.23/tests/data/dconf-example.meta.json 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/data/dconf-example.meta.json 2022-05-25 07:33:48.000000000 +0000 @@ -31,6 +31,62 @@ "description": "I am description", "permissions": "readwrite", "visibility": "public" + }, + "number": { + "value": 1, + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" + }, + "array": { + "value": ["value1", "value2"], + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" + }, + "array_map": { + "value": [{"key1": "value1", "key2": "value2"}], + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" + }, + "array_map_struct": { + "value": [{"key1": {"field1": "value1"}, "key2": "value2"}], + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" + }, + "map": { + "value": {"key1": "value1", "key2": "value2"}, + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" + }, + "map_array": { + "value": {"key1": ["value1"], "key2": ["value2"]}, + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" + }, + "struct": { + "value": {"key1": "value1", "key2": "value2"}, + "serial": 0, + "flags": ["global"], + "name": "array value type", + "permissions": "readwrite", + "visibility": "public" } } } diff -Nru dtkcore-5.5.23/tests/data/dconf-example_other_app_configure.meta.json dtkcore-5.5.32/tests/data/dconf-example_other_app_configure.meta.json --- dtkcore-5.5.23/tests/data/dconf-example_other_app_configure.meta.json 1970-01-01 00:00:00.000000000 +0000 +++ dtkcore-5.5.32/tests/data/dconf-example_other_app_configure.meta.json 2022-05-25 07:33:48.000000000 +0000 @@ -0,0 +1,24 @@ +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "appPublic": { + "value": "publicValue", + "serial": 0, + "flags": ["global"], + "name": "I am a public field for all application", + "description": "I am description", + "permissions": "readwrite", + "visibility": "private" + }, + "appPrivate": { + "value": "appPrivate", + "serial": 0, + "flags": ["nooverride"], + "name": "I am a private field for the application", + "description": "I am description", + "permissions": "readwrite", + "visibility": "public" + } + } +} diff -Nru dtkcore-5.5.23/tests/data.qrc dtkcore-5.5.32/tests/data.qrc --- dtkcore-5.5.23/tests/data.qrc 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/data.qrc 2022-05-25 07:33:48.000000000 +0000 @@ -7,5 +7,6 @@ data/dconf-override/dconf-example.override.a.b.json data/dconf-global.meta.json data/dconf-global.override.json + data/dconf-example_other_app_configure.meta.json diff -Nru dtkcore-5.5.23/tests/test-recoverage-qmake.sh dtkcore-5.5.32/tests/test-recoverage-qmake.sh --- dtkcore-5.5.23/tests/test-recoverage-qmake.sh 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/test-recoverage-qmake.sh 2022-05-25 07:33:48.000000000 +0000 @@ -1,7 +1,8 @@ #!/bin/bash -BUILD_DIR=build -REPORT_DIR=report +BUILD_DIR=`pwd`/../build-ut +HTML_DIR=${BUILD_DIR}/html +XML_DIR=${BUILD_DIR}/report #EXTRACT_ARGS="src" cd ../ rm -rf $BUILD_DIR @@ -16,16 +17,13 @@ cd $BUILD_DIR qmake ../ CONFIG+=debug export ASAN_OPTIONS=halt_on_error=0 -TESTARGS="--gtest_output=xml:dde_test_report_dtkcore.xml" make check -j$(nproc) +TESTARGS="--gtest_output=xml:${XML_DIR}/report_dtkcore.xml" make check -j$(nproc) lcov -d ./ -c -o coverage_all.info #lcov --extract coverage_all.info $EXTRACT_ARGS --output-file coverage.info -lcov --remove coverage_all.info "*/tests/*" "*/usr/include*" "*build/src*" --output-file coverage.info +lcov --remove coverage_all.info "*/tests/*" "*/usr/include*" "*build/src*" "*build-ut/src*" --output-file coverage.info cd .. -genhtml -o $REPORT_DIR $BUILD_DIR/coverage.info +genhtml -o $HTML_DIR $BUILD_DIR/coverage.info && mv ${BUILD_DIR}/html/index.html ${BUILD_DIR}/html/cov_dtkcore.html -#rm -rf $BUILD_DIR -#rm -rf ../$BUILD_DIR - -test -e ./build/asan.log* && mv ./build/asan.log* ./build/asan_dtkcore.log || touch ./build/asan.log +test -e ${BUILD_DIR}/asan.log* && mv ${BUILD_DIR}/asan.log* ${BUILD_DIR}/asan_dtkcore.log || touch ${BUILD_DIR}/asan.log diff -Nru dtkcore-5.5.23/tests/tests.pro dtkcore-5.5.32/tests/tests.pro --- dtkcore-5.5.23/tests/tests.pro 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/tests.pro 2022-05-25 07:33:48.000000000 +0000 @@ -24,7 +24,7 @@ unix: { QMAKE_RPATHDIR += $$OUT_PWD/../src -LIBS += -L$$OUT_PWD/../src/ -ldtkcore -lgtest +LIBS += -lgtest # for dlsym LIBS += -ldl # TODO: vtabhook release test failed diff -Nru dtkcore-5.5.23/tests/ut_dconfig.cpp dtkcore-5.5.32/tests/ut_dconfig.cpp --- dtkcore-5.5.23/tests/ut_dconfig.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_dconfig.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -31,15 +31,16 @@ static constexpr char const *APP_ID = "tests"; static constexpr char const *FILE_NAME = "example"; - +static EnvGuard dsgDataDir; class ut_DConfig : public testing::Test { protected: static void SetUpTestCase() { fileBackendLocalPerfix.set("DSG_DCONFIG_FILE_BACKEND_LOCAL_PREFIX", "/tmp/example"); - metaGuard = new FileCopyGuard(":/data/dconf-example.meta.json", QString("%1/opt/apps/%2/files/schemas/configs/%3.json").arg(fileBackendLocalPerfix.value(), APP_ID, FILE_NAME)); + metaGuard = new FileCopyGuard(":/data/dconf-example.meta.json", QString("%1" PREFIX"/share/dsg/configs/%2/%3.json").arg(fileBackendLocalPerfix.value(), APP_ID, FILE_NAME)); backendType.set("DSG_DCONFIG_BACKEND_TYPE", "FileBackend"); + dsgDataDir.set("DSG_DATA_DIRS", PREFIX"/share/dsg"); } static void TearDownTestCase() { QDir(fileBackendLocalPerfix.value()).removeRecursively(); @@ -47,6 +48,7 @@ delete metaGuard; backendType.restore(); + dsgDataDir.restore(); } virtual void SetUp() override; @@ -71,14 +73,47 @@ } TEST_F(ut_DConfig, value) { + const QStringList array{"1", "2"}; + QVariantMap map; + map.insert("key1", "value1"); + map.insert("key2", "value2"); { DConfig config(FILE_NAME); config.setValue("key2", "126"); ASSERT_EQ(config.value("key2").toString(), QString("126")); + + config.setValue("array", array); + ASSERT_EQ(config.value("array").toStringList(), array); + + config.setValue("map", map); + ASSERT_EQ(config.value("map").toMap(), map); } { DConfig config(FILE_NAME); ASSERT_EQ(config.value("key2").toString(), QString("126")); + ASSERT_EQ(config.value("array").toStringList(), array); + ASSERT_EQ(config.value("map").toMap(), map); + } + { + DConfig config(FILE_NAME); + config.reset("canExit"); + ASSERT_EQ(config.value("canExit").toBool(), true); + + config.reset("key2"); + ASSERT_EQ(config.value("key2").toString(), QString("125")); + + config.reset("number"); + ASSERT_EQ(config.value("number").toInt(), 1); + + config.reset("array"); + const QStringList &originArray {"value1", "value2"}; + ASSERT_EQ(config.value("array").toStringList(), originArray); + + config.reset("map"); + QVariantMap originMap; + originMap.insert("key1", "value1"); + originMap.insert("key2", "value2"); + ASSERT_EQ(config.value("map").toMap(), originMap); } } @@ -91,4 +126,14 @@ } } +TEST_F(ut_DConfig, OtherAppConfigfile) { + + constexpr char const *APP_OTHER = "tests_other"; + FileCopyGuard gurand(":/data/dconf-example_other_app_configure.meta.json", QString("%1" PREFIX"/share/dsg/configs/%2/%3.json").arg(fileBackendLocalPerfix.value(), APP_OTHER, FILE_NAME)); + + QScopedPointer config(DConfig::create(APP_OTHER, FILE_NAME)); + ASSERT_TRUE(config->isValid()); + ASSERT_EQ(config->value("appPublic").toString(), QString("publicValue")); +} + void ut_DConfig::SetUp() {} diff -Nru dtkcore-5.5.23/tests/ut_dconfigfile.cpp dtkcore-5.5.32/tests/ut_dconfigfile.cpp --- dtkcore-5.5.23/tests/ut_dconfigfile.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_dconfigfile.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -35,8 +35,8 @@ { protected: static void SetUpTestCase() { - dsgDataDir.set("DSG_DATA_DIR", "/tmp/dconfig/dsg_data"); home.set("HOME", "/tmp/home"); + dsgDataDir.set("DSG_DATA_DIRS", PREFIX"/share/dsg"); } static void TearDownTestCase() { dsgDataDir.restore(); @@ -46,9 +46,9 @@ const char *APP_ID = "org.foo.appid"; const char *FILE_NAME = "org.foo.name"; - QString metaPath = QString("%1/opt/apps/%2/files/schemas/configs").arg(LocalPrefix, APP_ID); - QString metaGlobalPath = QString("%1%2/configs").arg(LocalPrefix, DStandardPaths::path(DStandardPaths::DSG::DataDir)); - QString overridePath = QString("%1%2/configs/overrides/%3/%4").arg(LocalPrefix, DStandardPaths::path(DStandardPaths::DSG::DataDir), APP_ID, FILE_NAME); + QString metaPath = QString("%1" PREFIX"/share/dsg/configs/%2").arg(LocalPrefix, APP_ID); + QString metaGlobalPath = QString("%1" PREFIX"/share/dsg/configs").arg(LocalPrefix); + QString overridePath = QString("%1" PREFIX"/share/dsg/configs/overrides/%2/%3").arg(LocalPrefix, APP_ID, FILE_NAME); uint uid = getuid(); static EnvGuard dsgDataDir; static EnvGuard home; @@ -105,6 +105,86 @@ ASSERT_EQ(config.meta()->permissions("canExit"), DConfigFile::ReadWrite); } +TEST_F(ut_DConfigFile, setValueTypeCheck) { + + FileCopyGuard gurad(":/data/dconf-example.meta.json", QString("%1/%2.json").arg(metaPath, FILE_NAME)); + DConfigFile config(APP_ID, FILE_NAME); + config.load(LocalPrefix); + QScopedPointer userCache(config.createUserCache(uid)); + userCache->load(LocalPrefix); + { + const auto type = config.value("canExit", userCache.get()).type(); + ASSERT_TRUE(config.setValue("canExit", false, "test", userCache.get())); + ASSERT_TRUE(config.setValue("canExit", "true", "test", userCache.get())); + ASSERT_FALSE(config.setValue("canExit", "true2", "test", userCache.get())); + ASSERT_EQ(config.value("canExit", userCache.get()).type(), type); + } + { + const auto type = config.value("key2", userCache.get()).type(); + ASSERT_TRUE(config.setValue("key2", "121", "test", userCache.get())); + ASSERT_FALSE(config.setValue("key2", 121, "test", userCache.get())); + ASSERT_EQ(config.value("key2", userCache.get()).type(), type); + } + { + const auto type = config.value("number", userCache.get()).type(); + ASSERT_TRUE(config.setValue("number", 1, "test", userCache.get())); + ASSERT_TRUE(config.setValue("number", 2.0, "test", userCache.get())); + ASSERT_TRUE(config.setValue("number", "3", "test", userCache.get())); + ASSERT_FALSE(config.setValue("number", "1ab", "test", userCache.get())); + ASSERT_EQ(config.value("number", userCache.get()).type(), type); + } + { + const auto type = config.value("array", userCache.get()).type(); + const QStringList array{"value1", "value2"}; + ASSERT_TRUE(config.setValue("array", QStringList(), "test", userCache.get())); + ASSERT_TRUE(config.setValue("array", array, "test", userCache.get())); + ASSERT_TRUE(config.setValue("array", QJsonDocument::fromJson("[]").toVariant(), "test", userCache.get())); + ASSERT_FALSE(config.setValue("array", "", "test", userCache.get())); + ASSERT_FALSE(config.setValue("array", "value1", "test", userCache.get())); + ASSERT_EQ(config.value("array", userCache.get()).type(), type); + } + { + const auto type = config.value("array_map", userCache.get()).type(); + QVariantList array; + QVariantMap map1; + map1["key1"] = "value1"; + map1["key2"] = "value2"; + array.append(map1); + ASSERT_EQ(config.value("array_map", userCache.get()).toList(), array); + ASSERT_TRUE(config.setValue("array_map", QVariantList(), "test", userCache.get())); + ASSERT_TRUE(config.setValue("array_map", array, "test", userCache.get())); + ASSERT_TRUE(config.setValue("array_map", QJsonDocument::fromJson("[]").toVariant(), "test", userCache.get())); + ASSERT_FALSE(config.setValue("array_map", "", "test", userCache.get())); + ASSERT_FALSE(config.setValue("array_map", "value1", "test", userCache.get())); + ASSERT_EQ(config.value("array_map", userCache.get()).type(), type); + } + { + const auto type = config.value("map", userCache.get()).type(); + QVariantMap map; + map.insert("key1", "value1"); + map.insert("key2", "value2"); + ASSERT_TRUE(config.setValue("map", QVariantMap(), "test", userCache.get())); + ASSERT_TRUE(config.setValue("map", map, "test", userCache.get())); + ASSERT_TRUE(config.setValue("map", QJsonDocument::fromJson("{}").toVariant(), "test", userCache.get())); + ASSERT_FALSE(config.setValue("map", QJsonDocument::fromJson("[]").toVariant(), "test", userCache.get())); + ASSERT_FALSE(config.setValue("map", "key1", "test", userCache.get())); + ASSERT_EQ(config.value("map", userCache.get()).type(), type); + } + { + const auto type = config.value("map_array", userCache.get()).type(); + QVariantMap map; + map.insert("key1", QStringList{"value1"}); + map.insert("key2", QStringList{"value2"}); + ASSERT_EQ(config.value("map_array", userCache.get()).toMap(), map); + ASSERT_TRUE(config.setValue("map_array", QVariantMap(), "test", userCache.get())); + ASSERT_TRUE(config.setValue("map_array", map, "test", userCache.get())); + ASSERT_TRUE(config.setValue("map_array", QJsonDocument::fromJson("{}").toVariant(), "test", userCache.get())); + ASSERT_FALSE(config.setValue("map_array", "", "test", userCache.get())); + ASSERT_FALSE(config.setValue("map_array", "value1", "test", userCache.get())); + ASSERT_EQ(config.value("map_array", userCache.get()).type(), type); + } +} + TEST_F(ut_DConfigFile, fileIODevice) { FileCopyGuard gurad(":/data/dconf-example.meta.json", QString("%1/%2.json").arg(metaPath, FILE_NAME)); @@ -169,6 +249,12 @@ QScopedPointer userCache(config.createUserCache(uid)); ASSERT_TRUE(userCache->load(LocalPrefix)); ASSERT_EQ(config.value("key3", userCache.get()), QString("application")); + const QStringList array{"value1", "value2"}; + ASSERT_EQ(config.value("array", userCache.get()), array); + QVariantMap map; + map.insert("key1", "value1"); + map.insert("key2", "value2"); + ASSERT_EQ(config.value("map", userCache.get()).toMap(), map); } TEST_F(ut_DConfigFile, fileOverride) { diff -Nru dtkcore-5.5.23/tests/ut_dfilesystemwatcher.cpp dtkcore-5.5.32/tests/ut_dfilesystemwatcher.cpp --- dtkcore-5.5.23/tests/ut_dfilesystemwatcher.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_dfilesystemwatcher.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -21,7 +21,9 @@ #include #include +#define private public #include "filesystem/dfilesystemwatcher.h" +#undef private DCORE_USE_NAMESPACE @@ -63,13 +65,18 @@ TEST_F(ut_DFileSystemWatcher, testDFileSystemWatcherAddPath) { + if (!fileSystemWatcher->d_func()) return; + fileSystemWatcher->addPath("/tmp/etc0"); + QStringList dirs = fileSystemWatcher->directories(); ASSERT_TRUE(dirs.contains("/tmp/etc0")); } TEST_F(ut_DFileSystemWatcher, testDFileSystemWatcherAddPaths) { + if (!fileSystemWatcher->d_func()) return; + fileSystemWatcher->addPaths( QStringList() << "/tmp/etc0" << "/tmp/etc1"); QStringList dirs = fileSystemWatcher->directories(); ASSERT_TRUE(dirs.contains("/tmp/etc0")); @@ -78,6 +85,8 @@ TEST_F(ut_DFileSystemWatcher, testDFileSystemWatcherRemovePath) { + if (!fileSystemWatcher->d_func()) return; + fileSystemWatcher->addPath("/tmp/etc0"); QStringList dirs0 = fileSystemWatcher->directories(); ASSERT_TRUE(dirs0.contains("/tmp/etc0")); @@ -88,6 +97,8 @@ TEST_F(ut_DFileSystemWatcher, testDFileSystemWatcherRemovePaths) { + if (!fileSystemWatcher->d_func()) return; + fileSystemWatcher->addPaths( QStringList() << "/tmp/etc0" << "/tmp/etc1"); QStringList dirs0 = fileSystemWatcher->directories(); ASSERT_TRUE(dirs0.contains("/tmp/etc0")); diff -Nru dtkcore-5.5.23/tests/ut_dfilewatcher.cpp dtkcore-5.5.32/tests/ut_dfilewatcher.cpp --- dtkcore-5.5.23/tests/ut_dfilewatcher.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_dfilewatcher.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -77,24 +77,32 @@ TEST_F(ut_DFileWatcher, testDFileWatcherStartWatcher) { + if (!fileWatcher->startWatcher()) return; + fileWatcher->setEnabledSubfileWatcher(QUrl()); ASSERT_TRUE(fileWatcher->startWatcher()); } TEST_F(ut_DFileWatcher, testDFileWatcherStopWatcher) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); ASSERT_TRUE(fileWatcher->stopWatcher()); } TEST_F(ut_DFileWatcher, testDFileWatcherRestartWatcher) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); ASSERT_TRUE(fileWatcher->restartWatcher()); } TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileDeleted) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileDeleted); QFile file("/tmp/etc/test"); @@ -108,6 +116,8 @@ TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileAttributeChanged) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileAttributeChanged); QFile file("/tmp/etc/test"); @@ -127,6 +137,8 @@ TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileMoved) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileMoved); QString oldFile("/tmp/etc/test"); @@ -141,6 +153,8 @@ TEST_F(ut_DFileWatcher, testDFileSystemWatcherSubfileCreated) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); QSignalSpy spy(fileWatcher, &DBaseFileWatcher::subfileCreated); QFile file("/tmp/etc/test"); @@ -158,6 +172,8 @@ TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileModified) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileModified); QFile file("/tmp/etc/test"); @@ -175,6 +191,8 @@ TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileClosed) { + if (!fileWatcher->startWatcher()) return; + ASSERT_TRUE(fileWatcher->startWatcher()); QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileClosed); QFile file("/tmp/etc/test"); diff -Nru dtkcore-5.5.23/tests/ut_dfilewatchermanager.cpp dtkcore-5.5.32/tests/ut_dfilewatchermanager.cpp --- dtkcore-5.5.23/tests/ut_dfilewatchermanager.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_dfilewatchermanager.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -68,6 +68,7 @@ TEST_F(ut_DFileWatcherManager, testDFileWatcherManagerAdd) { auto watcher = fileWatcherManager->add("/tmp/test"); + if (!watcher->startWatcher()) return; // test fileDeleted signal QSignalSpy spy(watcher, &DBaseFileWatcher::fileDeleted); diff -Nru dtkcore-5.5.23/tests/ut_dutil.cpp dtkcore-5.5.32/tests/ut_dutil.cpp --- dtkcore-5.5.23/tests/ut_dutil.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_dutil.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -309,7 +309,7 @@ entry.setStringValue("专业版", "EditionName[zh_CN]", "Version"); entry.setStringValue("20", "MajorVersion", "Version"); entry.setStringValue("100A", "MinorVersion", "Version"); - entry.setStringValue("11018.107", "OsBuild", "Version"); + entry.setStringValue("11Z18.107", "OsBuild", "Version"); ASSERT_TRUE(entry.save()); ASSERT_TRUE(DSysInfo::uosSystemName(QLocale("C")) == "UnionTech OS Desktop"); diff -Nru dtkcore-5.5.23/tests/ut_qsettingsbackend.cpp dtkcore-5.5.32/tests/ut_qsettingsbackend.cpp --- dtkcore-5.5.23/tests/ut_qsettingsbackend.cpp 2021-12-08 03:52:50.000000000 +0000 +++ dtkcore-5.5.32/tests/ut_qsettingsbackend.cpp 2022-05-25 07:33:48.000000000 +0000 @@ -100,7 +100,7 @@ { QPointer tmpSetting = DSettings::fromJson(jsonContent.toLatin1()); QScopedPointer scopeSettings(tmpSetting.data()); - static QSettingBackend qBackend("/tmp/test.ini"); + QSettingBackend qBackend("/tmp/test.ini"); scopeSettings->setBackend(&qBackend); Q_EMIT qBackend.setOption("Test", true); @@ -108,4 +108,7 @@ ASSERT_TRUE(!qKeys.isEmpty()); QVariant value = qBackend.getOption("Test"); ASSERT_TRUE(!value.toBool()); + + // ensure `DSettings` is released before `SettingBackend` if `doSetOption` maybe execute. + scopeSettings.reset(); }