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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
### 任务描述

`GSettings` 是一套可使用多个 `storage backends` 的 `API` ,默认使用 `dconf` 作为 `backend` 。`GSettings` 的配置文件是 `xml` 格式的,文件需以 `.gschema.xml` 结尾,文件名通常与 `id` 相同。配置文件安装在 `/usr/share/glib-2.0/schemas/` 目录下,手动添加进去的文件需要执行 `glib-complie-schemas` 命令让其生效。
Copy link
Contributor

Choose a reason for hiding this comment

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

考虑下能不能将 DConfig 作为 Gsettings 的backend,解决它俩的配置同步问题


`DConfig` 配置策略是由 `deepin` 自主研发的一套存储规范,涉及主要包括配置描述文件(`meta`)、配置存储文件(`cache`)、覆盖机制配置文件(`override`), 应用需要配置的为 `meta` 和 `override`(可选)文件,它们均为 `json` 格式的文件。

基于 `DConfig` 及 `GSettings` 接口,完成一个配置转换小工具,此应用程序应当满足:

1. 项目使用 `cmake` 管理,基于 `dtkcore`
2. 熟悉 `DConfig` 及 `GSettings` 相关概念及实现。
3. 功能需求:
- 支持解析 `GSettings` 的 `.gschema.xml` 配置文件转换 `DConfig` 配置需要的 `json` 文件,包括描述信息,默认值等都需要相同
```bash
# 仅供参考
gsettings2dconfig -i ./com.deepin.dde.dock.module.gschema.xml -o ./com.deepin.dde.dock.module.json
```
- 支持 **双向** 转发配置 `GSettings` <==> `DConfig` ,即 `GSettings` 配置项变化时同步到 `DConfig`配置项,一样的 `DConfig` 配置项变化时同步到 `GSettings` 配置项。
```bash
# 仅供参考
gsettings2dconfig --proxy -id com.deepin.dtk -path /dtk/deepin/deepin-terminal -appid dtk.deepin.deepin-terminal -resource com.deepin.dtk
```
- 支持同步配置 `GSettings` 配置到 `DConfig`
```bash
# 仅供参考
gsettings2dconfig --sync -id com.deepin.dtk -path /dtk/deepin/deepin-terminal -appid dtk.deepin.deepin-terminal -resource com.deepin.dtk
```

### 环境的准备

下载并安装最新 `deepin` 操作系统;

为方便起见,下述步骤假定您在使用 `deepin V20` 。
- 检查 `gsetings` 是否安装正确,直接终端输入 `gsettings`,是否出现 `gsettings` 帮助文档;

- 安装 `libdtkcore-dev`, `libgsettings-qt-dev`
```
sudo apt install libdtkcore-dev libgsettings-qt-dev
```


### 验收标准

最终完成的应用程序应当能够提供下述功能:

- [ ] 能够恰当的运行和退出
- [ ] 代码符合 deepin 编码风格
- [ ] 项目用 CMake 管理,程序基于 DTK
- [ ] 支持 `gschema` 文件转 `meta` 文件
- [ ] 支持 `GSettings` 信号同步到 `DConfig`
- [ ] 帮助文档提示友好

我们通过对上述各项标准的完成数量来评估任务的完成程度

### 涉及的项目/提交到何处

- 此项目需要您最终将代码提交到 `linuxdeepin/dde-app-services` 仓库之中
- 在仓库中的 `dde-app-services/dconfig-center/` 目录下存储您的代码

### 预计工作量 116h

- 创建环境 4h
- 熟悉需求 8h
- 熟悉 `DConfig` 及 `GSettings` 相关资料 8h
- 设计 10h
- 代码编写 60 h
- 自测 16h
- 验收及沟通 8h


