1 /*
2 Copyright (C) 2010 - 2018 by Jody Northup
3 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY.
11
12 See the COPYING file for more details.
13 */
14
15 #include "filesystem.hpp"
16 #include "lexical_cast.hpp"
17 #include "log.hpp"
18 #include "persist_context.hpp"
19 #include "persist_manager.hpp"
20 #include "serialization/binary_or_text.hpp"
21 #include "serialization/parser.hpp"
22
pack_scalar(const std::string & name,const t_string & val)23 config pack_scalar(const std::string &name, const t_string &val)
24 {
25 config cfg;
26 cfg[name] = val;
27 return cfg;
28 }
29
get_persist_cfg_name(const std::string & name_space)30 static std::string get_persist_cfg_name(const std::string &name_space) {
31 return (filesystem::get_dir(filesystem::get_user_data_dir() + "/persist/") + name_space + ".cfg");
32 }
33
load()34 void persist_file_context::load()
35 {
36 std::string cfg_name = get_persist_cfg_name(namespace_.root_);
37 if (filesystem::file_exists(cfg_name) && !filesystem::is_directory(cfg_name)) {
38 filesystem::scoped_istream file_stream = filesystem::istream_file(cfg_name);
39 if (!(file_stream->fail())) {
40 try {
41 read(cfg_,*file_stream);
42 } catch (const config::error &err) {
43 LOG_PERSIST << err.message << std::endl;
44 }
45 }
46 }
47 }
48
persist_file_context(const std::string & name_space)49 persist_file_context::persist_file_context(const std::string &name_space)
50 : persist_context(name_space)
51 {
52 load();
53 }
54
clear_var(const std::string & global,bool immediate)55 bool persist_file_context::clear_var(const std::string &global, bool immediate)
56 {
57 config bak;
58 config bactive;
59 if (immediate) {
60 bak = cfg_;
61 config *node = get_node(bak, namespace_);
62 if (node)
63 bactive = node->child_or_add("variables");
64 load();
65 }
66 config *active = get_node(cfg_, namespace_);
67 if (active == nullptr)
68 return false;
69
70 bool ret = active->has_child("variables");
71 if (ret) {
72 config &cfg = active->child("variables");
73 bool exists = cfg.has_attribute(global);
74 if (!exists) {
75 if (cfg.has_child(global)) {
76 exists = true;
77 std::string::const_iterator index_start = std::find(global.begin(),global.end(),'[');
78 if (index_start != global.end())
79 {
80 const std::string::const_iterator index_end = std::find(global.begin(),global.end(),']');
81 const std::string index_str(index_start+1,index_end);
82 size_t index = static_cast<size_t>(lexical_cast_default<int>(index_str));
83 cfg.remove_child(global,index);
84 if (immediate) bactive.remove_child(global,index);
85 } else {
86 cfg.clear_children(global);
87 if (immediate) bactive.clear_children(global);
88 }
89 }
90 }
91 if (exists) {
92 cfg.remove_attribute(global);
93 if (immediate) bactive.remove_attribute(global);
94 if (cfg.empty()) {
95 active->clear_children("variables");
96 active->remove_attribute("variables");
97 name_space working = namespace_;
98 while ((active->empty()) && (!working.lineage_.empty())) {
99 name_space prev = working.prev();
100 active = get_node(cfg_, prev);
101 active->clear_children(working.node_);
102 if (active->has_child("variables") && active->child("variables").empty()) {
103 active->clear_children("variables");
104 active->remove_attribute("variables");
105 }
106 working = prev;
107 }
108 }
109
110 if (!in_transaction_)
111 ret = save_context();
112 else if (immediate) {
113 ret = save_context();
114 cfg_ = bak;
115 active = get_node(cfg_, namespace_);
116 if (active != nullptr) {
117 active->clear_children("variables");
118 active->remove_attribute("variables");
119 if (!bactive.empty())
120 active->add_child("variables",bactive);
121 }
122 } else {
123 ret = true;
124 }
125 } else {
126 if (immediate) {
127 cfg_ = bak;
128 active = get_node(cfg_, namespace_);
129 if (active != nullptr) {
130 active->clear_children("variables");
131 active->remove_attribute("variables");
132 if (!bactive.empty())
133 active->add_child("variables", bactive);
134 }
135 }
136 ret = exists;
137 }
138 }
139
140 // TODO: figure out when this is the case and adjust the next loop
141 // condition accordingly. -- shadowm
142 assert(active);
143
144 while (active && active->empty() && !namespace_.lineage_.empty()) {
145 name_space prev = namespace_.prev();
146 active = get_node(cfg_, prev);
147 if (active == nullptr) {
148 break;
149 }
150 active->clear_children(namespace_.node_);
151 if (active->has_child("variables") && active->child("variables").empty()) {
152 active->clear_children("variables");
153 active->remove_attribute("variables");
154 }
155 namespace_ = prev;
156 }
157 return ret;
158 }
159
get_var(const std::string & global) const160 config persist_file_context::get_var(const std::string &global) const
161 {
162 config ret;
163 const config *active = get_node(cfg_, namespace_);
164 if (active && (active->has_child("variables"))) {
165 const config &cfg = active->child("variables");
166 size_t arrsize = cfg.child_count(global);
167 if (arrsize > 0) {
168 for (size_t i = 0; i < arrsize; i++)
169 ret.add_child(global,cfg.child(global,i));
170 } else {
171 ret = pack_scalar(global,cfg[global]);
172 }
173 } else {
174 ret = pack_scalar(global,"");
175 }
176 return ret;
177 }
save_context()178 bool persist_file_context::save_context() {
179 bool success = false;
180
181 std::string cfg_name = get_persist_cfg_name(namespace_.root_);
182 if (!cfg_name.empty()) {
183 if (cfg_.empty()) {
184 success = filesystem::delete_file(cfg_name);
185 } else {
186 filesystem::scoped_ostream out = filesystem::ostream_file(cfg_name);
187 if (!out->fail())
188 {
189 config_writer writer(*out,false);
190 try {
191 writer.write(cfg_);
192 success = true;
193 } catch(config::error &err) {
194 LOG_PERSIST << err.message << std::endl;
195 success = false;
196 }
197 }
198 }
199 }
200 return success;
201 }
set_var(const std::string & global,const config & val,bool immediate)202 bool persist_file_context::set_var(const std::string &global,const config &val, bool immediate)
203 {
204 config bak;
205 config bactive;
206 if (immediate) {
207 bak = cfg_;
208 bactive = get_node(bak, namespace_, true)->child_or_add("variables");
209 load();
210 }
211
212 config *active = get_node(cfg_, namespace_, true);
213 config &cfg = active->child_or_add("variables");
214 if (val.has_attribute(global)) {
215 if (val[global].empty()) {
216 clear_var(global,immediate);
217 } else {
218 cfg[global] = val[global];
219 if (immediate) bactive[global] = val[global];
220 }
221 } else {
222 cfg.clear_children(global);
223 cfg.append(val);
224 if (immediate) {
225 bactive.clear_children(global);
226 bactive.append(val);
227 }
228 }
229 // dirty_ = true;
230 if (!in_transaction_)
231 return save_context();
232 else if (immediate) {
233 bool ret = save_context();
234 cfg_ = bak;
235 active = get_node(cfg_, namespace_, true);
236 active->clear_children("variables");
237 active->remove_attribute("variables");
238 active->add_child("variables",bactive);
239 return ret;
240 } else
241 return true;
242 }
243
set_node(const std::string & name)244 void persist_context::set_node(const std::string &name) {
245 std::string newspace = namespace_.root_;
246 if (!name.empty())
247 newspace += "." + name;
248 namespace_ = name_space(newspace);
249 }
250
get_node() const251 std::string persist_context::get_node() const
252 {
253 return namespace_.namespace_;
254 }
255