The Battle for Wesnoth  1.19.15+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.try_emplace("APPLE");
46 #endif
47 
48 #ifdef __ANDROID__
49  target.try_emplace("ANDROID");
50 #endif
51 
52 #if defined(MOUSE_TOUCH_EMULATION) || defined(TARGET_OS_IPHONE)
53  target.try_emplace("IPHONEOS");
54 #endif
55 
56  target.try_emplace("WESNOTH_VERSION", 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  defines_map_.clear();
87 
88  //
89  // Set-up default defines map.
90  //
91 
92  add_builtin_defines(defines_map_);
93 }
94 
95 config config_cache::get_config(const std::string& file_path, abstract_validator* validator)
96 {
97  return load_configs(file_path, validator);
98 }
99 
100 void config_cache::write_file(const std::string& file_path, const config& cfg)
101 {
104  writer.write(cfg);
105 }
106 
107 void config_cache::write_file(const std::string& file_path, const preproc_map& defines)
108 {
109  if(defines.empty()) {
110  if(filesystem::file_exists(file_path)) {
111  filesystem::delete_directory(file_path);
112  }
113  return;
114  }
115 
118 
119  // Write all defines to stream.
120  for(const preproc_map::value_type& define : defines) {
121  define.second.write(writer, define.first);
122  }
123 }
124 
125 config config_cache::read_file(const std::string& file_path)
126 {
127  return io::read_gz(*filesystem::istream_file(file_path));
128 }
129 
131 {
133  // HACK: copy_map doesn't have built-in defines in some cases (issue #1924)
134  // or they may be out of date, so brute-force them in.
135  add_builtin_defines(res);
136  return res;
137 }
138 
140 {
142 }
143 
144 std::pair<config, preproc_map> config_cache::read_configs(const std::string& file_path, abstract_validator* validator)
145 {
146  preproc_map copy_map = make_copy_map();
147  config cfg = read_configs(file_path, copy_map, validator);
148  return { std::move(cfg), std::move(copy_map) };
149 }
150 
151 config config_cache::read_configs(const std::string& file_path, preproc_map& defines_map, abstract_validator* validator)
152 {
153  return io::read(*preprocess_file(file_path, defines_map), validator);
154 }
155 
156 config config_cache::read_cache(const std::string& file_path, abstract_validator* validator)
157 {
158  static const std::string extension = ".gz";
159 
160  std::stringstream defines_string;
161  defines_string << file_path;
162 
163  bool is_valid = true;
164 
165  for(const auto& [key, define] : defines_map_) {
166  //
167  // Only WESNOTH_VERSION is allowed to be non-empty.
168  //
169  if((!define.value.empty() || !define.arguments.empty()) && key != "WESNOTH_VERSION") {
170  is_valid = false;
171  ERR_CACHE << "Invalid preprocessor define: " << key;
172  break;
173  }
174 
175  defines_string << " " << key;
176  }
177 
178  // Do cache check only if define map is valid and
179  // caching is allowed.
180  const std::string& cache_path = filesystem::get_cache_dir();
181 
182  if(is_valid && !cache_path.empty()) {
183  // Use a hash for a shorter display of the defines.
184  const std::string fname = cache_path + "/" +
186  utils::md5(defines_string.str()).hex_digest();
187  const std::string fname_checksum = fname + ".checksum" + extension;
188 
189  filesystem::file_tree_checksum dir_checksum;
190 
192  try {
193  if(filesystem::file_exists(fname_checksum)) {
194  DBG_CACHE << "Reading checksum: " << fname_checksum;
195  dir_checksum = filesystem::file_tree_checksum{read_file(fname_checksum)};
196  }
197  } catch(const config::error&) {
198  ERR_CACHE << "cache checksum is corrupt";
199  } catch(const filesystem::io_exception&) {
200  ERR_CACHE << "error reading cache checksum";
201  } catch(const std::ios_base::failure&) {
202  ERR_CACHE << "error reading cache checksum";
203  }
204  }
205 
206  if(force_valid_cache_) {
207  LOG_CACHE << "skipping cache validation (forced)";
208  }
209 
210  if(filesystem::file_exists(fname + extension) && (force_valid_cache_ || (dir_checksum == filesystem::data_tree_checksum()))) {
211  LOG_CACHE << "found valid cache at '" << fname << extension << "' with defines_map " << defines_string.str();
212  log_scope("read cache");
213 
214  try {
215  config cfg = read_file(fname + extension);
216  const std::string define_file = fname + ".define" + extension;
217 
218  if(filesystem::file_exists(define_file)) {
220  }
221 
222  return cfg;
223  } catch(const config::error& e) {
224  ERR_CACHE << "cache " << fname << extension << " is corrupt. Loading from files: "<< e.message;
225  } catch(const filesystem::io_exception&) {
226  ERR_CACHE << "error reading cache " << fname << extension << ". Loading from files";
227  } catch (const boost::iostreams::gzip_error& e) {
228  //read_file -> ... -> read_gz can throw this exception.
229  ERR_CACHE << "cache " << fname << extension << " is corrupt. Error code: " << e.error();
230  }
231  }
232 
233  LOG_CACHE << "no valid cache found. Writing cache to '" << fname << extension << " with defines_map "<< defines_string.str() << "'";
234 
235  // Now we need queued defines so read them to memory
237 
238  auto [cfg, defines] = read_configs(file_path, validator);
239  add_defines_map_diff(defines);
240 
241  try {
242  write_file(fname + extension, cfg);
243  write_file(fname + ".define" + extension, defines);
244 
245  config checksum_cfg;
246 
247  filesystem::data_tree_checksum().write(checksum_cfg);
248  write_file(fname_checksum, checksum_cfg);
249  } catch(const filesystem::io_exception&) {
250  ERR_CACHE << "could not write to cache '" << fname << "'";
251  }
252 
253  return cfg;
254  }
255 
256  LOG_CACHE << "Loading plain config instead of cache";
257 
258  auto [cfg, defines] = read_configs(file_path, validator);
259  add_defines_map_diff(defines);
260  return cfg;
261 }
262 
263 void config_cache::read_defines_file(const std::string& file_path)
264 {
265  DBG_CACHE << "Reading cached defines from: " << file_path;
266  config file_cfg = read_file(file_path);
267 
268  for(const auto [key, cfg] : file_cfg.all_children_view()) {
270  }
271 }
272 
274 {
275  for(const std::string& p : config_cache_transaction::instance().get_define_files()) {
277  }
278 }
279 
280 config config_cache::load_configs(const std::string& config_path, abstract_validator* validator)
281 {
282  // Make sure that we have fake transaction if no real one is going on
284 
285  if(use_cache_) {
286  return read_cache(config_path, validator);
287  } else {
288  auto [cfg, defines] = read_configs(config_path, validator);
289  add_defines_map_diff(defines);
290  return cfg;
291  }
292 }
293 
295 {
296  fake_invalid_cache_ = force;
297 }
298 
300 {
301  use_cache_ = use;
302 }
303 
305 {
306  force_valid_cache_ = force;
307 }
308 
310 {
312 }
313 
314 void config_cache::add_define(const std::string& define)
315 {
316  DBG_CACHE << "adding define: " << define;
317  defines_map_.try_emplace(define);
318 
320  // we have to add this to active map too
322  }
323 }
324 
325 void config_cache::remove_define(const std::string& define)
326 {
327  DBG_CACHE << "removing define: " << define;
328  defines_map_.erase(define);
329 
331  // we have to remove this from active map too
333  }
334 }
335 
337 {
338  std::vector<std::string> files, dirs;
340 
341  LOG_CACHE << "clean_cache(): " << files.size() << " files, "
342  << dirs.size() << " dirs to check";
343 
344  const std::string& exclude_current = cache_file_prefix_ + "*";
345 
346  bool status = true;
347 
348  status &= delete_cache_files(files, exclude_current);
349  status &= delete_cache_files(dirs, exclude_current);
350 
351  LOG_CACHE << "clean_cache(): done";
352 
353  return status;
354 }
355 
357 {
358  std::vector<std::string> files, dirs;
360 
361  LOG_CACHE << "purge_cache(): deleting " << files.size() << " files, "
362  << dirs.size() << " dirs";
363 
364  bool status = true;
365 
366  status &= delete_cache_files(files);
367  status &= delete_cache_files(dirs);
368 
369  LOG_CACHE << "purge_cache(): done";
370  return status;
371 }
372 
373 bool config_cache::delete_cache_files(const std::vector<std::string>& paths,
374  const std::string& exclude_pattern)
375 {
376  const bool delete_everything = exclude_pattern.empty();
377  bool status = true;
378 
379  for(const std::string& file_path : paths)
380  {
381  if(!delete_everything) {
382  const std::string& fn = filesystem::base_name(file_path);
383 
384  if(utils::wildcard_string_match(fn, exclude_pattern)) {
385  LOG_CACHE << "delete_cache_files(): skipping " << file_path
386  << " excluded by '" << exclude_pattern << "'";
387  continue;
388  }
389  }
390 
391  LOG_CACHE << "delete_cache_files(): deleting " << file_path;
392  if(!filesystem::delete_directory(file_path)) {
393  ERR_CACHE << "delete_cache_files(): could not delete "
394  << file_path;
395  status = false;
396  }
397  }
398 
399  return status;
400 }
401 
403  : define_filenames_()
404  , active_map_()
405 {
406  assert(state_ == FREE);
407  state_ = NEW;
408  active_ = this;
409 }
410 
412 {
413  state_ = FREE;
414  active_ = nullptr;
415 }
416 
418 {
419  state_ = LOCKED;
420 }
421 
422 const std::vector<std::string>& config_cache_transaction::get_define_files() const
423 {
424  return define_filenames_;
425 }
426 
427 void config_cache_transaction::add_define_file(const std::string& file)
428 {
429  define_filenames_.push_back(file);
430 }
431 
433 {
434  if(active_map_.empty()) {
435  active_map_.insert(defines_map.begin(), defines_map.end());
436  if(get_state() == NEW) {
437  state_ = ACTIVE;
438  }
439  }
440 
441  return active_map_;
442 }
443 
445 {
446  switch(get_state()) {
447  case ACTIVE: {
448  preproc_map temp;
449  std::set_difference(new_map.begin(),
450  new_map.end(),
451  active_map_.begin(),
452  active_map_.end(),
453  std::insert_iterator(temp, temp.begin()));
454 
455  active_map_.insert(temp.begin(), temp.end());
456  std::swap(temp, new_map);
457  break;
458  }
459 
460  case LOCKED:
461  new_map.clear();
462  break;
463 
464  default:
465  break;
466  }
467 }
468 
469 } // namespace game_config
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
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)
static config_cache_transaction * active_
void add_define_file(const std::string &file)
Singleton class to manage game config file caching.
void add_define(const std::string &define)
Add a entry to preproc defines map.
void recheck_filetree_checksum()
Force cache checksum validation.
config read_cache(const std::string &path, 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.
config load_configs(const std::string &path, abstract_validator *validator=nullptr)
static config_cache & instance()
Get reference to the singleton object.
preproc_map & make_copy_map()
const preproc_map & get_preproc_map() const
config read_file(const std::string &file)
void remove_define(const std::string &define)
Remove a entry to preproc defines map.
void clear_defines()
Clear stored defines map to default values.
bool clean_cache()
Deletes stale cache files not in use by the game.
config get_config(const std::string &path, abstract_validator *validator=nullptr)
Gets a config object from given path.
bool delete_cache_files(const std::vector< std::string > &paths, const std::string &exclude_pattern="")
std::pair< config, preproc_map > read_configs(const std::string &path, abstract_validator *validator)
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.
Holds a fake cache transaction if no real one is used.
virtual std::string hex_digest() const override
Definition: hash.cpp:119
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1333
#define LOG_CACHE
static lg::log_domain log_cache("cache")
#define DBG_CACHE
#define ERR_CACHE
const config * cfg
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 void insert(preproc_map &, const config &)
mock_party p
#define e