|
| 1 | +# 配置文件规范 |
| 2 | + |
| 3 | +> __警告!此规范内容是不稳定版本,可能会发生破坏兼容性的更新。当无法保障向下兼容时,将会升级此文档的主版本号,如从“1.0”更新到“2.0”。反之,普通更新只会升级次版本号,如“1.0”更新到“1.1”,其对“1.0”版本向下兼容。请在使用前确认此文档的版本号,并为将来可能发生的兼容性变化做好准备。__ |
| 4 | +
|
| 5 | + |
| 6 | +* 修改日期:2021.3.17 |
| 7 | +* 版本:1.0 |
| 8 | +* 议题:#3 |
| 9 | + |
| 10 | +## 引言 |
| 11 | + |
| 12 | +本文档规定了配置文件的存储格式和安装路径、配置文件的读写、配置中心的设计、开发库 API 接口等规范。 |
| 13 | + |
| 14 | +## 名词解释 |
| 15 | + |
| 16 | +* 配置描述文件:此类文件由安装包携带,类似于 gsettings 的 schemes 文件,用于描述配置项的元信息,以及携带配置项的默认值 |
| 17 | +* 配置存储文件:对于一些可修改的配置项,本文件用于保存程序运行过程中的改动 |
| 18 | +* $APP_ROOT:应用程序安装的根目录 |
| 19 | +* $appid:应用程序的唯一ID |
| 20 | + |
| 21 | +## 角色说明 |
| 22 | + |
| 23 | +* 应用程序:读写配置文件的实体,亦是配置文件的提供者,一个应用程序可提供多个配置文件 |
| 24 | +* 配置文件:包含一系列配置项的集合,存储了配置项相关的信息 |
| 25 | +* 配置中心:为程序提供读写配置项的 DBus 接口,实现配置项的 override 机制 |
| 26 | +* DTK:应用程序开发基础库,提供统一的配置文件读写工具类 |
| 27 | + |
| 28 | +## 配置描述文件 |
| 29 | + |
| 30 | +配置描述文件使用 json 格式提供,以下将“配置描述文件”简称为“描述文件”。 |
| 31 | + |
| 32 | +* 文件名:$appid.json,如:foo.example.json |
| 33 | +* 安装路径: |
| 34 | + * $APP_ROOT/configs/:用于放置应用程序所携带的描述文件 |
| 35 | + * $DSG\_DATA\_DIR/configs/:用于放置开发库所携带的描述文件,如 $DSG\_DATA\_DIR/configs/org.dtk.core.json,此目录下的配置描述文件为所有程序共享。如果安装到 $DSG\_DATA\_DIR/configs/ 下的配置文件与 $APP_ROOT/configs/ 中的同名(忽略子目录),则使用 $APP_ROOT/configs/ 下的文件 |
| 36 | +* 子目录:描述文件安装可包含子路径。 |
| 37 | + |
| 38 | + 假设 foo.example.json 安装到 “$APP_ROOT/configs/A/B”,当程序读取配置文件时,可额外传入 subpath 参数,假设传入的参数为 “/A/B/C”,则查找 foo.example.json 时的优先级顺序是: |
| 39 | + |
| 40 | + 1. $APP_ROOT/configs/A/B/C/foo.example.json |
| 41 | + 2. $APP_ROOT/configs/A/B/foo.example.json |
| 42 | + 3. $APP_ROOT/configs/A/foo.example.json |
| 43 | + 4. $APP_ROOT/configs/foo.example.json |
| 44 | + |
| 45 | + 依次从上往下,直到找到一个存在的文件为止。当 subpath 为空时,直接读取 $APP_ROOT/configs/foo.example.json。安装到 $DSG\_DATA\_DIR/configs/ 下的配置文件同理。 |
| 46 | + |
| 47 | +* 描述文件包含下列属性: |
| 48 | + * magic:此 json 文件的标识性信息,所有描述文件均标记为 “dsg.config.meta” |
| 49 | + * version:此描述文件的内容格式的版本。版本号使用两位数字描述,首位数字不同的描述文件相互之间不兼容,第二位数字不同的描述文件需满足向下兼容。解析此描述文件的程序要根据版本进行内容分析,当遇到不兼容的版本时,需要立即终止解析,并向使用者报告错误信息,如 “1.0” 和 “2.0” 版本之间不兼容,如果解析程序最高只支持 1.0 版本,则遇到 2.0 版本的描述文件时应该报告错误,但是如果遇到 1.1 版本,则可以继续执行。 |
| 50 | +* contents:配置项的内容,每一个配置项是一个 json 对象,配置项之间的相对顺序无意义,且不支持嵌套。每一项配置可包含下列属性: |
| 51 | + * value:配置项的默认值,可使用 json 支持的各种数据类型,如字符串、数字、数组、对象等 |
| 52 | + * serial:单调递增的整数值,使用场景:假设程序 “A” 的配置项 “a” 记录其是否已经进行了初始化,之后 “A” 可能更改了初始化相关的代码,需要确保版本更新之后能重新进行初始化,则可以将配置项 “a” 的 “serial” 属性增加 1,则旧版 “A” 程序所记录的配置项 “a” 将失效,以此确保更新 “A” 之后能再次进行初始化工作。此配置项可以省略,无此项时读取配置存储文件将忽略 serial 字段。 |
| 53 | + * name:配置项的可显示名称,需国际化(使用DTK工具为其生成 ts 文件,ts 编译后的 qm 文件需要与配置描述文件同名同路径放置)。此名称可用于展示到用户界面,如当程序 A 请求通过配置中心读取程序 B 的某个配置项时,将提示用户“程序 A 请求获取程序 B 的"允许退出"配置项的值,是否允许?”,用户可选择拒绝程序 A 的请求,名称在这里的作用是利于用户理解此配置项的含义。 |
| 54 | + * description:描述此配置项的用途,需国际化(同 name) |
| 55 | + * permissions:配置项的权限 |
| 56 | + * readonly:不允许修改,当程序读取此配置时,将直接使用默认值 |
| 57 | + * readwrite:可读可写,如果此值被修改过,则不再使用此处定义的默认值 |
| 58 | + * visibility:配置项的可见性 |
| 59 | + * private 仅限程序内部使用,对外不可见。此类配置项完全由程序自己读写,可随意增删改写其含义,无需做兼容性考虑 |
| 60 | + * public 外部程序可使用。__此类配置项一旦发布,在兼容性版本的升级中,要保障此配置项向下兼容,简而言之,只允许在程序/库的大版本升级时才允许删除或修改此类配置项,当配置项的 permissions、visibility、flags 任意一个属性被修改则认为此配置项被修改,除此之外修改 value、name、description 属性时则不需要考虑兼容性__ |
| 61 | + * flags:配置项的一些特性 |
| 62 | + * nooverride:存在此标记时,将表明则此配置项不可被覆盖(详见下述 [override 机制](#override))。反之,不存在此标记时表明此配置项允许被覆盖,对于此类配置项,如若其有界面设置入口,则当此项不可写时,应当隐藏或禁用界面的设置入口 |
| 63 | + * global:当读写此类配置时,将忽略用户身份,无论程序使用哪个用户身份执行,读操作都将获取到同样的数据,写操作将对所有用户都生效。但是,如果对应的配置存储目录不存在或无权限写入,则忽略此标志 |
| 64 | + |
| 65 | +$APP_ROOT/configs/foo.example.json 描述文件内容示例: |
| 66 | + |
| 67 | +```json |
| 68 | +{ |
| 69 | + "magic": "dsg.config.meta", |
| 70 | + "version": "1.0", |
| 71 | + "contents": { |
| 72 | + "key1": { |
| 73 | + "value": value1, |
| 74 | + "time": "2017-07-24T15:46:29", |
| 75 | + "name": "允许退出", |
| 76 | + "description": "xxxxxxxx", |
| 77 | + "permissions": "readwrite", |
| 78 | + "visibility": "private", |
| 79 | + "flags": ["nooverride"] |
| 80 | + } |
| 81 | + } |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +### override 机制<a name="override"></a> |
| 86 | + |
| 87 | +以 foo.example.json 和 org.dtk.core.json 为例 |
| 88 | + |
| 89 | +* override 文件放置路径(按优先级排序): |
| 90 | + 1. /etc/dsg/configs/overrides/$appid/foo.example/ |
| 91 | + 2. \$DSG\_DATA\_DIR/configs/overrides/$appid/foo.example/ |
| 92 | + 3. /etc/dsg/configs/overrides/org.dtk.core/ |
| 93 | + 4. \$DSG\_DATA\_DIR/configs/overrides/org.dtk.core/ |
| 94 | + |
| 95 | +* 同配置描述文件一样支持子目录机制,忽略其描述文件所对应的子目录,从头按规则顺序查找 override 目录 |
| 96 | +* 对于第 2 和第 4 类路径,其省略了 $appid,放置在此目录下的文件对所有应用程序都生效,表示为所有应用程序提供对 org.dtk.core 配置的覆盖 |
| 97 | +* `$DSG_XDG_DATA/configs` 下的路径用于放置安装包携带的文件 |
| 98 | +* `/etc` 下的路径用于放置动态创建的文件,比如用户手动添加,或者域管等程序在运行时创建 |
| 99 | +* 文件名只允许使用[拉丁字符](https://unicode-table.com/en/blocks/basic-latin/),后缀为 ".json"。使用[自然排序](http://www.naturalordersort.org/)(如“a2”在“a11”之前)规则,按文件名排序,越靠后的配置文件优先级越高。 |
| 100 | +* 可以覆盖配置项的 "value"、"permissions" 属性 |
| 101 | + |
| 102 | +以 /etc/dsg/configs/overrides/foo.example/foo.example/oem1-override.json 为例,可包含以下属性: |
| 103 | + |
| 104 | +* magic:此 json 文件的标识性信息,所有 override 文件均标记为 “dsg.config.override” |
| 105 | +* version:此文件的内容格式的版本。版本号使用两位数字描述,首位数字不同的描述文件相互之间不兼容,第二位数字不同的描述文件需满足向下兼容。解析此描述文件的程序要根据版本进行内容分析,当遇到不兼容的版本时,需要立即终止解析,忽略此文件,并在程序日志中写入警告信息,如 “1.0” 和 “2.0” 版本之间不兼容,如果解析程序最高只支持 1.0 版本,则遇到 2.0 版本的描述文件时应该终止解析,但是如果遇到 1.1 版本,则可以继续执行。 |
| 106 | +* contents:覆盖的配置项的内容,每一项是一个 json 对象,项之间的相对顺序无意义。每一项可包含下列属性: |
| 107 | + * value:覆盖配置项的默认值 |
| 108 | + * serial:覆盖配置项对应的 serial 属性 |
| 109 | + * comment:描述此 override 行为的注释内容 |
| 110 | + * permissions:覆盖配置项的权限 |
| 111 | + * readonly:将配置项覆盖为只读 |
| 112 | + * readwrite:将配置项覆盖为可读可写 |
| 113 | + |
| 114 | +```json |
| 115 | +{ |
| 116 | + "magic": "dsg.config.override", |
| 117 | + "version": "1.0", |
| 118 | + "contents": { |
| 119 | + "key1": { |
| 120 | + "value": value1, |
| 121 | + "comment": "xxxxxxxx", |
| 122 | + "permissions": "readonly" |
| 123 | + } |
| 124 | + } |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +## 配置存储文件 |
| 129 | + |
| 130 | +使用 json 作为配置文件的存储格式,以下称为“存储文件”。根据配置项是否携带 global 标志,将分开存储,以 foo.example.json 为例,分别存储在(下列路径用于存储程序的运行时修改的配置内容,请勿往此目录安装任何文件): |
| 131 | + |
| 132 | +* 用户级别:\$HOME/.config/$appid/foo.example.json |
| 133 | +* global 级别:\$DSG\_APP_DATA/configs/foo.example.json |
| 134 | + |
| 135 | +* 如果设置 subpath,则只从 subpath 中查找保存的配置文件,不 fallback 到上级目录 |
| 136 | + |
| 137 | +foo.example.json 文件格式如下: |
| 138 | + |
| 139 | +* magic:此 json 文件的标识性信息,所有存储文件均标记为 “dsg.config.cache” |
| 140 | +* version:此文件的内容格式的版本。版本号使用两位数字描述,首位数字不同的描述文件相互之间不兼容,第二位数字不同的描述文件需满足向下兼容。读取此描述文件的程序要根据版本进行内容分析,当遇到不兼容的版本时,需要立即终止解析,忽略此文件,并在程序日志中写入警告信息,如 “1.0” 和 “2.0” 版本之间不兼容,如果解析程序最高只支持 1.0 版本,则遇到 2.0 版本的描述文件时应该终止解析,但是如果遇到 1.1 版本,则可以继续执行。写入此描述文件时,遇到不兼容的版本时,需要先清空当前内容再写入,每次写入皆需更新此字段。 |
| 141 | +* contents:保存的配置项的内容,每一项是一个 json 对象,项之间的相对顺序无意义。每一项可包含下列属性: |
| 142 | + * value:保存修改后的值 |
| 143 | + * serial:在使用此配置项的存储值之前,应当先与配置描述文件中定义的 serial 属性做比较(需遵守 override 机制),如果此值不等于配置表述文件中记录的值,否则忽略此处存储的值 |
| 144 | + * time:记录值的修改时间,使用 UTC 时间,采用 ISO 8601 表示方法 |
| 145 | + * user:记录修改此项设置的用户名称 |
| 146 | + * appid:记录修改此项设置的应用程序id,如无法正常获取程序id,需记录二进制文件路径 |
| 147 | + |
| 148 | +```json |
| 149 | +{ |
| 150 | + "magic": "dsg.config.cache", |
| 151 | + "version": "1.0", |
| 152 | + "contents": { |
| 153 | + "key1": { |
| 154 | + "value": value1, |
| 155 | + "time": "2017-07-24T15:46:29", |
| 156 | + "user": "user name", |
| 157 | + "appid": "foo.example", |
| 158 | + "serial": 0 |
| 159 | + } |
| 160 | + } |
| 161 | +} |
| 162 | +``` |
| 163 | + |
| 164 | +## 配置中心 |
| 165 | + |
| 166 | +配置中心为上述配置文件的管理服务,对外提供配置项的读写接口。基于 DBus 服务实现,关于 DBus 的规范请查看:<https://dbus.freedesktop.org/doc/dbus-specification.html>。 |
| 167 | + |
| 168 | +配置中心需要监听`配置描述文件`和`配置override目录`的变化,当描述文件被修改或override目录新增、移除、修改文件时,需要重新解析对应的文件内容,但是不需要监听`配置存储文件`的变化。 |
| 169 | + |
| 170 | +### 与程序的关系 |
| 171 | + |
| 172 | +当配置中心的 DBus 服务存在时(需要注意,仅需要它存在,不要求它一定处于运行状态),所有配置项的读写皆要通过此服务进行。反之,可直接基于文件进行配置项的读写操作,无需考虑文件竞争的情况,且无需在修改配置项时通知其他进程。 |
| 173 | + |
| 174 | +### 配置中心的 DBus 接口 |
| 175 | + |
| 176 | +* 服务名:org.desktopspec.ConfigManager |
| 177 | +* 路 径:/org/desktopspec/ConfigManager |
| 178 | + |
| 179 | +```xml |
| 180 | +<interface name='org.desktopspec.ConfigManager'> |
| 181 | + <method name='acquireManager'> |
| 182 | + <arg type='s' name='appid' direction='in'/> |
| 183 | + <arg type='s' name='name' direction='in'/> |
| 184 | + <arg type='s' name='subpath' direction='in'/> |
| 185 | + <arg type='o' name='path' direction='out'/> |
| 186 | + </method> |
| 187 | +</interface> |
| 188 | +<interface name='org.desktopspec.ConfigManager.Manager'> |
| 189 | + <property access="read" type="s" name="version"/> |
| 190 | + <property access="read" type="as" name="keyList"/> |
| 191 | + <property access="read" type="b" name="canRead"/> |
| 192 | + <property access="read" type="b" name="canWrite"/> |
| 193 | + <property access="read" type="b" name="canOverride"/> |
| 194 | + <!--为每个 key 提供一个只读属性,如: |
| 195 | + <property access="read" type="v" name="key1"/> |
| 196 | + ... |
| 197 | + <property access="read" type="v" name="key2"/> |
| 198 | + --> |
| 199 | + <method name='value'> |
| 200 | + <arg type='s' name='key' direction='in'/> |
| 201 | + <arg type='v' name='value' direction='out'/> |
| 202 | + </method> |
| 203 | + <method name='setValue'> |
| 204 | + <arg type='s' name='key' direction='in'/> |
| 205 | + <arg type='v' name='value' direction='in'/> |
| 206 | + </method> |
| 207 | + <method name='name'> |
| 208 | + <arg type='s' name='key' direction='in'/> |
| 209 | + <arg type='s' name='language' direction='in'/> |
| 210 | + <arg type='s' name='name' direction='out'/> |
| 211 | + </method> |
| 212 | + <method name='description'> |
| 213 | + <arg type='s' name='key' direction='in'/> |
| 214 | + <arg type='s' name='language' direction='in'/> |
| 215 | + <arg type='s' name='description' direction='out'/> |
| 216 | + </method> |
| 217 | + <method name='visibility'> |
| 218 | + <arg type='s' name='key' direction='in'/> |
| 219 | + <arg type='s' name='visibility' direction='out'/> |
| 220 | + </method> |
| 221 | + <!--采用引用计数的方式,引用为 0 时才真正的销毁--> |
| 222 | + <method name='release'> |
| 223 | + </method> |
| 224 | + <signal name="valueChanged"> |
| 225 | + <arg name="key" type="s" direction="out"/>' |
| 226 | + </signal> |
| 227 | +</interface> |
| 228 | +``` |
| 229 | + |
| 230 | +## API 接口规范 |
| 231 | + |
| 232 | +此接口规范定义了开发库所提供的关于配置文件读写的相关接口,如果应用程序所使用的开发库实现了此规范,则程序应当优先使用开发库提供的接口。 |
| 233 | + |
| 234 | +### 接口的伪代码 |
| 235 | + |
| 236 | +```cpp |
| 237 | +// 此规范实现自:https://gitlabwh.uniontech.com/wuhan/se/deepin-specifications/-/blob/master/unstable/配置文件规范.md |
| 238 | +class DConfig { |
| 239 | + DConfig(string name, string subpath); |
| 240 | + |
| 241 | + property list<string> keyList; |
| 242 | + |
| 243 | + signal valueChanged(string key); |
| 244 | + |
| 245 | + variant value(string key); |
| 246 | + void setValue(string key, variant value); |
| 247 | +}; |
| 248 | + |
| 249 | +``` |
0 commit comments