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