storage.tin 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. #nop vim: set filetype=tt:;
  2. /*
  3. 本文件属于 PaoTin++ 的一部分。
  4. PaoTin++ © 2020~2026 的所有版权均由担子炮(dzp <danzipao@gmail.com>) 享有并保留一切法律权利
  5. 你可以在遵照 GPLv3 协议的基础之上使用、修改及重新分发本程序。
  6. */
  7. ///=== {
  8. ///// storage 模块实现了一个通用的本地存储引擎。
  9. ///// 可以用来存储和载入变量,这允许其它模块可以持久化自己的数据。
  10. ///// 变量会被存储在本地文件系统当中,称之为「存储文件」。
  11. /////
  12. ///// 存储文件按规定会统一存放在 data 目录下,支持文件重定位。也就是说,
  13. ///// 对于存储文件 file1 来说,其可能的物理存放位置为:
  14. ///// 1. var/data/file1.tin (优先)
  15. ///// 2. data/file1.tin (其次)
  16. /////
  17. ///// storage 支持的存储有两大维度:
  18. ///// - 存储形式
  19. ///// - 文件式(Save/Load): 按文件组织多个变量。
  20. ///// - 键值式(Set/Get): 把琐碎的数据集中存到同一个文件里,不必单独建文件。
  21. ///// - 作用域
  22. ///// - share: 跨角色共享。
  23. ///// - user: 按角色 ID 隔离,存 data/<角色ID>/ 下。
  24. /////
  25. ///// 两大维度正交,组合成四组 API:
  26. ///// - 文件式 share: storage.Save / storage.Load
  27. ///// - 文件式 user : storage.SaveUser / storage.LoadUser
  28. ///// - 键值式 share: storage.Set / storage.Get
  29. ///// - 键值式 user : storage.SetUser / storage.GetUser
  30. /////
  31. ///// 各模块可以自行决定变量的存放位置,通常来说,对于较大的变量,建议通过文件式 API,
  32. ///// 也就是 storage.Save/SaveUser 来创建自己模块专属的存储文件,并在需要时通过读取
  33. ///// API storage.Load/LoadUser 提取这些变量。每个存储文件可以存储一个或多个变量。
  34. /////
  35. ///// 而那些较为琐碎的存储项,通常是一些较小的变量,storage 模块自行维护了一组全局收
  36. ///// 纳文件(data/storage.tin),各模块可通过键值式 API 来读写它们,更为方便。
  37. /////
  38. ///// NOTE: 旧 API storage.SetGlobal/storage.GetGlobal 与 storage.Set/storage.Get 等
  39. ///// 价,目前已废弃,但暂时保留以兼容既有代码,请各模块作者尽快更换。
  40. ///// };
  41. #var lib_storage[META] {
  42. {NAME} {通用存储引擎}
  43. {DESC} {可以存储和载入变量,这允许其它模块可以持久化自己的数据}
  44. {AUTHOR} {担子炮}
  45. };
  46. VAR {存储路径} storage-path {data};
  47. VAR {跨角色共享存储项} storage-globals {};
  48. VAR {角色私有存储项} storage-user-globals {};
  49. #func {lib_storage.Init} {
  50. #local _ {@mkdir{data}};
  51. #local files {};
  52. #line quiet #scan dir {var/data/} files;
  53. #if { &files[] > 0 } {
  54. #var storage-path {var/data};
  55. };
  56. storage.Load {storage} {storage-globals};
  57. #return {true};
  58. };
  59. #nop 内部函数,计算指定作用域的存储文件路径。user 作用域需要已登录的角色 ID。;
  60. #func {storage.path} {
  61. #local scope {%1};
  62. #local file {%2};
  63. #replace file {.tin$} {};
  64. #cat file {.tin};
  65. #if { "$scope" != "{share|user}" } {
  66. errLog storage.path 参数错误,scope 只能为 share 或 user。;
  67. #return {};
  68. };
  69. #if { "$scope" == "share" } {
  70. #return {$storage-path/$file};
  71. };
  72. #if { "$user[id]" == "" } {
  73. errLog 尚未登录角色,无法操作 user 作用域的存储文件。;
  74. #return {};
  75. };
  76. #local _ {@mkdir{$storage-path/$user[id]}};
  77. #return {$storage-path/$user[id]/$file};
  78. };
  79. #nop 内部命令,将指定变量列表存储到给定的存储文件中。;
  80. #alias {storage.dump} {
  81. #local vars {%1};
  82. #local path {%2};
  83. #class comm-store-tmp open;
  84. #local var {};
  85. #foreach {$vars} {var} {
  86. #local count {&{${var}[]}};
  87. #if { $count < 100 } {
  88. #var {dump-$var} {${$var}};
  89. dbgLog storage 变量 $var 已写入磁盘。共有 @math.Max{$count;1} 个数据项。;
  90. #continue;
  91. };
  92. #local idx {0};
  93. #loop 1 {$count} {idx} {
  94. #local key {*{${var}[+$idx]}};
  95. #local value {${${var}[+$idx]}};
  96. #var {dump-${var}[$key]} {$value};
  97. };
  98. dbgLog storage 变量 $var 已写入磁盘。共有 $count 个数据项。;
  99. };
  100. #class comm-store-tmp close;
  101. #class comm-store-tmp write {$path};
  102. #class comm-store-tmp kill;
  103. };
  104. #nop 内部命令,从给定的存储文件中加载变量到指定变量列表中。
  105. #nop 存储文件不存在时静默跳过,不会报错。;
  106. #alias {storage.load} {
  107. #local path {%1};
  108. #local vars {%2};
  109. #if { !@existsFile{$path} } {
  110. #return;
  111. };
  112. #line quiet #class comm-store-tmp {assign} {load-file $path};
  113. #local var {};
  114. #foreach {$vars} {var} {
  115. #local count {&{dump-${var}[]}};
  116. #if { $count < 100 } {
  117. #var {$var} {${dump-$var}};
  118. dbgLog storage 已从磁盘中加载变量 $var,共有 @math.Max{$count;1} 个数据项。;
  119. #continue;
  120. };
  121. #local idx {0};
  122. #loop 1 {$count} {idx} {
  123. #local key {*{dump-${var}[+$idx]}};
  124. #local value {${dump-${var}[+$idx]}};
  125. #var {${var}[$key]} {$value};
  126. };
  127. dbgLog storage 已从磁盘中加载变量 $var,共有 $count 个数据项。;
  128. };
  129. #class comm-store-tmp kill;
  130. };
  131. ///=== {
  132. // ## storage.Save <文件名> <变量名1> [...]
  133. // 将由变量名列表所指定的变量及其值存储到指定的存储文件中。
  134. // 存储文件的路径为 share 作用域,所有角色共享同一份数据。
  135. // 例子: storage.Save {foo} {my-var1;my-var2};
  136. // 注意文件名可以省略且建议省略 .tin 后缀。
  137. // };
  138. #alias {storage.Save} {
  139. #local file {%1};
  140. #local vars {%2};
  141. #if { "$file" == "" || "$vars" == "" } {
  142. xtt.Usage storage.Save;
  143. #return;
  144. };
  145. storage.dump {$vars} {@storage.path{share;$file}};
  146. };
  147. ///=== {
  148. // ## storage.Load <文件名> <变量名1> [...]
  149. // 从指定的存储文件中加载变量。
  150. // 存储文件的路径为 share 作用域,所有角色共享同一份数据。
  151. // 存储文件中实际存储的变量可能更多一些,但本函数可以只加载其中一部分变量。
  152. // 例子: storage.Load {foo} {my-var1;my-var2};
  153. // 注意文件名可以省略且建议省略 .tin 后缀。
  154. // };
  155. #alias {storage.Load} {
  156. #local file {%1};
  157. #local vars {%2};
  158. #if { "$file" == "" || "$vars" == "" } {
  159. xtt.Usage storage.Load;
  160. #return;
  161. };
  162. storage.load {@storage.path{share;$file}} {$vars};
  163. };
  164. ///=== {
  165. // ## storage.SaveUser <文件名> <变量名1> [...]
  166. // 将由变量名列表所指定的变量及其值存储到当前角色的私有存储文件中。
  167. // 存储文件的路径为 user 作用域,位于 data/<角色ID>/ 目录下,各角色相互隔离。
  168. // 本命令需要登录完成、获得角色 ID 之后才能使用,否则报错拒绝。
  169. // 例子: storage.SaveUser {foo} {my-var1;my-var2};
  170. // 注意文件名可以省略且建议省略 .tin 后缀。
  171. // };
  172. #alias {storage.SaveUser} {
  173. #local file {%1};
  174. #local vars {%2};
  175. #if { "$file" == "" || "$vars" == "" } {
  176. xtt.Usage storage.SaveUser;
  177. #return;
  178. };
  179. #if { "$user[id]" == "" } {
  180. errLog storage 尚未登录角色,无法使用 storage.SaveUser。;
  181. #return;
  182. };
  183. storage.dump {$vars} {@storage.path{user;$file}};
  184. };
  185. ///=== {
  186. // ## storage.LoadUser <文件名> <变量名1> [...]
  187. // 从当前角色的私有存储文件中加载变量。
  188. // 存储文件的路径为 user 作用域,位于 data/<角色ID>/ 目录下,各角色相互隔离。
  189. // 本命令需要登录完成、获得角色 ID 之后才能使用,否则报错拒绝。
  190. // 存储文件中实际存储的变量可能更多一些,但本函数可以只加载其中一部分变量。
  191. // 例子: storage.LoadUser {foo} {my-var1;my-var2};
  192. // 注意文件名可以省略且建议省略 .tin 后缀。
  193. // };
  194. #alias {storage.LoadUser} {
  195. #local file {%1};
  196. #local vars {%2};
  197. #if { "$file" == "" || "$vars" == "" } {
  198. xtt.Usage storage.LoadUser;
  199. #return;
  200. };
  201. #if { "$user[id]" == "" } {
  202. errLog storage 尚未登录角色,无法使用 storage.LoadUser。;
  203. #return;
  204. };
  205. storage.load {@storage.path{user;$file}} {$vars};
  206. };
  207. ///=== {
  208. // ## storage.Set <KEY> [<值>]
  209. // 将值关联到 KEY 上,并存储到全局存储文件中。
  210. // 本命令与 storage.SetUser 相对,读写的是 share 作用域的键值存储。
  211. // 参见 storage.Get
  212. // };
  213. #alias {storage.Set} {
  214. #local key {%1};
  215. #local value {%2};
  216. #if { "$key" == "" } {
  217. xtt.Usage storage.Set;
  218. #return;
  219. };
  220. #var storage-globals[$key] {$value};
  221. storage.Save {storage} {storage-globals};
  222. dbgLog storage 全局存储项已写入磁盘。;
  223. };
  224. ///=== {
  225. // #@ storage.Get <KEY>
  226. // 从全局存储文件中根据 KEY 提取值。
  227. // 本函数与 storage.GetUser 相对,读写的是 share 作用域的键值存储。
  228. // 参见 storage.Set
  229. // };
  230. #func {storage.Get} {
  231. #local key {%1};
  232. #if { "$key" == "" } {
  233. xtt.Usage storage.Get;
  234. #return {};
  235. };
  236. #return {$storage-globals[$key]};
  237. };
  238. ///=== {
  239. // ## storage.SetGlobal <KEY> [<值>]
  240. // 旧名称,已废弃,等同于 storage.Set,保留以兼容既有代码,请尽快迁移。
  241. // };
  242. #alias {storage.SetGlobal} {
  243. warnLog 本接口已废弃,请尽快改用 storage.Set;
  244. storage.Set %0;
  245. };
  246. ///=== {
  247. // #@ storage.GetGlobal <KEY>
  248. // 旧名称,已废弃,等同于 storage.Get,保留以兼容既有代码,请尽快迁移。
  249. // };
  250. #func {storage.GetGlobal} {
  251. #local key {%1};
  252. warnLog 本接口已废弃,请尽快改用 storage.Get;
  253. #return @storage.Get{$key};
  254. };
  255. ///=== {
  256. // ## storage.SetUser <KEY> [<值>]
  257. // 将值关联到 KEY 上,并存储到当前角色的私有存储文件中。
  258. // 需要登录完成、获得角色 ID 之后才能使用,否则报错拒绝。
  259. // 参见 storage.GetUser
  260. // };
  261. #alias {storage.SetUser} {
  262. #local key {%1};
  263. #local value {%2};
  264. #if { "$key" == "" } {
  265. xtt.Usage storage.SetUser;
  266. #return;
  267. };
  268. #if { "$user[id]" == "" } {
  269. errLog storage 尚未登录角色,无法使用 storage.SetUser。;
  270. #return;
  271. };
  272. storage.ensure-user;
  273. #var storage-user-globals[$key] {$value};
  274. storage.SaveUser {storage-user} {storage-user-globals};
  275. };
  276. ///=== {
  277. // #@ storage.GetUser <KEY>
  278. // 从当前角色的私有存储文件中根据 KEY 提取值。
  279. // 需要登录完成、获得角色 ID 之后才能使用,否则报错拒绝。
  280. // 参见 storage.SetUser
  281. // };
  282. #func {storage.GetUser} {
  283. #local key {%1};
  284. #if { "$key" == "" } {
  285. xtt.Usage storage.SetUser;
  286. #return {};
  287. };
  288. #if { "$user[id]" == "" } {
  289. errLog storage 尚未登录角色,无法使用 storage.GetUser。;
  290. #return {};
  291. };
  292. storage.ensure-user;
  293. #return {$storage-user-globals[$key]};
  294. };
  295. VAR {角色私有存储已加载} storage-user-loaded {false};
  296. #nop 内部命令,确保角色私有存储项已从磁盘加载。;
  297. #alias {storage.ensure-user} {
  298. #if { @isTrue{$storage-user-loaded} } {
  299. #return;
  300. };
  301. storage.LoadUser {storage-user} {storage-user-globals};
  302. #var storage-user-loaded {true};
  303. };