### 参考文档
- [GSettings 简介](https://www.jianshu.com/p/69289aee550b)
- [DConfig 规范](./%E9%85%8D%E7%BD%AE%E6%96%87%E4%BB%B6%E8%A7%84%E8%8C%83.md)

- [dtkcore](https://github.com/linuxdeepin/dtkcore)
- [dde-app-services](https://github.com/linuxdeepin/dde-app-services)

### 联系方式

此任务的任务对接人为: [email protected]

Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
# 配置文件规范

> __警告!此规范内容是不稳定版本,可能会发生破坏兼容性的更新。当无法保障向下兼容时,将会升级此文档的主版本号,如从“1.0”更新到“2.0”。反之,普通更新只会升级次版本号,如“1.0”更新到“1.1”,其对“1.0”版本向下兼容。请在使用前确认此文档的版本号,并为将来可能发生的兼容性变化做好准备。__

* 维护者:zccrs <[email protected]>
* 修改日期:2021.3.17
* 版本:1.0
* 议题:#3

## 引言

本文档规定了配置文件的存储格式和安装路径、配置文件的读写、配置中心的设计、开发库 API 接口等规范。

## 名词解释

* 配置描述文件:此类文件由安装包携带,类似于 gsettings 的 schemes 文件,用于描述配置项的元信息,以及携带配置项的默认值
* 配置存储文件:对于一些可修改的配置项,本文件用于保存程序运行过程中的改动
* $APP_ROOT:应用程序安装的根目录
* $appid:应用程序的唯一ID

## 角色说明

* 应用程序:读写配置文件的实体,亦是配置文件的提供者,一个应用程序可提供多个配置文件
* 配置文件:包含一系列配置项的集合,存储了配置项相关的信息
* 配置中心:为程序提供读写配置项的 DBus 接口,实现配置项的 override 机制
* DTK:应用程序开发基础库,提供统一的配置文件读写工具类

## 配置描述文件

配置描述文件使用 json 格式提供,以下将“配置描述文件”简称为“描述文件”。

* 文件名:$appid.json,如:foo.example.json
* 安装路径:
* $APP_ROOT/configs/:用于放置应用程序所携带的描述文件
* $DSG\_DATA\_DIR/configs/:用于放置开发库所携带的描述文件,如 $DSG\_DATA\_DIR/configs/org.dtk.core.json,此目录下的配置描述文件为所有程序共享。如果安装到 $DSG\_DATA\_DIR/configs/ 下的配置文件与 $APP_ROOT/configs/ 中的同名(忽略子目录),则使用 $APP_ROOT/configs/ 下的文件
* 子目录:描述文件安装可包含子路径。

假设 foo.example.json 安装到 “$APP_ROOT/configs/A/B”,当程序读取配置文件时,可额外传入 subpath 参数,假设传入的参数为 “/A/B/C”,则查找 foo.example.json 时的优先级顺序是:

1. $APP_ROOT/configs/A/B/C/foo.example.json
2. $APP_ROOT/configs/A/B/foo.example.json
3. $APP_ROOT/configs/A/foo.example.json
4. $APP_ROOT/configs/foo.example.json

依次从上往下,直到找到一个存在的文件为止。当 subpath 为空时,直接读取 $APP_ROOT/configs/foo.example.json。安装到 $DSG\_DATA\_DIR/configs/ 下的配置文件同理。

* 描述文件包含下列属性:
* magic:此 json 文件的标识性信息,所有描述文件均标记为 “dsg.config.meta”
* version:此描述文件的内容格式的版本。版本号使用两位数字描述,首位数字不同的描述文件相互之间不兼容,第二位数字不同的描述文件需满足向下兼容。解析此描述文件的程序要根据版本进行内容分析,当遇到不兼容的版本时,需要立即终止解析,并向使用者报告错误信息,如 “1.0” 和 “2.0” 版本之间不兼容,如果解析程序最高只支持 1.0 版本,则遇到 2.0 版本的描述文件时应该报告错误,但是如果遇到 1.1 版本,则可以继续执行。
* contents:配置项的内容,每一个配置项是一个 json 对象,配置项之间的相对顺序无意义,且不支持嵌套。每一项配置可包含下列属性:
* value:配置项的默认值,可使用 json 支持的各种数据类型,如字符串、数字、数组、对象等
* serial:单调递增的整数值,使用场景:假设程序 “A” 的配置项 “a” 记录其是否已经进行了初始化,之后 “A” 可能更改了初始化相关的代码,需要确保版本更新之后能重新进行初始化,则可以将配置项 “a” 的 “serial” 属性增加 1,则旧版 “A” 程序所记录的配置项 “a” 将失效,以此确保更新 “A” 之后能再次进行初始化工作。此配置项可以省略,无此项时读取配置存储文件将忽略 serial 字段。
* name:配置项的可显示名称,需国际化(使用DTK工具为其生成 ts 文件,ts 编译后的 qm 文件需要与配置描述文件同名同路径放置)。此名称可用于展示到用户界面,如当程序 A 请求通过配置中心读取程序 B 的某个配置项时,将提示用户“程序 A 请求获取程序 B 的"允许退出"配置项的值,是否允许?”,用户可选择拒绝程序 A 的请求,名称在这里的作用是利于用户理解此配置项的含义。
* description:描述此配置项的用途,需国际化(同 name)
* permissions:配置项的权限
* readonly:不允许修改,当程序读取此配置时,将直接使用默认值
* readwrite:可读可写,如果此值被修改过,则不再使用此处定义的默认值
* visibility:配置项的可见性
* private 仅限程序内部使用,对外不可见。此类配置项完全由程序自己读写,可随意增删改写其含义,无需做兼容性考虑
* public 外部程序可使用。__此类配置项一旦发布,在兼容性版本的升级中,要保障此配置项向下兼容,简而言之,只允许在程序/库的大版本升级时才允许删除或修改此类配置项,当配置项的 permissions、visibility、flags 任意一个属性被修改则认为此配置项被修改,除此之外修改 value、name、description 属性时则不需要考虑兼容性__
* flags:配置项的一些特性
* nooverride:存在此标记时,将表明则此配置项不可被覆盖(详见下述 [override 机制](#override))。反之,不存在此标记时表明此配置项允许被覆盖,对于此类配置项,如若其有界面设置入口,则当此项不可写时,应当隐藏或禁用界面的设置入口
* global:当读写此类配置时,将忽略用户身份,无论程序使用哪个用户身份执行,读操作都将获取到同样的数据,写操作将对所有用户都生效。但是,如果对应的配置存储目录不存在或无权限写入,则忽略此标志

$APP_ROOT/configs/foo.example.json 描述文件内容示例:

```json
{
"magic": "dsg.config.meta",
"version": "1.0",
"contents": {
"key1": {
"value": value1,
"time": "2017-07-24T15:46:29",
"name": "允许退出",
"description": "xxxxxxxx",
"permissions": "readwrite",
"visibility": "private",
"flags": ["nooverride"]
}
}
}
```

### override 机制<a name="override"></a>

以 foo.example.json 和 org.dtk.core.json 为例

* override 文件放置路径(按优先级排序):
1. /etc/dsg/configs/overrides/$appid/foo.example/
2. \$DSG\_DATA\_DIR/configs/overrides/$appid/foo.example/
3. /etc/dsg/configs/overrides/org.dtk.core/
4. \$DSG\_DATA\_DIR/configs/overrides/org.dtk.core/

* 同配置描述文件一样支持子目录机制,忽略其描述文件所对应的子目录,从头按规则顺序查找 override 目录
* 对于第 2 和第 4 类路径,其省略了 $appid,放置在此目录下的文件对所有应用程序都生效,表示为所有应用程序提供对 org.dtk.core 配置的覆盖
* `$DSG_XDG_DATA/configs` 下的路径用于放置安装包携带的文件
* `/etc` 下的路径用于放置动态创建的文件,比如用户手动添加,或者域管等程序在运行时创建
* 文件名只允许使用[拉丁字符](https://unicode-table.com/en/blocks/basic-latin/),后缀为 ".json"。使用[自然排序](http://www.naturalordersort.org/)(如“a2”在“a11”之前)规则,按文件名排序,越靠后的配置文件优先级越高。
* 可以覆盖配置项的 "value"、"permissions" 属性

以 /etc/dsg/configs/overrides/foo.example/foo.example/oem1-override.json 为例,可包含以下属性:

* magic:此 json 文件的标识性信息,所有 override 文件均标记为 “dsg.config.override”
* version:此文件的内容格式的版本。版本号使用两位数字描述,首位数字不同的描述文件相互之间不兼容,第二位数字不同的描述文件需满足向下兼容。解析此描述文件的程序要根据版本进行内容分析,当遇到不兼容的版本时,需要立即终止解析,忽略此文件,并在程序日志中写入警告信息,如 “1.0” 和 “2.0” 版本之间不兼容,如果解析程序最高只支持 1.0 版本,则遇到 2.0 版本的描述文件时应该终止解析,但是如果遇到 1.1 版本,则可以继续执行。
* contents:覆盖的配置项的内容,每一项是一个 json 对象,项之间的相对顺序无意义。每一项可包含下列属性:
* value:覆盖配置项的默认值
* serial:覆盖配置项对应的 serial 属性
* comment:描述此 override 行为的注释内容
* permissions:覆盖配置项的权限
* readonly:将配置项覆盖为只读
* readwrite:将配置项覆盖为可读可写

```json
{
"magic": "dsg.config.override",
"version": "1.0",
"contents": {
"key1": {
"value": value1,
"comment": "xxxxxxxx",
"permissions": "readonly"
}
}
}
```

## 配置存储文件

使用 json 作为配置文件的存储格式,以下称为“存储文件”。根据配置项是否携带 global 标志,将分开存储,以 foo.example.json 为例,分别存储在(下列路径用于存储程序的运行时修改的配置内容,请勿往此目录安装任何文件):

* 用户级别:\$HOME/.config/$appid/foo.example.json
* global 级别:\$DSG\_APP_DATA/configs/foo.example.json

* 如果设置 subpath,则只从 subpath 中查找保存的配置文件,不 fallback 到上级目录

foo.example.json 文件格式如下:

* magic:此 json 文件的标识性信息,所有存储文件均标记为 “dsg.config.cache”
* version:此文件的内容格式的版本。版本号使用两位数字描述,首位数字不同的描述文件相互之间不兼容,第二位数字不同的描述文件需满足向下兼容。读取此描述文件的程序要根据版本进行内容分析,当遇到不兼容的版本时,需要立即终止解析,忽略此文件,并在程序日志中写入警告信息,如 “1.0” 和 “2.0” 版本之间不兼容,如果解析程序最高只支持 1.0 版本,则遇到 2.0 版本的描述文件时应该终止解析,但是如果遇到 1.1 版本,则可以继续执行。写入此描述文件时,遇到不兼容的版本时,需要先清空当前内容再写入,每次写入皆需更新此字段。
* contents:保存的配置项的内容,每一项是一个 json 对象,项之间的相对顺序无意义。每一项可包含下列属性:
* value:保存修改后的值
* serial:在使用此配置项的存储值之前,应当先与配置描述文件中定义的 serial 属性做比较(需遵守 override 机制),如果此值不等于配置表述文件中记录的值,否则忽略此处存储的值
* time:记录值的修改时间,使用 UTC 时间,采用 ISO 8601 表示方法
* user:记录修改此项设置的用户名称
* appid:记录修改此项设置的应用程序id,如无法正常获取程序id,需记录二进制文件路径

```json
{
"magic": "dsg.config.cache",
"version": "1.0",
"contents": {
"key1": {
"value": value1,
"time": "2017-07-24T15:46:29",
"user": "user name",
"appid": "foo.example",
"serial": 0
}
}
}
```

## 配置中心

配置中心为上述配置文件的管理服务,对外提供配置项的读写接口。基于 DBus 服务实现,关于 DBus 的规范请查看:<https://dbus.freedesktop.org/doc/dbus-specification.html>。

配置中心需要监听`配置描述文件`和`配置override目录`的变化,当描述文件被修改或override目录新增、移除、修改文件时,需要重新解析对应的文件内容,但是不需要监听`配置存储文件`的变化。

### 与程序的关系

当配置中心的 DBus 服务存在时(需要注意,仅需要它存在,不要求它一定处于运行状态),所有配置项的读写皆要通过此服务进行。反之,可直接基于文件进行配置项的读写操作,无需考虑文件竞争的情况,且无需在修改配置项时通知其他进程。

### 配置中心的 DBus 接口

* 服务名:org.desktopspec.ConfigManager
* 路 径:/org/desktopspec/ConfigManager

```xml
<interface name='org.desktopspec.ConfigManager'>
<method name='acquireManager'>
<arg type='s' name='appid' direction='in'/>
<arg type='s' name='name' direction='in'/>
<arg type='s' name='subpath' direction='in'/>
<arg type='o' name='path' direction='out'/>
</method>
</interface>
<interface name='org.desktopspec.ConfigManager.Manager'>
<property access="read" type="s" name="version"/>
<property access="read" type="as" name="keyList"/>
<property access="read" type="b" name="canRead"/>
<property access="read" type="b" name="canWrite"/>
<property access="read" type="b" name="canOverride"/>
<!--为每个 key 提供一个只读属性,如:
<property access="read" type="v" name="key1"/>
...
<property access="read" type="v" name="key2"/>
-->
<method name='value'>
<arg type='s' name='key' direction='in'/>
<arg type='v' name='value' direction='out'/>
</method>
<method name='setValue'>
<arg type='s' name='key' direction='in'/>
<arg type='v' name='value' direction='in'/>
</method>
<method name='name'>
<arg type='s' name='key' direction='in'/>
<arg type='s' name='language' direction='in'/>
<arg type='s' name='name' direction='out'/>
</method>
<method name='description'>
<arg type='s' name='key' direction='in'/>
<arg type='s' name='language' direction='in'/>
<arg type='s' name='description' direction='out'/>
</method>
<method name='visibility'>
<arg type='s' name='key' direction='in'/>
<arg type='s' name='visibility' direction='out'/>
</method>
<!--采用引用计数的方式,引用为 0 时才真正的销毁-->
<method name='release'>
</method>
<signal name="valueChanged">
<arg name="key" type="s" direction="out"/>'
</signal>
</interface>
```

## API 接口规范

此接口规范定义了开发库所提供的关于配置文件读写的相关接口,如果应用程序所使用的开发库实现了此规范,则程序应当优先使用开发库提供的接口。

### 接口的伪代码

```cpp
// 此规范实现自:https://gitlabwh.uniontech.com/wuhan/se/deepin-specifications/-/blob/master/unstable/配置文件规范.md
class DConfig {
DConfig(string name, string subpath);

property list<string> keyList;

signal valueChanged(string key);

variant value(string key);
void setValue(string key, variant value);
};

```
Loading