The Battle for Wesnoth  1.19.13+dev
config_cache.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2025
3  by Pauli Nieminen <paniemin@cc.hut.fi>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
18 #include "config_cache.hpp"
19 #include "filesystem.hpp"
20 #include "gettext.hpp"
21 #include "log.hpp"
22 #include "hash.hpp"
24 #include "serialization/parser.hpp"
26 #include "game_version.hpp"
27 
28 #include <boost/algorithm/string/replace.hpp>
29 #include <boost/iostreams/filter/gzip.hpp>
30 
31 static lg::log_domain log_cache("cache");
32 #define ERR_CACHE LOG_STREAM(err, log_cache)
33 #define LOG_CACHE LOG_STREAM(info, log_cache)
34 #define DBG_CACHE LOG_STREAM(debug, log_cache)
35 
36 namespace game_config
37 {
38 
39 namespace
40 {
41 
42 void add_builtin_defines(preproc_map& target)
43 {
44 #ifdef __APPLE__
45  target["APPLE"] = preproc_define();
46 #endif
47 
48 #ifdef __ANDROID__
49  target["ANDROID"] = preproc_define();
50 #endif
51 
52 #if defined(MOUSE_TOUCH_EMULATION) || defined(TARGET_OS_IPHONE)
53  target["IPHONEOS"] = preproc_define();
54 #endif
55 
56  target["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
57 }
58 
59 }
60 
62 {
63  static config_cache cache;
64  return cache;
65 }
66 
68  : force_valid_cache_(false)
69  , use_cache_(true)
70  , fake_invalid_cache_(false)
71  , defines_map_()
72  , cache_file_prefix_("cache-v" + boost::algorithm::replace_all_copy(game_config::revision, ":", "_") + "-")
73 {
74  // To set-up initial defines map correctly
75  clear_defines();
76 }
77 
79 {
80  return defines_map_;
81 }
82 
84 {
85  LOG_CACHE << "Clearing defines map!";
86 
87  defines_map_.clear();
88 
89  //
90  // Set-up default defines map.
91  //
92 
93  add_builtin_defines(defines_map_);
94 }
95 
96 void config_cache::get_config(const std::string& file_path, config& cfg, abstract_validator* validator)
97 {
98  load_configs(file_path, cfg, validator);
99 }
100 
101 void config_cache::write_file(const std::string& file_path, const config& cfg)
102 {
105  writer.write(cfg);
106 }
107 
108 void config_cache::write_file(const std::string& file_path, const preproc_map& defines)
109 {
110  if(defines.empty()) {
111  if(filesystem::file_exists(file_path)) {
112  filesystem::delete_directory(file_path);
113  }
114  return;
115  }
116 
119 
120  // Write all defines to stream.
121  for(const preproc_map::value_type& define : defines) {
122  define.second.write(writer, define.first);
123  }
124 }
125 
126 void config_cache::read_file(const std::string& file_path, config& cfg)
127 {
128  cfg = io::read_gz(*filesystem::istream_file(file_path));
129 }
130 
132 {
134  // HACK: copy_map doesn't have built-in defines in some cases (issue #1924)
135  // or they may be out of date, so brute-force them in.
136  add_builtin_defines(res);
137  return res;
138 }
139 
141 {
143 }
144 
145 void config_cache::read_configs(const std::string& file_path, config& cfg, preproc_map& defines_map, abstract_validator* validator)
146 {
147  //read the file and then write to the cache
148  cfg = io::read(*preprocess_file(file_path, &defines_map), validator);
149 }
150 
151 void config_cache::read_cache(const std::string& file_path, config& cfg, abstract_validator* validator)
152 {
153  static const std::string extension = ".gz";
154 
155  std::stringstream defines_string;
156  defines_string << file_path;
157 
158  bool is_valid = true;
159 
160  for(const preproc_map::value_type& d : defines_map_) {
161  //
162  // Only WESNOTH_VERSION is allowed to be non-empty.
163  //
164  if((!d.second.value.empty() || !d.second.arguments.empty()) &&
165  d.first != "WESNOTH_VERSION")
166  {
167  is_valid = false;
168  ERR_CACHE << "Invalid preprocessor define: " << d.first;
169  break;
170  }
171 
172  defines_string << " " << d.first;
173  }
174 
175  // Do cache check only if define map is valid and
176  // caching is allowed.
177  const std::string& cache_path = filesystem::get_cache_dir();
178 
179  if(is_valid && !cache_path.empty()) {
180  // Use a hash for a shorter display of the defines.
181  const std::string fname = cache_path + "/" +
183  utils::md5(defines_string.str()).hex_digest();
184  const std::string fname_checksum = fname + ".checksum" + extension;
185 
186  filesystem::file_tree_checksum dir_checksum;
187 
189  try {
190  if(filesystem::file_exists(fname_checksum)) {
191  config checksum_cfg;
192 
193  DBG_CACHE << "Reading checksum: " << fname_checksum;
194  read_file(fname_checksum, checksum_cfg);
195 
196  dir_checksum = filesystem::file_tree_checksum(checksum_cfg);
197  }
198  } catch(const config::error&) {
199  ERR_CACHE << "cache checksum is corrupt";
200  } catch(const filesystem::io_exception&) {
201  ERR_CACHE << "error reading cache checksum";
202  } catch(const std::ios_base::failure&) {
203  ERR_CACHE << "error reading cache checksum";
204  }
205  }
206 
207  if(force_valid_cache_) {
208  LOG_CACHE << "skipping cache validation (forced)";
209  }
210 
211  if(filesystem::file_exists(fname + extension) && (force_valid_cache_ || (dir_checksum == filesystem::data_tree_checksum()))) {
212  LOG_CACHE << "found valid cache at '" << fname << extension << "' with defines_map " << defines_string.str();
213  log_scope("read cache");
214 
215  try {
216  read_file(fname + extension,cfg);
217  const std::string define_file = fname + ".define" + extension;
218 
219  if(filesystem::file_exists(define_file)) {
221  }
222 
223  return;
224  } catch(const config::error& e) {
225  ERR_CACHE << "cache " << fname << extension << " is corrupt. Loading from files: "<< e.message;
226  } catch(const filesystem::io_exception&) {
227  ERR_CACHE << "error reading cache " << fname << extension << ". Loading from files";
228  } catch (const boost::iostreams::gzip_error& e) {
229  //read_file -> ... -> read_gz can throw this exception.
230  ERR_CACHE << "cache " << fname << extension << " is corrupt. Error code: " << e.error();
231  }
232  }
233 
234  LOG_CACHE << "no valid cache found. Writing cache to '" << fname << extension << " with defines_map "<< defines_string.str() << "'";
235 
236  // Now we need queued defines so read them to memory
238 
239  preproc_map copy_map(make_copy_map());
240 
241  read_configs(file_path, cfg, copy_map, validator);
242  add_defines_map_diff(copy_map);
243 
244  try {
245  write_file(fname + extension, cfg);
246  write_file(fname + ".define" + extension, copy_map);
247 
248  config checksum_cfg;
249 
250  filesystem::data_tree_checksum().write(checksum_cfg);
251  write_file(fname_checksum, checksum_cfg);
252  } catch(const filesystem::io_exception&) {
253  ERR_CACHE << "could not write to cache '" << fname << "'";
254  }
255 
256  return;
257  }
258 
259  LOG_CACHE << "Loading plain config instead of cache";
260 
261  preproc_map copy_map(make_copy_map());
262  read_configs(file_path, cfg, copy_map, validator);
263  add_defines_map_diff(copy_map);
264 }
265 
266 void config_cache::read_defines_file(const std::string& file_path)
267 {
268  config cfg;
269  read_file(file_path, cfg);
270 
271  DBG_CACHE << "Reading cached defines from: " << file_path;
272 
273  // use static preproc_define::read_pair(config) to make a object
274  // and pass that object config_cache_transaction::insert_to_active method
275  for(const auto [key, cfg] : cfg.all_children_view()) {
277  }
278 }
279 
281 {
282  const std::vector<std::string>& files = config_cache_transaction::instance().get_define_files();
283 
284  for(const std::string &p : files) {
286  }
287 }
288 
289 void config_cache::load_configs(const std::string& config_path, config& cfg, abstract_validator* validator)
290 {
291  // Make sure that we have fake transaction if no real one is going on
293 
294  if (use_cache_) {
295  read_cache(config_path, cfg, validator);
296  } else {
297  preproc_map copy_map(make_copy_map());
298  read_configs(config_path, cfg, copy_map, validator);
299  add_defines_map_diff(copy_map);
300  }
301 }
302 
304 {
305  fake_invalid_cache_ = force;
306 }
307 
309 {
310  use_cache_ = use;
311 }
312 
314 {
315  force_valid_cache_ = force;
316 }
317 
319 {
321 }
322 
323 void config_cache::add_define(const std::string& define)
324 {
325  DBG_CACHE << "adding define: " << define;
326  defines_map_[define] = preproc_define();
327 
329  // we have to add this to active map too
331  }
332 
333 }
334 
335 void config_cache::remove_define(const std::string& define)
336 {
337  DBG_CACHE << "removing define: " << define;
338  defines_map_.erase(define);
339 
341  // we have to remove this from active map too
343  }
344 }
345 
347 {
348  std::vector<std::string> files, dirs;
350 
351  LOG_CACHE << "clean_cache(): " << files.size() << " files, "
352  << dirs.size() << " dirs to check";
353 
354  const std::string& exclude_current = cache_file_prefix_ + "*";
355 
356  bool status = true;
357 
358  status &= delete_cache_files(files, exclude_current);
359  status &= delete_cache_files(dirs, exclude_current);
360 
361  LOG_CACHE << "clean_cache(): done";
362 
363  return status;
364 }
365 
367 {
368  std::vector<std::string> files, dirs;
370 
371  LOG_CACHE << "purge_cache(): deleting " << files.size() << " files, "
372  << dirs.size() << " dirs";
373 
374  bool status = true;
375 
376  status &= delete_cache_files(files);
377  status &= delete_cache_files(dirs);
378 
379  LOG_CACHE << "purge_cache(): done";
380  return status;
381 }
382 
383 bool config_cache::delete_cache_files(const std::vector<std::string>& paths,
384  const std::string& exclude_pattern)
385 {
386  const bool delete_everything = exclude_pattern.empty();
387  bool status = true;
388 
389  for(const std::string& file_path : paths)
390  {
391  if(!delete_everything) {
392  const std::string& fn = filesystem::base_name(file_path);
393 
394  if(utils::wildcard_string_match(fn, exclude_pattern)) {
395  LOG_CACHE << "delete_cache_files(): skipping " << file_path
396  << " excluded by '" << exclude_pattern << "'";
397  continue;
398  }
399  }
400 
401  LOG_CACHE << "delete_cache_files(): deleting " << file_path;
402  if(!filesystem::delete_directory(file_path)) {
403  ERR_CACHE << "delete_cache_files(): could not delete "
404  << file_path;
405  status = false;
406  }
407  }
408 
409  return status;
410 }
411 
414 
416  : define_filenames_()
417  , active_map_()
418 {
419  assert(state_ == FREE);
420  state_ = NEW;
421  active_ = this;
422 }
423 
425 {
426  state_ = FREE;
427  active_ = nullptr;
428 }
429 
431 {
432  state_ = LOCKED;
433 }
434 
435 const std::vector<std::string>& config_cache_transaction::get_define_files() const
436 {
437  return define_filenames_;
438 }
439 
440 void config_cache_transaction::add_define_file(const std::string& file)
441 {
442  define_filenames_.push_back(file);
443 }
444 
446 {
447  if(active_map_.empty()) {
448  active_map_.insert(defines_map.begin(), defines_map.end());
449  if(get_state() == NEW) {
450  state_ = ACTIVE;
451  }
452  }
453 
454  return active_map_;
455 }
456 
457 namespace
458 {
459 
460 bool compare_define(const preproc_map::value_type& a, const preproc_map::value_type& b)
461 {
462  if(a.first < b.first) {
463  return true;
464  }
465 
466  if(b.first < a.first) {
467  return false;
468  }
469 
470  if(a.second < b.second) {
471  return true;
472  }
473 
474  return false;
475 }
476 
477 } // end anonymous namespace
478 
480 {
481  if(get_state() == ACTIVE) {
482  preproc_map temp;
483  std::set_difference(new_map.begin(),
484  new_map.end(),
485  active_map_.begin(),
486  active_map_.end(),
487  std::insert_iterator<preproc_map>(temp,temp.begin()),
488  &compare_define);
489 
490  for(const preproc_map::value_type &def : temp) {
491  insert_to_active(def);
492  }
493 
494  temp.swap(new_map);
495  } else if (get_state() == LOCKED) {
496  new_map.clear();
497  }
498 }
499 
500 void config_cache_transaction::insert_to_active(const preproc_map::value_type& def)
501 {
502  active_map_[def.first] = def.second;
503 }
504 
505 }
Used in parsing config file.
Definition: validator.hpp:38
Class for writing a config out to a file in pieces.
void write(const config &cfg)
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
Used to share macros between cache objects You have to create transaction object to load all macros t...
void add_defines_map_diff(preproc_map &defines_map)
std::vector< std::string > define_filenames_
static config_cache_transaction & instance()
const std::vector< std::string > & get_define_files() const
void lock()
Lock the transaction so no more macros are added.
preproc_map & get_active_map(const preproc_map &defines_map)
void insert_to_active(const preproc_map::value_type &def)
Used to let std::for_each insert new defines to active_map map to active.
static config_cache_transaction * active_
void add_define_file(const std::string &file)
Singleton class to manage game config file caching.
void load_configs(const std::string &path, config &cfg, abstract_validator *validator=nullptr)
void add_define(const std::string &define)
Add a entry to preproc defines map.
void recheck_filetree_checksum()
Force cache checksum validation.
void read_cache(const std::string &path, config &cfg, abstract_validator *validator=nullptr)
void write_file(const std::string &file, const config &cfg)
bool purge_cache()
Deletes all cache files.
void set_use_cache(bool use)
Enable/disable caching.
static config_cache & instance()
Get reference to the singleton object.
preproc_map & make_copy_map()
const preproc_map & get_preproc_map() const
void remove_define(const std::string &define)
Remove a entry to preproc defines map.
void clear_defines()
Clear stored defines map to default values.
void read_configs(const std::string &path, config &cfg, preproc_map &defines, abstract_validator *validator=nullptr)
bool clean_cache()
Deletes stale cache files not in use by the game.
void read_file(const std::string &file, config &cfg)
bool delete_cache_files(const std::vector< std::string > &paths, const std::string &exclude_pattern="")
void read_defines_file(const std::string &path)
void add_defines_map_diff(preproc_map &)
void set_force_valid_cache(bool force)
Enable/disable cache validation.
void get_config(const std::string &path, config &cfg, abstract_validator *validator=nullptr)
Gets a config object from given path.
Holds a fake cache transaction if no real one is used.
virtual std::string hex_digest() const override
Definition: hash.cpp:119
#define LOG_CACHE
static lg::log_domain log_cache("cache")
#define DBG_CACHE
#define ERR_CACHE
Declarations for File-IO.
Interfaces for manipulating version numbers of engine, add-ons, etc.
Standard logging facilities (interface).
#define log_scope(description)
Definition: log.hpp:275
Definition: span.hpp:53
std::string get_cache_dir()
Definition: filesystem.cpp:882
filesystem::scoped_istream istream_file(const std::string &fname, bool treat_failure_as_error)
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs, name_mode mode, filter_mode filter, reorder_mode reorder, file_tree_checksum *checksum)
Get a list of all files and/or directories in a given directory.
Definition: filesystem.cpp:463
std::string base_name(const std::string &file, const bool remove_extension)
Returns the base filename of a file, with directory name stripped.
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:341
bool delete_directory(const std::string &dirname, const bool keep_pbl)
const file_tree_checksum & data_tree_checksum(bool reset=false)
Get the time at which the data/ tree was last modified at.
filesystem::scoped_ostream ostream_file(const std::string &fname, std::ios_base::openmode mode, bool create_directory)
std::unique_ptr< std::ostream > scoped_ostream
Definition: filesystem.hpp:54
Game configuration data as global variables.
Definition: build_info.cpp:61
const version_info wesnoth_version(VERSION)
int cache_compression_level
Definition: filesystem.cpp:118
const std::string revision
config read(std::istream &in, abstract_validator *validator)
Definition: parser.cpp:600
config read_gz(std::istream &file, abstract_validator *validator)
Might throw a std::ios_base::failure especially a gzip_error.
Definition: parser.cpp:656
bool wildcard_string_match(std::string_view str, std::string_view pat) noexcept
Performs pattern matching with wildcards.
fake
For describing the type of faked display, if any.
Definition: video.hpp:44
filesystem::scoped_istream preprocess_file(const std::string &fname, preproc_map *defines)
Function to use the WML preprocessor on a file.
std::map< std::string, struct preproc_define > preproc_map
An exception object used when an IO error occurs.
Definition: filesystem.hpp:67
static preproc_map::value_type read_pair(const config &)
mock_party p
#define d
#define e
#define b