--- /dev/null
+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
--- /dev/null
+name: Call build-deb
+on:
+ pull_request_target:
+ types: [opened, synchronize]
+ 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 }}
--- /dev/null
+name: chatOps
+on:
+ issue_comment:
+ types: [created]
+
+jobs:
+ chatopt:
+ uses: linuxdeepin/.github/.github/workflows/chatOps.yml@master
--- /dev/null
+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
--- /dev/null
+name: cppcheck
+on:
+ pull_request_target:
+
+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
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
<arg type='s' name='key' direction='in'/>
<arg type='v' name='value' direction='in'/>
</method>
+ <method name='reset'>
+ <arg type='s' name='key' direction='in'/>
+ </method>
<method name='name'>
<arg type='s' name='key' direction='in'/>
<arg type='s' name='language' direction='in'/>
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 配置后端的抽象接口.
*/
/*!
- \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 后端配置的唯一标识
*/
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)
{
}
DConfigBackend *getOrCreateBackend();
DConfigBackend *createBackendByEnv();
+ QString appId;
QString name;
QString subpath;
QScopedPointer<DConfigBackend> backend;
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();
}
}
+ 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");
return QDBusConnection::systemBus().interface()->isServiceRegistered(DSG_CONFIG);
}
+ static bool isServiceActivatable()
+ {
+ const QDBusReply<QStringList> activatableNames = QDBusConnection::systemBus().interface()->
+ callWithArgumentList(QDBus::AutoDetect,
+ QLatin1String("ListActivatableNames"),
+ QList<QVariant>());
+// qInfo() << activatableNames.value() << activatableNames.value().contains(DSG_CONFIG);
+
+ return activatableNames.value().contains(DSG_CONFIG);
+ }
+
virtual bool isValid() const override
{
return config && config->isValid();
return config->keyList();
}
+ static QVariant decodeQDBusArgument(const QVariant &v)
+ {
+ if (v.canConvert<QDBusArgument>()) {
+ // 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<QDBusArgument>();
+ 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
config->setValue(key, QDBusVariant(value));
}
+ virtual void reset(const QString &key) override
+ {
+ config->reset(key);
+ }
+
virtual QString name() const override
{
return QString("DBusBackend");
}
#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 {
#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);
}
}
/*!
- \class DTK::Core::DConfig
+ \class Dtk::Core::DConfig
\inmodule dtkcore
\brief 配置策略提供的接口类
{
}
+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);
}
}
d->backend->setValue(key, value);
}
+/*!
+ * \brief 设置其配置项对应的默认值,此值为经过override机制覆盖后的值,不一定为此配置文件中meta中定义的值
+ * \param 配置项名称
+ */
+void DConfig::reset(const QString &key)
+{
+ D_D(DConfig);
+ d->backend->reset(key);
+}
+
/*!
* \brief 返回配置文件名称
* \return
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("");}
};
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;
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
\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,
}
/*!
- \enum DConfigFile::Flag
+ \class Dtk::Core::DConfigFile
\inmodule dtkcore
+ \brief 规范配置文件读写的相关接口的配置文件实现.
+ */
+
+/*!
+ \enum DConfigFile::Flag
+
\value NoOverride 存在此标记时,将表明则此配置项不可被覆盖(详见下述 override 机制)。
反之,不存在此标记时表明此配置项允许被覆盖,对于此类配置项,
如若其有界面设置入口,则当此项不可写时,应当隐藏或禁用界面的设置入口.
/*!
\enum DConfigFile::Permissions
- \inmodule dtkcore
\value ReadOnly 将配置项覆盖为只读,
\value ReadWrite 将配置项覆盖为可读可写.
/*!
\enum DConfigFile::Visibility
- \inmodule dtkcore
\value Private 仅限程序内部使用,
对外不可见。此类配置项完全由程序自己读写,可随意增删改写其含义,无需做兼容性考虑,
*/
/*!
- \class DConfigFile::Version
+ \struct Dtk::Core::DConfigFile::Version
\inmodule dtkcore
-
\brief 版本信息
此文件的内容格式的版本。版本号使用两位数字描述,
/*!
- \class DConfigMeta
+ \class Dtk::Core::DConfigMeta
\inmodule dtkcore
- \brief 提供配置文件的原型和覆盖机制的访问接口
+ \brief 提供配置文件的原型和覆盖机制的访问接口.
*/
/*!
\fn DConfigFile::Version DConfigMeta::version() const = 0;
- \breaf 返回配置版本信息
+ \brief 返回配置版本信息.
\return
*/
/*!
\fn bool DConfigMeta::load(const QString &localPrefix = QString()) = 0;
- \breaf 解析配置文件
+ \brief 解析配置文件
\a localPrefix 为目录前缀
\return
*/
/*!
\fn bool DConfigMeta::load(QIODevice *meta, const QList<QIODevice*> &overrides) = 0;
- \breaf 解析配置文件流
+ \brief 解析配置文件流
\a meta 为原型流
\a overrides 为覆盖机制查找的文件流
\return
/*!
\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
*/
/*!
\fn DConfigFile::Permissions DConfigMeta::permissions(const QString &key) const = 0;
- \breaf 返回指定配置项的权限
+ \brief 返回指定配置项的权限
\a key 配置项名称
\return
/*!
\fn DConfigFile::Visibility DConfigMeta::visibility(const QString &key) const = 0;
- \breaf 返回指定配置项的可见性
+ \brief 返回指定配置项的可见性
\a key 配置项名称
\return
/*!
\fn int DConfigMeta::serial(const QString &key) const = 0;
- \breaf 返回配置项的单调递增值
+ \brief 返回配置项的单调递增值
\a key 配置项名称
\return -1为无效值,表明没有配置此项
*/
/*!
\fn QString DConfigMeta::displayName(const QString &key, const QLocale &locale) = 0;
- \breaf 返回指定配置项的显示名
+ \brief 返回指定配置项的显示名
\a key 配置项名称
\a locale 为语言版本
\return
/*!
\fn QString DConfigMeta::description(const QString &key, const QLocale &locale) = 0;
- \breaf 返回指定配置项的描述信息
+ \brief 返回指定配置项的描述信息
\a key 配置项名称
\a locale 为语言版本
\return
/*!
\fn QString DConfigMeta::metaPath(const QString &localPrefix = QString(), bool *useAppId = nullptr) const = 0;
- \breaf 返回描述文件的路径
+ \brief 返回描述文件的路径
\a localPrefix 目录的所有需要查找的覆盖机制目录
\return
*/
/*!
\fn QStringList DConfigMeta::allOverrideDirs(const bool useAppId, const QString &prefix = QString()) const = 0;
- \breaf 获得前缀为\a prefix目录的所有需要查找的覆盖机制目录
+ \brief 获得前缀为 \a prefix 目录的所有需要查找的覆盖机制目录
\a userAppId 是否不使用通用目录
\return
*/
/*!
\fn QVariant DConfigMeta::value(const QString &key) const = 0;
- \breaf meta初始值经过覆盖机制覆盖后的原始值
+ \brief meta初始值经过覆盖机制覆盖后的原始值
\a key 配置项名称
\return
*/
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;
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
}
/*!
- \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
*/
if (homePath.isEmpty()) {
return QString();
}
- const QString userHomeConfigDir = homePath + QStringLiteral("/.config");
+ const QString userHomeConfigDir = homePath + QStringLiteral("/.config/dsg/configs/");
return prefix + userHomeConfigDir + "/" + configKey.appId;
}
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())
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;
}
}
-/*!
- \class DTK::Core::DConfigFile
- \inmodule dtkcore
-
- \brief 规范配置文件读写的相关接口的配置文件实现
-
- */
-
/*!
\brief 支持的版本
\return
d->globalCache = cache;
}
-/*
- \breaf 解析配置文件
+/*!
+ \brief 解析配置文件
\a localPrefix 为目录前缀
\return
*/
return d->load(localPrefix);
}
-/*
- \breaf 解析配置文件流
+/*!
+ \brief 解析配置文件流
\a meta 为原型流
\a overrides 为覆盖机制查找的文件流
\return
return this->meta()->load(meta, overrides);
}
-/*
- \breaf 保存缓存的值到磁盘中
+/*!
+ \brief 保存缓存的值到磁盘中
\a format 保存格式
\a sync 是否立即刷新
\return
}
/*!
- \breaf 设置缓存中的值
+ \brief 设置缓存中的值
\a key 配置项名称
\a value 需要设置的值
\a uid 设置时的用户id
}
}
+ setStatus(DDesktopEntry::NoError);
return true;
}
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);
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);
+ }
}
/*!
switch (type) {
case QStandardPaths::GenericDataLocation: {
QString snapRoot = env.value("SNAP");
- QString genericDataDir = snapRoot + "/usr/share/";
+ QString genericDataDir = snapRoot + PREFIX"/share/";
return QStringList() << genericDataDir;
}
default:
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)
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);
INCLUDEPATH += $$PWD/../base
+DEFINES += PREFIX=\\\"$$PREFIX\\\"
+
HEADERS += \
$$PWD/dbasefilewatcher.h \
$$PWD/dfilesystemwatcher.h \
#include <QJsonDocument>
#include <QJsonObject>
#include <QVariant>
+
+#if QT_HAS_INCLUDE(<QGSettings/QGSettings>)
#include <QGSettings/QGSettings>
+#else
+#include <QGSettings>
+#endif
#include <DSettings>
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();
<file>data/dconf-override/dconf-example.override.a.b.json</file>
<file>data/dconf-global.meta.json</file>
<file>data/dconf-global.override.json</file>
+ <file>data/dconf-example_other_app_configure.meta.json</file>
</qresource>
</RCC>
"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"
}
}
}
--- /dev/null
+{
+ "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"
+ }
+ }
+}
#!/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
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
unix: {
QMAKE_RPATHDIR += $$OUT_PWD/../src
-LIBS += -L$$OUT_PWD/../src/ -ldtkcore -lgtest
+LIBS += -lgtest
# for dlsym
LIBS += -ldl
# TODO: vtabhook release test failed
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", "/usr/share/dsg");
}
static void TearDownTestCase() {
QDir(fileBackendLocalPerfix.value()).removeRecursively();
delete metaGuard;
backendType.restore();
+ dsgDataDir.restore();
}
virtual void SetUp() override;
}
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);
}
}
}
}
+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<DConfig> config(DConfig::create(APP_OTHER, FILE_NAME));
+ ASSERT_TRUE(config->isValid());
+ ASSERT_EQ(config->value("appPublic").toString(), QString("publicValue"));
+}
+
void ut_DConfig::SetUp() {}
{
protected:
static void SetUpTestCase() {
- dsgDataDir.set("DSG_DATA_DIR", "/tmp/dconfig/dsg_data");
home.set("HOME", "/tmp/home");
}
static void TearDownTestCase() {
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;
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<DConfigCache> 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));
QScopedPointer<DConfigCache> 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) {
#include <gtest/gtest.h>
#include <QDir>
+#define private public
#include "filesystem/dfilesystemwatcher.h"
+#undef private
DCORE_USE_NAMESPACE
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"));
TEST_F(ut_DFileSystemWatcher, testDFileSystemWatcherRemovePath)
{
+ if (!fileSystemWatcher->d_func()) return;
+
fileSystemWatcher->addPath("/tmp/etc0");
QStringList dirs0 = fileSystemWatcher->directories();
ASSERT_TRUE(dirs0.contains("/tmp/etc0"));
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"));
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");
TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileAttributeChanged)
{
+ if (!fileWatcher->startWatcher()) return;
+
ASSERT_TRUE(fileWatcher->startWatcher());
QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileAttributeChanged);
QFile file("/tmp/etc/test");
TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileMoved)
{
+ if (!fileWatcher->startWatcher()) return;
+
ASSERT_TRUE(fileWatcher->startWatcher());
QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileMoved);
QString oldFile("/tmp/etc/test");
TEST_F(ut_DFileWatcher, testDFileSystemWatcherSubfileCreated)
{
+ if (!fileWatcher->startWatcher()) return;
+
ASSERT_TRUE(fileWatcher->startWatcher());
QSignalSpy spy(fileWatcher, &DBaseFileWatcher::subfileCreated);
QFile file("/tmp/etc/test");
TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileModified)
{
+ if (!fileWatcher->startWatcher()) return;
+
ASSERT_TRUE(fileWatcher->startWatcher());
QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileModified);
QFile file("/tmp/etc/test");
TEST_F(ut_DFileWatcher, testDFileSystemWatcherFileClosed)
{
+ if (!fileWatcher->startWatcher()) return;
+
ASSERT_TRUE(fileWatcher->startWatcher());
QSignalSpy spy(fileWatcher, &DBaseFileWatcher::fileClosed);
QFile file("/tmp/etc/test");
TEST_F(ut_DFileWatcherManager, testDFileWatcherManagerAdd)
{
auto watcher = fileWatcherManager->add("/tmp/test");
+ if (!watcher->startWatcher()) return;
// test fileDeleted signal
QSignalSpy spy(watcher, &DBaseFileWatcher::fileDeleted);
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");
{
QPointer<DSettings> tmpSetting = DSettings::fromJson(jsonContent.toLatin1());
QScopedPointer<DSettings> scopeSettings(tmpSetting.data());
- static QSettingBackend qBackend("/tmp/test.ini");
+ QSettingBackend qBackend("/tmp/test.ini");
scopeSettings->setBackend(&qBackend);
Q_EMIT qBackend.setOption("Test", true);
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();
}