wesnoth/src/config_cache.cpp
Gregory A Lundberg f23127e65a Remove LOW_MEM compile-time option
Per @Vultraz
2016-11-11 09:20:56 -06:00

492 lines
13 KiB
C++

/*
Copyright (C) 2008 - 2016 by Pauli Nieminen <paniemin@cc.hut.fi>
Part of the Battle for Wesnoth Project http://www.wesnoth.org/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.
See the COPYING file for more details.
*/
#define GETTEXT_DOMAIN "wesnoth-lib"
#include "config_cache.hpp"
#include "filesystem.hpp"
#include "gettext.hpp"
#include "game_config.hpp"
#include "log.hpp"
#include "font/marked-up_text.hpp"
#include "utils/sha1.hpp"
#include "serialization/binary_or_text.hpp"
#include "serialization/parser.hpp"
#include "serialization/string_utils.hpp"
#include "version.hpp"
#include <boost/algorithm/string/replace.hpp>
#include <boost/iostreams/filter/gzip.hpp>
static lg::log_domain log_cache("cache");
#define ERR_CACHE LOG_STREAM(err, log_cache)
#define LOG_CACHE LOG_STREAM(info, log_cache)
#define DBG_CACHE LOG_STREAM(debug, log_cache)
namespace game_config
{
config_cache& config_cache::instance()
{
static config_cache cache;
return cache;
}
config_cache::config_cache()
: force_valid_cache_(false)
, use_cache_(true)
, fake_invalid_cache_(false)
, defines_map_()
, cache_file_prefix_()
{
cache_file_prefix_
= "cache-v" +
boost::algorithm::replace_all_copy(game_config::revision,
":", "_") +
"-";
// To set-up initial defines map correctly
clear_defines();
}
const preproc_map& config_cache::get_preproc_map() const
{
return defines_map_;
}
void config_cache::clear_defines()
{
LOG_CACHE << "Clearing defines map!" << std::endl;
defines_map_.clear();
//
// Set-up default defines map.
//
#ifdef __APPLE__
defines_map_["APPLE"] = preproc_define();
#endif
defines_map_["WESNOTH_VERSION"] = preproc_define(game_config::wesnoth_version.str());
}
void config_cache::get_config(const std::string& file_path, config& cfg)
{
load_configs(file_path, cfg);
}
void config_cache::write_file(std::string file_path, const config& cfg)
{
filesystem::scoped_ostream stream = filesystem::ostream_file(file_path);
config_writer writer(*stream, true, game_config::cache_compression_level);
writer.write(cfg);
}
void config_cache::write_file(std::string file_path, const preproc_map& defines)
{
if(defines.empty()) {
if(filesystem::file_exists(file_path)) {
filesystem::delete_directory(file_path);
}
return;
}
filesystem::scoped_ostream stream = filesystem::ostream_file(file_path);
config_writer writer(*stream, true, game_config::cache_compression_level);
// Write all defines to stream.
for(const preproc_map::value_type& define : defines) {
define.second.write(writer, define.first);
}
}
void config_cache::read_file(const std::string& file_path, config& cfg)
{
filesystem::scoped_istream stream = filesystem::istream_file(file_path);
read_gz(cfg, *stream);
}
preproc_map& config_cache::make_copy_map()
{
return config_cache_transaction::instance().get_active_map(defines_map_);
}
void config_cache::add_defines_map_diff(preproc_map& defines_map)
{
return config_cache_transaction::instance().add_defines_map_diff(defines_map);
}
void config_cache::read_configs(const std::string& file_path, config& cfg, preproc_map& defines_map)
{
//read the file and then write to the cache
filesystem::scoped_istream stream = preprocess_file(file_path, &defines_map);
read(cfg, *stream);
}
void config_cache::read_cache(const std::string& file_path, config& cfg)
{
static const std::string extension = ".gz";
std::stringstream defines_string;
defines_string << file_path;
bool is_valid = true;
for(const preproc_map::value_type& d : defines_map_) {
//
// Only WESNOTH_VERSION is allowed to be non-empty.
//
if((!d.second.value.empty() || !d.second.arguments.empty()) &&
d.first != "WESNOTH_VERSION")
{
is_valid = false;
ERR_CACHE << "Invalid preprocessor define: " << d.first << '\n';
break;
}
defines_string << " " << d.first;
}
// Do cache check only if define map is valid and
// caching is allowed.
const std::string& cache_path = filesystem::get_cache_dir();
if(is_valid && !cache_path.empty()) {
// Use a hash for a shorter display of the defines.
const std::string fname = cache_path + "/" +
cache_file_prefix_ +
sha1_hash(defines_string.str()).display();
const std::string fname_checksum = fname + ".checksum" + extension;
filesystem::file_tree_checksum dir_checksum;
if(!force_valid_cache_ && !fake_invalid_cache_) {
try {
if(filesystem::file_exists(fname_checksum)) {
config checksum_cfg;
DBG_CACHE << "Reading checksum: " << fname_checksum << "\n";
read_file(fname_checksum, checksum_cfg);
dir_checksum = filesystem::file_tree_checksum(checksum_cfg);
}
} catch(config::error&) {
ERR_CACHE << "cache checksum is corrupt" << std::endl;
} catch(filesystem::io_exception&) {
ERR_CACHE << "error reading cache checksum" << std::endl;
}
}
if(force_valid_cache_) {
LOG_CACHE << "skipping cache validation (forced)\n";
}
if(filesystem::file_exists(fname + extension) && (force_valid_cache_ || (dir_checksum == filesystem::data_tree_checksum()))) {
LOG_CACHE << "found valid cache at '" << fname << extension << "' with defines_map " << defines_string.str() << "\n";
log_scope("read cache");
try {
read_file(fname + extension,cfg);
const std::string define_file = fname + ".define" + extension;
if(filesystem::file_exists(define_file)) {
config_cache_transaction::instance().add_define_file(define_file);
}
return;
} catch(config::error& e) {
ERR_CACHE << "cache " << fname << extension << " is corrupt. Loading from files: "<< e.message << std::endl;
} catch(filesystem::io_exception&) {
ERR_CACHE << "error reading cache " << fname << extension << ". Loading from files" << std::endl;
} catch (boost::iostreams::gzip_error& e) {
//read_file -> ... -> read_gz can throw this exception.
ERR_CACHE << "cache " << fname << extension << " is corrupt. Error code: " << e.error() << std::endl;
}
}
LOG_CACHE << "no valid cache found. Writing cache to '" << fname << extension << " with defines_map "<< defines_string.str() << "'\n";
// Now we need queued defines so read them to memory
read_defines_queue();
preproc_map copy_map(make_copy_map());
read_configs(file_path, cfg, copy_map);
add_defines_map_diff(copy_map);
try {
write_file(fname + extension, cfg);
write_file(fname + ".define" + extension, copy_map);
config checksum_cfg;
filesystem::data_tree_checksum().write(checksum_cfg);
write_file(fname_checksum, checksum_cfg);
} catch(filesystem::io_exception&) {
ERR_CACHE << "could not write to cache '" << fname << "'" << std::endl;
}
return;
}
LOG_CACHE << "Loading plain config instead of cache\n";
preproc_map copy_map(make_copy_map());
read_configs(file_path, cfg, copy_map);
add_defines_map_diff(copy_map);
}
void config_cache::read_defines_file(const std::string& file_path)
{
config cfg;
read_file(file_path, cfg);
DBG_CACHE << "Reading cached defines from: " << file_path << "\n";
// use static preproc_define::read_pair(config) to make a object
// and pass that object config_cache_transaction::insert_to_active method
for(const config::any_child &value : cfg.all_children_range()) {
config_cache_transaction::instance().insert_to_active(
preproc_define::read_pair(value.cfg));
}
}
void config_cache::read_defines_queue()
{
const std::vector<std::string>& files = config_cache_transaction::instance().get_define_files();
for(const std::string &p : files) {
read_defines_file(p);
}
}
void config_cache::load_configs(const std::string& config_path, config& cfg)
{
// Make sure that we have fake transaction if no real one is going on
fake_transaction fake;
if (use_cache_) {
read_cache(config_path, cfg);
} else {
preproc_map copy_map(make_copy_map());
read_configs(config_path, cfg, copy_map);
add_defines_map_diff(copy_map);
}
}
void config_cache::set_force_invalid_cache(bool force)
{
fake_invalid_cache_ = force;
}
void config_cache::set_use_cache(bool use)
{
use_cache_ = use;
}
void config_cache::set_force_valid_cache(bool force)
{
force_valid_cache_ = force;
}
void config_cache::recheck_filetree_checksum()
{
filesystem::data_tree_checksum(true);
}
void config_cache::add_define(const std::string& define)
{
DBG_CACHE << "adding define: " << define << "\n";
defines_map_[define] = preproc_define();
if(config_cache_transaction::is_active()) {
// we have to add this to active map too
config_cache_transaction::instance().get_active_map(defines_map_).insert(
std::make_pair(define, preproc_define()));
}
}
void config_cache::remove_define(const std::string& define)
{
DBG_CACHE << "removing define: " << define << "\n";
defines_map_.erase(define);
if(config_cache_transaction::is_active()) {
// we have to remove this from active map too
config_cache_transaction::instance().get_active_map(defines_map_).erase(define);
}
}
bool config_cache::clean_cache()
{
std::vector<std::string> files, dirs;
filesystem::get_files_in_dir(filesystem::get_cache_dir(), &files, &dirs, filesystem::ENTIRE_FILE_PATH);
LOG_CACHE << "clean_cache(): " << files.size() << " files, "
<< dirs.size() << " dirs to check\n";
const std::string& exclude_current = cache_file_prefix_ + "*";
bool status = true;
status &= delete_cache_files(files, exclude_current);
status &= delete_cache_files(dirs, exclude_current);
LOG_CACHE << "clean_cache(): done\n";
return status;
}
bool config_cache::purge_cache()
{
std::vector<std::string> files, dirs;
filesystem::get_files_in_dir(filesystem::get_cache_dir(), &files, &dirs, filesystem::ENTIRE_FILE_PATH);
LOG_CACHE << "purge_cache(): deleting " << files.size() << " files, "
<< dirs.size() << " dirs\n";
bool status = true;
status &= delete_cache_files(files);
status &= delete_cache_files(dirs);
LOG_CACHE << "purge_cache(): done\n";
return status;
}
bool config_cache::delete_cache_files(const std::vector<std::string>& paths,
const std::string& exclude_pattern)
{
const bool delete_everything = exclude_pattern.empty();
bool status = true;
for(const std::string& file_path : paths)
{
if(!delete_everything) {
const std::string& fn = filesystem::base_name(file_path);
if(utils::wildcard_string_match(fn, exclude_pattern)) {
LOG_CACHE << "delete_cache_files(): skipping " << file_path
<< " excluded by '" << exclude_pattern << "'\n";
continue;
}
}
LOG_CACHE << "delete_cache_files(): deleting " << file_path << '\n';
if(!filesystem::delete_directory(file_path)) {
ERR_CACHE << "delete_cache_files(): could not delete "
<< file_path << '\n';
status = false;
}
}
return status;
}
config_cache_transaction::state config_cache_transaction::state_ = FREE;
config_cache_transaction* config_cache_transaction::active_ = 0;
config_cache_transaction::config_cache_transaction()
: define_filenames_()
, active_map_()
{
assert(state_ == FREE);
state_ = NEW;
active_ = this;
}
config_cache_transaction::~config_cache_transaction()
{
state_ = FREE;
active_ = 0;
}
void config_cache_transaction::lock()
{
state_ = LOCKED;
}
const std::vector<std::string>& config_cache_transaction::get_define_files() const
{
return define_filenames_;
}
void config_cache_transaction::add_define_file(const std::string& file)
{
define_filenames_.push_back(file);
}
preproc_map& config_cache_transaction::get_active_map(const preproc_map& defines_map)
{
if(active_map_.empty()) {
active_map_.insert(defines_map.begin(), defines_map.end());
if(get_state() == NEW) {
state_ = ACTIVE;
}
}
return active_map_;
}
namespace
{
bool compare_define(const preproc_map::value_type& a, const preproc_map::value_type& b)
{
if(a.first < b.first) {
return true;
}
if(b.first < a.first) {
return false;
}
if(a.second < b.second) {
return true;
}
return false;
}
} // end anonymous namespace
void config_cache_transaction::add_defines_map_diff(preproc_map& new_map)
{
if(get_state() == ACTIVE) {
preproc_map temp;
std::set_difference(new_map.begin(),
new_map.end(),
active_map_.begin(),
active_map_.end(),
std::insert_iterator<preproc_map>(temp,temp.begin()),
&compare_define);
for(const preproc_map::value_type &def : temp) {
insert_to_active(def);
}
temp.swap(new_map);
} else if (get_state() == LOCKED) {
new_map.clear();
}
}
void config_cache_transaction::insert_to_active(const preproc_map::value_type& def)
{
active_map_[def.first] = def.second;
}
}