diff --git "a/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/README.md" "b/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/README.md" new file mode 100644 index 0000000..39309dc --- /dev/null +++ "b/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/README.md" @@ -0,0 +1,80 @@ +### 任务描述 + +`GSettings` 是一套可使用多个 `storage backends` 的 `API` ,默认使用 `dconf` 作为 `backend` 。`GSettings` 的配置文件是 `xml` 格式的,文件需以 `.gschema.xml` 结尾,文件名通常与 `id` 相同。配置文件安装在 `/usr/share/glib-2.0/schemas/` 目录下,手动添加进去的文件需要执行 `glib-complie-schemas` 命令让其生效。 + +`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) + +### 联系方式 + +此任务的任务对接人为: yeshanshan@uniontech.com + diff --git "a/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/\351\205\215\347\275\256\346\226\207\344\273\266\350\247\204\350\214\203.md" "b/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/\351\205\215\347\275\256\346\226\207\344\273\266\350\247\204\350\214\203.md" new file mode 100644 index 0000000..c506aaf --- /dev/null +++ "b/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/\351\205\215\347\275\256\346\226\207\344\273\266\350\247\204\350\214\203.md" @@ -0,0 +1,249 @@ +# 配置文件规范 + +> __警告!此规范内容是不稳定版本,可能会发生破坏兼容性的更新。当无法保障向下兼容时,将会升级此文档的主版本号,如从“1.0”更新到“2.0”。反之,普通更新只会升级次版本号,如“1.0”更新到“1.1”,其对“1.0”版本向下兼容。请在使用前确认此文档的版本号,并为将来可能发生的兼容性变化做好准备。__ + +* 维护者:zccrs +* 修改日期: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 机制 + +以 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 的规范请查看:。 + +配置中心需要监听`配置描述文件`和`配置override目录`的变化,当描述文件被修改或override目录新增、移除、修改文件时,需要重新解析对应的文件内容,但是不需要监听`配置存储文件`的变化。 + +### 与程序的关系 + +当配置中心的 DBus 服务存在时(需要注意,仅需要它存在,不要求它一定处于运行状态),所有配置项的读写皆要通过此服务进行。反之,可直接基于文件进行配置项的读写操作,无需考虑文件竞争的情况,且无需在修改配置项时通知其他进程。 + +### 配置中心的 DBus 接口 + +* 服务名:org.desktopspec.ConfigManager +* 路 径:/org/desktopspec/ConfigManager + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + + +``` + +## API 接口规范 + +此接口规范定义了开发库所提供的关于配置文件读写的相关接口,如果应用程序所使用的开发库实现了此规范,则程序应当优先使用开发库提供的接口。 + +### 接口的伪代码 + +```cpp +// 此规范实现自:https://gitlabwh.uniontech.com/wuhan/se/deepin-specifications/-/blob/master/unstable/配置文件规范.md +class DConfig { + DConfig(string name, string subpath); + + property list keyList; + + signal valueChanged(string key); + + variant value(string key); + void setValue(string key, variant value); +}; + +``` diff --git "a/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/\351\205\215\347\275\256\347\255\226\347\225\245\344\273\213\347\273\215.md" "b/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/\351\205\215\347\275\256\347\255\226\347\225\245\344\273\213\347\273\215.md" new file mode 100644 index 0000000..157a8db --- /dev/null +++ "b/2.\350\277\233\351\230\266\351\232\276\345\272\246/[\345\274\200\345\217\221]gsettings\351\205\215\347\275\256\350\275\254dconfig\351\205\215\347\275\256\345\267\245\345\205\267/\351\205\215\347\275\256\347\255\226\347\225\245\344\273\213\347\273\215.md" @@ -0,0 +1,544 @@ +#配置策略使用说明 + +##配置介绍 + +配置策略涉及主要包括配置描述文件(meta)、配置存储文件(cache)、覆盖机制配置文件(override), +应用需要配置的为meta和override(可选)文件,它们均为json格式的文件。 + +### meta + +配置描述文件:由应用安装时部署,用于描述配置项的元信息,以及携带配置项的默认值,标志信息为 "magic": "dsg.config.meta"。 + +- 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 机制)。反之,不存在此标记时表明此配置项允许被覆盖,对于此类配置项,如若其有界面设置入口,则当此项不可写时,应当隐藏或禁用界面的设置入口。 + - global:当读写此类配置时,将忽略用户身份,无论程序使用哪个用户身份执行,读操作都将获取到同样的数据,写操作将对所有用户都生效。但是,如果对应的配置存储目录不存在或无权限写入,则忽略此标志。 + +### override + +override 文件:当描述文件中的flags没有nooverride标记时,override目录下的配置可覆盖配置项的同名属性,标志信息为 "magic": "dsg.config.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:将配置项覆盖为可读可写 + +### cache + +配置存储文件:对于一些可修改的配置项,此文件用于保存程序运行过程中的改动,根据配置项是否携带 global 标志,将分开存储,标志信息为 "magic": "dsg.config.cache"。 + +- 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,需记录二进制文件路径 + +## 配置规范 + +### 变量说明 + +*${appid}: 应用ID +*${configuration_id}:配置描述文件ID,值为配置描述文件名 +*${subpath}: 子路径 +*${override_id}: override文件ID,值为override文件名 + +### 配置文件命名 + +应用需要设置的配置文件分为两类,应用配置文件和公共库配置文件,它们均以`.json`为后缀,utf8格式编码文件名组成有所不同。 + +*配置描述文件,文件名分为两部分组成,一部分包含共库所属组织ID、开发库或应用ID,另一部分为配置文件含义,当无特殊含义时,这部分可以省略,中间以`.`连接。例如dtkcore开发库的配置文件: 通用配置时文件名可以使用 org.deepin.dtk.core.json, 如果此配置文件仅用来控制单一的功能,可在名称中得以体现,例如控制DTK程序的日志输出规则的配置项,可以使用 org.deepin.dtk.core.log.json 作为配置文件名称;dde-dock应用的公共配置文件名可以使用org.deepin.dock.json,若仅控制管理窗口信息的配置文件,配置文件名可以使用org.deepin.dock.window.json。 +*override文件,文件名只允许使用[拉丁字符](https://unicode-table.com/en/blocks/basic-latin/)。使用[自然排序](http://www.naturalordersort.org/)(如“a2”在“a11”之前)规则,按文件名排序,越靠后的配置文件优先级越高。 +*子路径,子路径推荐以`下划线命名法`命名。 + +----------------- + +### 配置文件存储路径 + +配置文件相关的路径按文件种类分为三类,其中配置描述文件和override文件需要应用配置,安装到指定位置,配置存储文件是配置策略服务自行指定的。 + +#### 配置描述文件 + +配置描述文件按功能分为应用文件和公共库文件,均安装在/usr/share/dsg/configs/目录下。 + +*应用配置描述文件: + + * /usr/share/dsg/configs/${appid}/[${subpath}]/${configuration_id}.json。例如dock应用的配置描述文件放置在/usr/share/dsg/configs/dock/org.deepin.dock.json,若有针对dtkcore开发库的log覆盖配置描述文件,可以放置在/usr/share/dsg/config/dock/org.deepin.dtk.core.log.json。 + +*开发库配置描述文件: + + * /usr/share/dsg/configs/[${subpath}]/${configuration_id}.json。例如dtkcore开发库的配置描述文件放置在/usr/share/dsg/config/org.deepin.dtk.core.json, 针对log配置的配置描述文件放置在/usr/share/dsg/config/org.deepin.dtk.core.log.json。 + +#### override文件 + +override文件安装在以下路径,override文件查找机制顺序优先级排序。 + +1. /etc/dsg/configs/overrides/${appid}/${configuration_id}/[${subpath}]/${override_id}.json +2. /usr/share/dsg/configs/overrides/${appid}/${configuration_id}/[${subpath}]/${override_id}.json +3. /etc/dsg/configs/overrides/${configuration_id}/[${subpath}]/${override_id}.json +4. /usr/share/dsg/configs/overrides/${configuration_id}/[${subpath}]/${override_id}.json + +/usr用于于放置安装包携带的文件 ,/etc 下的路径用于放置动态创建的文件,比如用户手动添加,或者域管等程序在运行时创建。 + +例如,通过override机制,当需要覆盖dock应用的org.deepin.dock.window.json时,我们可以放置/etc/dsg/configs/overrides/dock/org.deepin.dock.window/window.json文件,window.json会尝试覆盖org.deepin.dock.window.json配置描述文件里相同的配置项。 + +#### 配置存储文件 + +*user级别的配置项所处的配置存储文件:$HOME/.config/dsg/configs/${appid}/${configuration_id}.json +*global级别的配置项所处的配置存储文件:/var/dsg/appdata/configs/${configuration_id}.json + +### 配置项各字段 + +*`key`命名以`小驼峰法标识`命名,否则在某些场景下导致此项会无法正常使用。e.g: showWindowName。 +*`value`默认值与所属类型一致,当更新时与默认值类型不一致时,会判断类型是否兼容,做隐式转换。e.g:字符串类型: "value": "", bool类型: "value": false, int,double类型: "value": 1.0,数组类型: "value": [], 键值对类型: "value": {}。 +*`name`和`description`,建议设默认为中文, e.g: "name": "我是名字",中文:"name[zh_CN]": "我是名字", 英文:"name[en_US]": "I am name"。 +*`visibility`按最小权限为标准,默认权限设置为"private"类型,e.g: "visibility": "private" +*`flags`默认设为允许被覆盖,不确定是否是全局配置时,不要添加"global"标志,建议设为允许被覆盖,e.g: "flags": []。 +*`permissions`,不确定是否只读时,建议设为允许读写, e.g: "permissions": "readwrite" + +##前端使用 + +### 配置项定义 + +#### 定义meta + +- 示例:$Repo/configs/dconf-example.json +```json +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "canExit": { + "value": true, + "serial": 0, + "flags": ["global"], + "name": "I am name", + "name[zh_CN]": "我是名字", + "description": "I am description", + "permissions": "readwrite", + "visibility": "private" + }, + "key2": { + "value": "125", + "serial": 0, + "flags": ["nooverride"], + "name": "I am name", + "name[zh_CN]": "我是名字", + "description": "I am description", + "permissions": "readwrite", + "visibility": "public" + }, + "key3": { + "value": "application", + "serial": 0, + "flags": ["global"], + "name": "I am name", + "name[zh_CN]": "我是名字", + "description": "I am description", + "permissions": "readwrite", + "visibility": "public" + } + } +} +``` + +#### 定义override + +- 示例:$Repo/configs/a/dconf-example.override.a.json +```json +{ + "magic": "dsg.config.override", + "version": "1.0", + "contents": { + "key3": { + "value": "override /a", + "serial": 0, + "permissions": "readwrite" + } + } +} +``` + +### 安装路径 + +使用dtkcommon提供的prf变量或cmake函数来指定部署位置,使用`sudo make install`安装到约定位置. + +#### 部署meta + +可部署多个meta类型的配置描述文件,其中【base】可选,【appid】和【commonid】设置其中一项,当【commonid】设置为非空时,meta作为公共库描述文件使用,作用于所有的应用。 + +##### qmake工程管理 + +```pro + +#files - deployed files. +#base - used to get subpath, if it's empty, only copy files, and ignore it's subpath structure. +#appid - working for the app, if it's empty, depending on `TEMPLATE`. +#commonid - working for common, if it's empyt, depending on `TEMPLATE`. + +#/a为子目录 + +meta_file.files += \ + configs/example.json \ + configs/a/example.json + +meta_file.base = $$PWD/configs + +DCONFIG_META_FILES += meta_file +load(dtk_install_dconfig) +``` + +##### cmake工程管理 + +```CMakeLists.txt + +#FILES - deployed files. +#BASE - used to get subpath, if it's empty, only copy files, and ignore it's subpath structure. +#APPID - working for the app. +#COMMONID - working for common. + +#/a为子目录 + +dconfig_meta_files(APPID dconfigexample BASE ./configs FILES ./configs/example.json ./configs/a/example.json) +``` + +####部署override + +可部署多个override类型的覆盖文件,其中【base】、【appid】可选,当【base】为空时,没有子目录结构。 + +#####qmake工程管理 + +```pro + +#files - deployed files. +#base - used to get subpath, if it's empty, only copy files, and ignore it's subpath structure. +#appid - working for the app, if it's empty, working for all app. +#meta_name - override for the meta configure. + +#/a/b为子目录 + +override_file.files += \ + configs/dconf-example.override.json \ + configs/a/dconf-example.override.a.json \ + configs/a/b/dconf-example.override.a.b.json + +override_file.base = $$PWD/configs +override_file.meta_name = example +override_file.appid = $$TARGET + +DCONFIG_OVERRIDE_FILES += override_file +load(dtk_install_dconfig) +``` + +#####cmake工程管理 + +```CMakeLists.txt + +#FILES - deployed files. +#BASE - used to get subpath, if it's empty, only copy files, and ignore it's subpath structure. +#APPID - working for the app, if it's empty, working for all app. +#META_NAME - override for the meta configure. + +#/a/b为子目录 + +dconfig_override_files(APPID dconfigexample BASE ./configs META_NAME example FILES ./configs/dconf-example.override.json ./configs/a/dconf-example.override.a.json) +``` + +###开发库API接口 + +通过DTK::Core::DConfig类来使用配置策略。 +```c++ +// 构造DConfig +DConfig config(fileName); + +// 判断是否有效 +if (!config.isValid()) { + qWarning() << QString("DConfig is invalide, name:[%1], subpath[%2].").arg(config.name(), config.subpath()); + return; +} + +// 获取所有配置项的key +const QStringList &keyList = config.keyList(); + +// 获取指定配置项的值 +const bool value = config.value("canExit").toBool(); + +// 设置值指定配置项的值 +config.setValue("canExit", false); + +// 监听值改变的信号 +QObject::connect(&config, &DConfig::valueChanged, [](const QString &key){}); +``` + +***DConfig是在析构时保存设置修改的缓存信息,只有DConfig正常析构, 缓存信息才能写入磁盘*** + +## 后端使用 + +### 配置项定义 + +与前端使用配置项相同 + +### 安装路径 + +无法使用dtkcommon提供的默认安装路径,按约定放置在特定路径下,`[]`表示可选, +可参照libdtkcommon-dev的`DtkInstallDConfigConfig.cmake`或`dtk_install_dconfig.prf`实现 + +#### meta + +- 应用: /usr/share/dsg/apps/应用名/configs/[子目录名]/配置文件名.json, +之后切换到目录:opt/apps/应用名/files/schemas/configs/[子目录名]/配置文件名.json。 + +- 公共库: $DSG_DATA_DIR/configs/[子目录名]/配置文件名.json + +***当公共库与应用的配置文件同名,则用应用配置文件*** + +***$DSG_DATA_DIR值为空时,默认为`/usr/share/dsg`*** + +#### override + +- $DSG_DATA_DIR/configs/overrides/[应用名]/配置文件名/[子目录名]/override文件名.json + +#### cache + +cache路径不需要配置,这里只是说明cache将要保存的位置。 + +- user:$HOME/.config/应用名/配置文件名.json +- global:$DSG_APP_DATA/configs/配置文件名.json + +### 开发库API接口 + +只能通过 `dbus` 协议访问配置中心来访问配置策略, +访问配置项需要两步,先调用`acquireManager`获得`dbus配置路径`,再通过`dbus资源路径`访问具体配置项。 + +- 资源管理接口 +```xml + + + + + + + + + + + +``` + +- 资源访问 +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ' + + +``` + +- `dbus-send` 访问配置中心 + +`dbus-send` 使用 `acquireManager` 返回资源路径时,进程退出,导致资源被立即释放,需要开启配置中心延迟释放资源功能(设置-t选项,`dconfig-daemon -t 100`,会延迟 100ms 释放资源)。 + +``` bash + #!/bin/bash + + #acquireManager + DCONFIG_RESOURCE_PATH=$(dbus-send --system --type=method_call --print-reply=literal --dest=org.desktopspec.ConfigManager / org.desktopspec.ConfigManager.acquireManager string:'dconfigclient' string:'example' string:'')'' + + #object path + echo path: $DCONFIG_RESOURCE_PATH + + #monitor valueChanged + dbus-monitor --system "type='signal', interface='org.desktopspec.ConfigManager.Manager',member=valueChanged" & + + #description + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.description string:'canExit' string: + + #name默认语言 + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.name string:'canExit' string: + + #name中文 + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.name string:'canExit' string:'zh_CN' + + #visibility + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.visibility string:'canExit' + + #permissions + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.permissions string:'canExit' + + #value + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.value string:'canExit' + + #setValue + PROPERTY_VALUE=$(dbus-send --system --type=method_call --print-reply=literal --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.value string:'canExit') + if [[ "$PROPERTY_VALUE" = *true ]] ;then + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.setValue string:'canExit' variant:boolean:false + else + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.setValue string:'canExit' variant:boolean:true + fi + + #value + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.value string:'canExit' + + #update + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager / org.desktopspec.ConfigManager.update string:'/usr/share/dsg/apps/dconfig-example/configs/example.json' + + #release + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager $DCONFIG_RESOURCE_PATH org.desktopspec.ConfigManager.Manager.release + + jobs -p |xargs kill + ``` + +##dde-config + +dde-config 是一个命令行工具,主要用于通过命令行的方式,浏览和设置应用的配置项。dde-dconfig-editor工具的复制和导出的命令都是通过执行该工具完成的。 +安装 dconfig-daemon 的时候会自动安装该工具。 + +该工具的 Options 可以通过 --help 查看,其中三个常用命令行(以 dconfig-example 举例): + +- 设置配置项: dde-dconfig --set -a dconfig-example -r example -k advance.cursor.radiogroup -v 0 +- 查看配置项: dde-dconfig --get -a dconfig-example -r example -k advance.cursor.radiogroup +- 浏览应用所有配置: dde-dconfig --list -a dconfig-example +- 启动 dde-dconfig-editor: dde-dconfig --gui + +(-a 应用名 -r 资源名 -k 配置项的key -v 配置项的value) + +##安装依赖包 + +- 安装依赖开发包,`dde-dconfig-daemon`为配置中心,提供dbus接口 +```bash +sudo apt install libdtkcommon libdtkcore5 libdtkcore-dev dde-dconfig-daemon +``` + +- 工具包来编辑配置项[可选] +```bash +sudo apt install dde-dconfig-editor +``` + +##dpkg安装更新 + +制作一个deb trigger,在更新安装包时,可不重启配置中心来更新对应的配置资源服务。在包里$Repo/debian/postinst脚本添加dbus请求,来更新配置中心的对应配置资源服务。 + +```postinst + #!/bin/bash + +#`/usr/share/dsg/apps/dconfig-example/configs/example.json`为需要更新的配置描述文件. + + dbus-send --system --type=method_call --print-reply --dest=org.desktopspec.ConfigManager / org.desktopspec.ConfigManager.update string:'/usr/share/dsg/apps/dconfig-example/configs/example.json' +``` + +##与 DSettings 的区别 + +`DSettings` 虽然也是通过 `json` 作为配置文件,也有 `set/get` 和设置后端 `backend` 等接口,但是 `DSettings` 主要是设计来给 `DSettingsDialog` 用的,通过 `json` 来生成设置对话框的一种工具类,如果需要保存这些配置需要自行管理配置文件。而 `DConfig` 作为配置策略规范的实现,为了统一管理系统中应用的配置项而生,使用这个来配置应用之后,可以方便统一管理系统中各应用的配置项,按照规范放置配置文件后,应用只需关心 `DConfig` 这个类,并且还支持 `subpath` 和 `override` 机制方便定制。 + +###已经使用了DSettings 的应用如何迁移 + +- 按照本文中描述增加 `meta` 或者 `override` 配置文件。 +- 在 `DSettings` 的 `backend` 中做适配将原来设置和获取的接口。 + - `dtkcore` 将提供一个 `DSettingsDConfigBackend` ,应用可以使用这个。 + - 配置数据迁移,当启动时发现有旧的配置文件时先将配置都获取出来,然后设置到 `DSettingsDConfigBackend` 中,迁移完之后删除旧的配置文件即可。 + +可以查看下方参考文档里面“使用的demo”的例子。 + +##如何OEM + +通常OEM定制镜像中客户可能会要求一些应用功能默认关闭或者开启,值得注意的时这种定制只会影响初始行为,也就是镜像安装之后首次启动的默认行为,之后使用者的手动改变配置行为不在本次讨论范围之类。针对需要做OEM定制的客户,可以将一些需要定制的配置作为 `override` 项预装到系统镜像中,这样应用启动时默认行为会优先从 `override` 配置项中来读取。举例说明: 应用A可以配置启动时是正常大小、最大化或者全屏,uos镜像中默认时正常大小,有客户希望OEM镜像中打开时默认最大化并且无法修改,此时就可以通过国 `override` 这个配置来实现,将默认配置 `onstart` 由 `window_normal` 重载为 `window_maximum` ,并将 `permissions` 由 `readwrite` 重载为 `readonly`, 这样之后在定制镜像中应用打开就是最大化,并且就算设置了正常窗口也不会更改配置,这里应用还可以根据是否 `readonly` 禁用控件。 + + +```json +meta 配置 +{ + "magic": "dsg.config.meta", + "version": "1.0", + "contents": { + "window": { + "onstart": "window_normal", + "name": "window state on start", + "name[zh_CN]": "启动时窗口状态", + "description": "此配置可以修改应用启动时的窗口大小,如正常窗口、最大化或者全屏", + "permissions": "readwrite" + } + } +} + +定制 override +{ + "magic": "dsg.config.override", + "version": "1.0", + "contents": { + "window": { + "onstart": "window_maximum", + "name": "window state on start", + "name[zh_CN]": "启动时窗口状态", + "description": "此配置可以修改应用启动时的窗口大小,如正常窗口、最大化或者全屏", + "permissions": "readonly" + } + } +} +``` + +## 临时问题 + +- 应用使用`DConfig`接口时和单元测试测试使用了`DConfig`接口时,传递的`appid`为qApp的applicationName,当appid与应用名称不一致时,需要设置应用名称为appid。 +```c++ + qApp->setApplicationName(appid); + DConfig handler(configuration_id); +``` + 或者 +```c++ + DConfig *handler = Dconfig::create(appid, configuration_id); +``` +