Merge pull request #193 from gfgtdf/gamestate_refactor
Gamestate refactor part2
This commit is contained in:
commit
516206a0ee
22 changed files with 525 additions and 437 deletions
|
@ -860,6 +860,7 @@ set(wesnoth-main_SRC
|
|||
replay_controller.cpp
|
||||
resources.cpp
|
||||
save_blocker.cpp
|
||||
save_index.cpp
|
||||
savegame.cpp
|
||||
scripting/debug_lua.cpp
|
||||
scripting/lua.cpp
|
||||
|
|
|
@ -492,6 +492,7 @@ wesnoth_sources = Split("""
|
|||
replay_controller.cpp
|
||||
resources.cpp
|
||||
save_blocker.cpp
|
||||
save_index.cpp
|
||||
savegame.cpp
|
||||
scripting/debug_lua.cpp
|
||||
scripting/lua.cpp
|
||||
|
|
|
@ -29,7 +29,6 @@ namespace pathfind {
|
|||
#include <map>
|
||||
|
||||
class game_display;
|
||||
class game_state;
|
||||
class gamemap;
|
||||
class unit_map;
|
||||
class team;
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
#include "replay_helper.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "savegame.hpp"
|
||||
#include "save_index.hpp"
|
||||
#include "strftime.hpp"
|
||||
#include "synced_context.hpp"
|
||||
#include "thread.hpp"
|
||||
|
|
|
@ -460,7 +460,7 @@ void game_data::clear_variable(const std::string& varname)
|
|||
}
|
||||
}
|
||||
|
||||
void game_data::write_snapshot(config& cfg){
|
||||
void game_data::write_snapshot(config& cfg) const {
|
||||
cfg["scenario"] = scenario_;
|
||||
cfg["next_scenario"] = next_scenario_;
|
||||
|
||||
|
@ -763,20 +763,6 @@ void convert_old_saves(config& cfg){
|
|||
LOG_RG<<"cfg after conversion "<<cfg<<"\n";
|
||||
}
|
||||
|
||||
void game_state::write_snapshot(config& cfg) const
|
||||
{
|
||||
log_scope("write_game");
|
||||
cfg.merge_attributes(classification_.to_config());
|
||||
|
||||
//TODO: move id_manager handling to play_controller
|
||||
cfg["next_underlying_unit_id"] = str_cast(n_unit::id_manager::instance().get_save_id());
|
||||
|
||||
if(resources::gamedata != NULL){
|
||||
resources::gamedata->write_snapshot(cfg);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void extract_summary_from_config(config& cfg_save, config& cfg_summary)
|
||||
{
|
||||
const config &cfg_snapshot = cfg_save.child("snapshot");
|
||||
|
|
|
@ -106,7 +106,7 @@ public:
|
|||
/** the last location where a select event fired. */
|
||||
map_location last_selected;
|
||||
|
||||
void write_snapshot(config& cfg);
|
||||
void write_snapshot(config& cfg) const ;
|
||||
void write_config(config_writer& out);
|
||||
|
||||
const std::string& next_scenario() const { return next_scenario_; }
|
||||
|
@ -173,8 +173,6 @@ public:
|
|||
~game_state(){}
|
||||
game_state& operator=(const game_state& state);
|
||||
|
||||
//write the gamestate into a config object
|
||||
void write_snapshot(config& cfg) const;
|
||||
//write the config information into a stream (file)
|
||||
void write_config(config_writer& out) const;
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include "gui/widgets/window.hpp"
|
||||
#include "language.hpp"
|
||||
#include "preferences_display.hpp"
|
||||
#include "savegame.hpp"
|
||||
#include "utils/foreach.tpp"
|
||||
|
||||
#include <cctype>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include "gui/dialogs/dialog.hpp"
|
||||
#include "gui/widgets/listbox.hpp"
|
||||
#include "gui/widgets/text.hpp"
|
||||
#include "savegame.hpp"
|
||||
#include "save_index.hpp"
|
||||
#include "tstring.hpp"
|
||||
|
||||
namespace gui2
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "gettext.hpp"
|
||||
#include "game_config.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "gui/auxiliary/log.hpp"
|
||||
#include "gui/dialogs/field.hpp"
|
||||
#include "gui/dialogs/game_delete.hpp"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
#include "gui/dialogs/dialog.hpp"
|
||||
#include "gui/widgets/listbox.hpp"
|
||||
#include "gui/widgets/text.hpp"
|
||||
#include "savegame.hpp"
|
||||
#include "save_index.hpp"
|
||||
#include "tstring.hpp"
|
||||
|
||||
namespace gui2
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
#include "replay_helper.hpp"
|
||||
#include "resources.hpp"
|
||||
#include "savegame.hpp"
|
||||
#include "save_index.hpp"
|
||||
#include "sound.hpp"
|
||||
#include "statistics_dialog.hpp"
|
||||
#include "synced_context.hpp"
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
class display;
|
||||
class game_display;
|
||||
class config;
|
||||
class game_state;
|
||||
|
||||
namespace mp {
|
||||
|
||||
|
|
|
@ -779,6 +779,14 @@ config play_controller::to_config() const
|
|||
gui_->labels().write(cfg);
|
||||
sound::write_music_play_list(cfg);
|
||||
}
|
||||
|
||||
//TODO: move id_manager handling to play_controller
|
||||
cfg["next_underlying_unit_id"] = str_cast(n_unit::id_manager::instance().get_save_id());
|
||||
|
||||
|
||||
gamedata_.write_snapshot(cfg);
|
||||
|
||||
cfg.merge_attributes(gamestate_.classification().to_config());
|
||||
return cfg;
|
||||
}
|
||||
|
||||
|
|
|
@ -383,7 +383,6 @@ LEVEL_RESULT playsingle_controller::play_scenario(
|
|||
// At the beginning of the scenario, save a snapshot as replay_start
|
||||
if(gamestate_.snapshot.child_or_empty("variables")["turn_number"].to_int(-1)<1){
|
||||
gamestate_.replay_start() = to_config();
|
||||
gamestate_.write_snapshot(gamestate_.replay_start());
|
||||
}
|
||||
fire_preload();
|
||||
|
||||
|
|
396
src/save_index.cpp
Normal file
396
src/save_index.cpp
Normal file
|
@ -0,0 +1,396 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2014 by Jörg Hinrichs, refactored from various
|
||||
places formerly created by David White <dave@whitevine.net>
|
||||
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.
|
||||
*/
|
||||
|
||||
#include <boost/assign/list_of.hpp>
|
||||
#include <boost/iostreams/filter/gzip.hpp>
|
||||
|
||||
#include "save_index.hpp"
|
||||
|
||||
#include "format_time_summary.hpp"
|
||||
#include "formula_string_utils.hpp"
|
||||
#include "game_display.hpp"
|
||||
#include "game_end_exceptions.hpp"
|
||||
#include "game_preferences.hpp"
|
||||
#include "gettext.hpp"
|
||||
#include "log.hpp"
|
||||
#include "serialization/binary_or_text.hpp"
|
||||
#include "serialization/parser.hpp"
|
||||
|
||||
#include "gamestatus.hpp"
|
||||
#include "filesystem.hpp"
|
||||
#include "config.hpp"
|
||||
#include <boost/foreach.hpp>
|
||||
|
||||
static lg::log_domain log_engine("engine");
|
||||
#define LOG_SAVE LOG_STREAM(info, log_engine)
|
||||
#define ERR_SAVE LOG_STREAM(err, log_engine)
|
||||
|
||||
static lg::log_domain log_enginerefac("enginerefac");
|
||||
#define LOG_RG LOG_STREAM(info, log_enginerefac)
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef INADDR_ANY
|
||||
#undef INADDR_ANY
|
||||
#endif
|
||||
#ifdef INADDR_BROADCAST
|
||||
#undef INADDR_BROADCAST
|
||||
#endif
|
||||
#ifdef INADDR_NONE
|
||||
#undef INADDR_NONE
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
/**
|
||||
* conv_ansi_utf8()
|
||||
* - Convert a string between ANSI encoding (for Windows filename) and UTF-8
|
||||
* string &name
|
||||
* - filename to be converted
|
||||
* bool a2u
|
||||
* - if true, convert the string from ANSI to UTF-8.
|
||||
* - if false, reverse. (convert it from UTF-8 to ANSI)
|
||||
*/
|
||||
void conv_ansi_utf8(std::string &name, bool a2u) {
|
||||
int wlen = MultiByteToWideChar(a2u ? CP_ACP : CP_UTF8, 0,
|
||||
name.c_str(), -1, NULL, 0);
|
||||
if (wlen == 0) return;
|
||||
WCHAR *wc = new WCHAR[wlen];
|
||||
if (wc == NULL) return;
|
||||
if (MultiByteToWideChar(a2u ? CP_ACP : CP_UTF8, 0, name.c_str(), -1,
|
||||
wc, wlen) == 0) {
|
||||
delete [] wc;
|
||||
return;
|
||||
}
|
||||
int alen = WideCharToMultiByte(!a2u ? CP_ACP : CP_UTF8, 0, wc, wlen,
|
||||
NULL, 0, NULL, NULL);
|
||||
if (alen == 0) {
|
||||
delete [] wc;
|
||||
return;
|
||||
}
|
||||
CHAR *ac = new CHAR[alen];
|
||||
if (ac == NULL) {
|
||||
delete [] wc;
|
||||
return;
|
||||
}
|
||||
WideCharToMultiByte(!a2u ? CP_ACP : CP_UTF8, 0, wc, wlen,
|
||||
ac, alen, NULL, NULL);
|
||||
delete [] wc;
|
||||
if (ac == NULL) {
|
||||
return;
|
||||
}
|
||||
name = ac;
|
||||
delete [] ac;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void replace_underbar2space(std::string &name) {
|
||||
LOG_SAVE << "conv(A2U)-from:[" << name << "]" << std::endl;
|
||||
conv_ansi_utf8(name, true);
|
||||
LOG_SAVE << "conv(A2U)-to:[" << name << "]" << std::endl;
|
||||
LOG_SAVE << "replace_underbar2space-from:[" << name << "]" << std::endl;
|
||||
std::replace(name.begin(), name.end(), '_', ' ');
|
||||
LOG_SAVE << "replace_underbar2space-to:[" << name << "]" << std::endl;
|
||||
}
|
||||
|
||||
void replace_space2underbar(std::string &name) {
|
||||
LOG_SAVE << "conv(U2A)-from:[" << name << "]" << std::endl;
|
||||
conv_ansi_utf8(name, false);
|
||||
LOG_SAVE << "conv(U2A)-to:[" << name << "]" << std::endl;
|
||||
LOG_SAVE << "replace_space2underbar-from:[" << name << "]" << std::endl;
|
||||
std::replace(name.begin(), name.end(), ' ', '_');
|
||||
LOG_SAVE << "replace_space2underbar-to:[" << name << "]" << std::endl;
|
||||
}
|
||||
#else /* ! _WIN32 */
|
||||
void replace_underbar2space(std::string &name) {
|
||||
std::replace(name.begin(),name.end(),'_',' ');
|
||||
}
|
||||
void replace_space2underbar(std::string &name) {
|
||||
std::replace(name.begin(),name.end(),' ','_');
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
|
||||
namespace savegame {
|
||||
|
||||
|
||||
void save_index_class::rebuild(const std::string& name) {
|
||||
std::string filename = name;
|
||||
replace_space2underbar(filename);
|
||||
time_t modified = file_create_time(get_saves_dir() + "/" + filename);
|
||||
rebuild(name, modified);
|
||||
}
|
||||
void save_index_class::rebuild(const std::string& name, const time_t& modified) {
|
||||
log_scope("load_summary_from_file");
|
||||
config& summary = data(name);
|
||||
try {
|
||||
config full;
|
||||
std::string dummy;
|
||||
read_save_file(name, full, &dummy);
|
||||
::extract_summary_from_config(full, summary);
|
||||
} catch(game::load_game_failed&) {
|
||||
summary["corrupt"] = true;
|
||||
}
|
||||
summary["mod_time"] = str_cast(static_cast<int>(modified));
|
||||
write_save_index();
|
||||
}
|
||||
void save_index_class::remove(const std::string& name) {
|
||||
config& root = data();
|
||||
root.remove_attribute(name);
|
||||
write_save_index();
|
||||
}
|
||||
void save_index_class::set_modified(const std::string& name, const time_t& modified) {
|
||||
modified_[name] = modified;
|
||||
}
|
||||
config& save_index_class::get(const std::string& name) {
|
||||
config& result = data(name);
|
||||
time_t m = modified_[name];
|
||||
config::attribute_value& mod_time = result["mod_time"];
|
||||
if (mod_time.empty() || static_cast<time_t>(mod_time.to_int()) != m) {
|
||||
rebuild(name, m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
void save_index_class::write_save_index() {
|
||||
log_scope("write_save_index()");
|
||||
try {
|
||||
scoped_ostream stream = ostream_file(get_save_index_file());
|
||||
if (preferences::save_compression_format() != compression::NONE) {
|
||||
// TODO: maybe allow writing this using bz2 too?
|
||||
write_gz(*stream, data());
|
||||
} else {
|
||||
write(*stream, data());
|
||||
}
|
||||
} catch(io_exception& e) {
|
||||
ERR_SAVE << "error writing to save index file: '" << e.what() << "'" << std::endl;
|
||||
}
|
||||
}
|
||||
save_index_class::save_index_class()
|
||||
: loaded_(false)
|
||||
, data_()
|
||||
, modified_()
|
||||
{
|
||||
}
|
||||
config& save_index_class::data(const std::string& name) {
|
||||
config& cfg = data();
|
||||
if (config& sv = cfg.find_child("save", "save", name)) {
|
||||
return sv;
|
||||
}
|
||||
config& res = cfg.add_child("save");
|
||||
res["save"] = name;
|
||||
return res;
|
||||
}
|
||||
config& save_index_class::data() {
|
||||
if(loaded_ == false) {
|
||||
try {
|
||||
scoped_istream stream = istream_file(get_save_index_file());
|
||||
try {
|
||||
read_gz(data_, *stream);
|
||||
} catch (boost::iostreams::gzip_error&) {
|
||||
stream->seekg(0);
|
||||
read(data_, *stream);
|
||||
}
|
||||
} catch(io_exception& e) {
|
||||
ERR_SAVE << "error reading save index: '" << e.what() << "'" << std::endl;
|
||||
} catch(config::error& e) {
|
||||
ERR_SAVE << "error parsing save index config file:\n" << e.message << std::endl;
|
||||
data_.clear();
|
||||
}
|
||||
loaded_ = true;
|
||||
}
|
||||
return data_;
|
||||
}
|
||||
save_index_class save_index_manager;
|
||||
class filename_filter {
|
||||
public:
|
||||
filename_filter(const std::string& filter) : filter_(filter) {
|
||||
}
|
||||
bool operator()(const std::string& filename) const {
|
||||
return filename.end() == std::search(filename.begin(), filename.end(),
|
||||
filter_.begin(), filter_.end());
|
||||
}
|
||||
private:
|
||||
std::string filter_;
|
||||
};
|
||||
|
||||
/** Get a list of available saves. */
|
||||
std::vector<save_info> get_saves_list(const std::string* dir, const std::string* filter)
|
||||
{
|
||||
create_save_info creator(dir);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
get_files_in_dir(creator.dir,&filenames);
|
||||
|
||||
if (filter) {
|
||||
filenames.erase(std::remove_if(filenames.begin(), filenames.end(),
|
||||
filename_filter(*filter)),
|
||||
filenames.end());
|
||||
}
|
||||
|
||||
std::vector<save_info> result;
|
||||
std::transform(filenames.begin(), filenames.end(),
|
||||
std::back_inserter(result), creator);
|
||||
std::sort(result.begin(),result.end(),save_info_less_time());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
const config& save_info::summary() const {
|
||||
return save_index_manager.get(name());
|
||||
}
|
||||
|
||||
std::string save_info::format_time_local() const
|
||||
{
|
||||
char time_buf[256] = {0};
|
||||
tm* tm_l = localtime(&modified());
|
||||
if (tm_l) {
|
||||
const size_t res = strftime(time_buf,sizeof(time_buf),
|
||||
(preferences::use_twelve_hour_clock_format() ? _("%a %b %d %I:%M %p %Y") : _("%a %b %d %H:%M %Y")),
|
||||
tm_l);
|
||||
if(res == 0) {
|
||||
time_buf[0] = 0;
|
||||
}
|
||||
} else {
|
||||
LOG_SAVE << "localtime() returned null for time " << this->modified() << ", save " << name();
|
||||
}
|
||||
|
||||
return time_buf;
|
||||
}
|
||||
|
||||
std::string save_info::format_time_summary() const
|
||||
{
|
||||
time_t t = modified();
|
||||
return util::format_time_summary(t);
|
||||
}
|
||||
|
||||
bool save_info_less_time::operator() (const save_info& a, const save_info& b) const {
|
||||
if (a.modified() > b.modified()) {
|
||||
return true;
|
||||
} else if (a.modified() < b.modified()) {
|
||||
return false;
|
||||
// Special funky case; for files created in the same second,
|
||||
// a replay file sorts less than a non-replay file. Prevents
|
||||
// a timing-dependent bug where it may look like, at the end
|
||||
// of a scenario, the replay and the autosave for the next
|
||||
// scenario are displayed in the wrong order.
|
||||
} else if (a.name().find(_(" replay"))==std::string::npos && b.name().find(_(" replay"))!=std::string::npos) {
|
||||
return true;
|
||||
} else if (a.name().find(_(" replay"))!=std::string::npos && b.name().find(_(" replay"))==std::string::npos) {
|
||||
return false;
|
||||
} else {
|
||||
return a.name() > b.name();
|
||||
}
|
||||
}
|
||||
|
||||
static std::istream* find_save_file(const std::string &name, const std::string &alt_name, const std::vector<std::string> &suffixes) {
|
||||
BOOST_FOREACH(const std::string &suf, suffixes) {
|
||||
std::istream *file_stream = istream_file(get_saves_dir() + "/" + name + suf);
|
||||
if (file_stream->fail()) {
|
||||
delete file_stream;
|
||||
file_stream = istream_file(get_saves_dir() + "/" + alt_name + suf);
|
||||
}
|
||||
if (!file_stream->fail())
|
||||
return file_stream;
|
||||
else
|
||||
delete file_stream;
|
||||
}
|
||||
LOG_SAVE << "Could not open supplied filename '" << name << "'\n";
|
||||
throw game::load_game_failed();
|
||||
}
|
||||
|
||||
void read_save_file(const std::string& name, config& cfg, std::string* error_log)
|
||||
{
|
||||
std::string modified_name = name;
|
||||
replace_space2underbar(modified_name);
|
||||
|
||||
static const std::vector<std::string> suffixes = boost::assign::list_of("")(".gz")(".bz2");
|
||||
scoped_istream file_stream = find_save_file(modified_name, name, suffixes);
|
||||
|
||||
cfg.clear();
|
||||
try{
|
||||
/*
|
||||
* Test the modified name, since it might use a .gz
|
||||
* file even when not requested.
|
||||
*/
|
||||
if(is_gzip_file(modified_name)) {
|
||||
read_gz(cfg, *file_stream);
|
||||
} else if(is_bzip2_file(modified_name)) {
|
||||
read_bz2(cfg, *file_stream);
|
||||
} else {
|
||||
read(cfg, *file_stream);
|
||||
}
|
||||
} catch(const std::ios_base::failure& e) {
|
||||
LOG_SAVE << e.what();
|
||||
if(error_log) {
|
||||
*error_log += e.what();
|
||||
}
|
||||
throw game::load_game_failed();
|
||||
} catch(const config::error &err) {
|
||||
LOG_SAVE << err.message;
|
||||
if(error_log) {
|
||||
*error_log += err.message;
|
||||
}
|
||||
throw game::load_game_failed();
|
||||
}
|
||||
|
||||
if(cfg.empty()) {
|
||||
LOG_SAVE << "Could not parse file data into config\n";
|
||||
throw game::load_game_failed();
|
||||
}
|
||||
}
|
||||
|
||||
void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
|
||||
{
|
||||
const std::string auto_save = _("Auto-Save");
|
||||
int countdown = autosavemax;
|
||||
if (countdown == infinite_auto_saves)
|
||||
return;
|
||||
|
||||
std::vector<save_info> games = get_saves_list(NULL, &auto_save);
|
||||
for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); ++i) {
|
||||
if (countdown-- <= 0) {
|
||||
LOG_SAVE << "Deleting savegame '" << i->name() << "'\n";
|
||||
delete_game(i->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void delete_game(const std::string& name)
|
||||
{
|
||||
std::string modified_name = name;
|
||||
replace_space2underbar(modified_name);
|
||||
|
||||
remove((get_saves_dir() + "/" + name).c_str());
|
||||
remove((get_saves_dir() + "/" + modified_name).c_str());
|
||||
|
||||
save_index_manager.remove(name);
|
||||
}
|
||||
|
||||
|
||||
|
||||
create_save_info::create_save_info(const std::string* d)
|
||||
: dir(d ? *d : get_saves_dir())
|
||||
{
|
||||
}
|
||||
save_info create_save_info::operator()(const std::string& filename) const
|
||||
{
|
||||
std::string name = filename;
|
||||
replace_underbar2space(name);
|
||||
time_t modified = file_create_time(dir + "/" + filename);
|
||||
save_index_manager.set_modified(name, modified);
|
||||
return save_info(name, modified);
|
||||
}
|
||||
|
||||
}
|
||||
|
108
src/save_index.hpp
Normal file
108
src/save_index.hpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
/*
|
||||
Copyright (C) 2003 - 2014 by Jörg Hinrichs, refactored from various
|
||||
places formerly created by David White <dave@whitevine.net>
|
||||
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.
|
||||
*/
|
||||
|
||||
#ifndef SAVE_INDEX_H_INCLUDED
|
||||
#define SAVE_INDEX_H_INCLUDED
|
||||
|
||||
//#include "filesystem.hpp"
|
||||
//#include "gamestatus.hpp"
|
||||
//#include "tod_manager.hpp"
|
||||
//#include "show_dialog.hpp"
|
||||
#include "config.hpp"
|
||||
#include "serialization/compression.hpp"
|
||||
|
||||
class config_writer;
|
||||
class game_display;
|
||||
|
||||
|
||||
namespace savegame {
|
||||
|
||||
/** Filename and modification date for a file list */
|
||||
class save_info {
|
||||
private:
|
||||
friend class create_save_info;
|
||||
private:
|
||||
save_info(const std::string& name, const time_t& modified) :
|
||||
name_(name), modified_(modified)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::string& name() const { return name_; }
|
||||
const time_t& modified() const { return modified_; }
|
||||
public:
|
||||
std::string format_time_summary() const;
|
||||
std::string format_time_local() const;
|
||||
const config& summary() const;
|
||||
private:
|
||||
std::string name_;
|
||||
time_t modified_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A structure for comparing to save_info objects based on their modified time.
|
||||
* If the times are equal, will order based on the name.
|
||||
*/
|
||||
struct save_info_less_time {
|
||||
bool operator()(const save_info& a, const save_info& b) const;
|
||||
};
|
||||
|
||||
std::vector<save_info> get_saves_list(const std::string* dir = NULL, const std::string* filter = NULL);
|
||||
|
||||
/** Read the complete config information out of a savefile. */
|
||||
void read_save_file(const std::string& name, config& cfg, std::string* error_log);
|
||||
|
||||
/** Remove autosaves that are no longer needed (according to the autosave policy in the preferences). */
|
||||
void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves);
|
||||
|
||||
/** Delete a savegame. */
|
||||
void delete_game(const std::string& name);
|
||||
|
||||
|
||||
class create_save_info {
|
||||
public:
|
||||
create_save_info(const std::string* d = NULL) ;
|
||||
save_info operator()(const std::string& filename) const ;
|
||||
const std::string dir;
|
||||
};
|
||||
|
||||
|
||||
class save_index_class
|
||||
{
|
||||
public:
|
||||
void rebuild(const std::string& name) ;
|
||||
void rebuild(const std::string& name, const time_t& modified) ;
|
||||
void remove(const std::string& name) ;
|
||||
void set_modified(const std::string& name, const time_t& modified) ;
|
||||
config& get(const std::string& name) ;
|
||||
public:
|
||||
void write_save_index() ;
|
||||
|
||||
public:
|
||||
save_index_class();
|
||||
private:
|
||||
config& data(const std::string& name) ;
|
||||
config& data() ;
|
||||
private:
|
||||
bool loaded_;
|
||||
config data_;
|
||||
std::map< std::string, time_t > modified_;
|
||||
};
|
||||
extern save_index_class save_index_manager;
|
||||
} //end of namespace savegame
|
||||
|
||||
void replace_underbar2space(std::string &name);
|
||||
void replace_space2underbar(std::string &name);
|
||||
|
||||
#endif
|
366
src/savegame.cpp
366
src/savegame.cpp
|
@ -18,6 +18,7 @@
|
|||
|
||||
#include "savegame.hpp"
|
||||
|
||||
#include "save_index.hpp"
|
||||
#include "carryover.hpp"
|
||||
#include "dialogs.hpp" //FIXME: get rid of this as soon as the two remaining dialogs are moved to gui2
|
||||
#include "format_time_summary.hpp"
|
||||
|
@ -55,342 +56,9 @@ static lg::log_domain log_engine("engine");
|
|||
static lg::log_domain log_enginerefac("enginerefac");
|
||||
#define LOG_RG LOG_STREAM(info, log_enginerefac)
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef INADDR_ANY
|
||||
#undef INADDR_ANY
|
||||
#endif
|
||||
#ifdef INADDR_BROADCAST
|
||||
#undef INADDR_BROADCAST
|
||||
#endif
|
||||
#ifdef INADDR_NONE
|
||||
#undef INADDR_NONE
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
/**
|
||||
* conv_ansi_utf8()
|
||||
* - Convert a string between ANSI encoding (for Windows filename) and UTF-8
|
||||
* string &name
|
||||
* - filename to be converted
|
||||
* bool a2u
|
||||
* - if true, convert the string from ANSI to UTF-8.
|
||||
* - if false, reverse. (convert it from UTF-8 to ANSI)
|
||||
*/
|
||||
void conv_ansi_utf8(std::string &name, bool a2u) {
|
||||
int wlen = MultiByteToWideChar(a2u ? CP_ACP : CP_UTF8, 0,
|
||||
name.c_str(), -1, NULL, 0);
|
||||
if (wlen == 0) return;
|
||||
WCHAR *wc = new WCHAR[wlen];
|
||||
if (wc == NULL) return;
|
||||
if (MultiByteToWideChar(a2u ? CP_ACP : CP_UTF8, 0, name.c_str(), -1,
|
||||
wc, wlen) == 0) {
|
||||
delete [] wc;
|
||||
return;
|
||||
}
|
||||
int alen = WideCharToMultiByte(!a2u ? CP_ACP : CP_UTF8, 0, wc, wlen,
|
||||
NULL, 0, NULL, NULL);
|
||||
if (alen == 0) {
|
||||
delete [] wc;
|
||||
return;
|
||||
}
|
||||
CHAR *ac = new CHAR[alen];
|
||||
if (ac == NULL) {
|
||||
delete [] wc;
|
||||
return;
|
||||
}
|
||||
WideCharToMultiByte(!a2u ? CP_ACP : CP_UTF8, 0, wc, wlen,
|
||||
ac, alen, NULL, NULL);
|
||||
delete [] wc;
|
||||
if (ac == NULL) {
|
||||
return;
|
||||
}
|
||||
name = ac;
|
||||
delete [] ac;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
void replace_underbar2space(std::string &name) {
|
||||
LOG_SAVE << "conv(A2U)-from:[" << name << "]" << std::endl;
|
||||
conv_ansi_utf8(name, true);
|
||||
LOG_SAVE << "conv(A2U)-to:[" << name << "]" << std::endl;
|
||||
LOG_SAVE << "replace_underbar2space-from:[" << name << "]" << std::endl;
|
||||
std::replace(name.begin(), name.end(), '_', ' ');
|
||||
LOG_SAVE << "replace_underbar2space-to:[" << name << "]" << std::endl;
|
||||
}
|
||||
|
||||
void replace_space2underbar(std::string &name) {
|
||||
LOG_SAVE << "conv(U2A)-from:[" << name << "]" << std::endl;
|
||||
conv_ansi_utf8(name, false);
|
||||
LOG_SAVE << "conv(U2A)-to:[" << name << "]" << std::endl;
|
||||
LOG_SAVE << "replace_space2underbar-from:[" << name << "]" << std::endl;
|
||||
std::replace(name.begin(), name.end(), ' ', '_');
|
||||
LOG_SAVE << "replace_space2underbar-to:[" << name << "]" << std::endl;
|
||||
}
|
||||
#else /* ! _WIN32 */
|
||||
void replace_underbar2space(std::string &name) {
|
||||
std::replace(name.begin(),name.end(),'_',' ');
|
||||
}
|
||||
void replace_space2underbar(std::string &name) {
|
||||
std::replace(name.begin(),name.end(),' ','_');
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
|
||||
namespace savegame {
|
||||
|
||||
class save_index_class
|
||||
{
|
||||
public:
|
||||
void rebuild(const std::string& name) {
|
||||
std::string filename = name;
|
||||
replace_space2underbar(filename);
|
||||
time_t modified = file_create_time(get_saves_dir() + "/" + filename);
|
||||
rebuild(name, modified);
|
||||
}
|
||||
void rebuild(const std::string& name, const time_t& modified) {
|
||||
log_scope("load_summary_from_file");
|
||||
config& summary = data(name);
|
||||
try {
|
||||
config full;
|
||||
std::string dummy;
|
||||
read_save_file(name, full, &dummy);
|
||||
::extract_summary_from_config(full, summary);
|
||||
} catch(game::load_game_failed&) {
|
||||
summary["corrupt"] = true;
|
||||
}
|
||||
summary["mod_time"] = str_cast(static_cast<int>(modified));
|
||||
write_save_index();
|
||||
}
|
||||
void remove(const std::string& name) {
|
||||
config& root = data();
|
||||
root.remove_attribute(name);
|
||||
write_save_index();
|
||||
}
|
||||
void set_modified(const std::string& name, const time_t& modified) {
|
||||
modified_[name] = modified;
|
||||
}
|
||||
config& get(const std::string& name) {
|
||||
config& result = data(name);
|
||||
time_t m = modified_[name];
|
||||
config::attribute_value& mod_time = result["mod_time"];
|
||||
if (mod_time.empty() || static_cast<time_t>(mod_time.to_int()) != m) {
|
||||
rebuild(name, m);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
public:
|
||||
void write_save_index() {
|
||||
log_scope("write_save_index()");
|
||||
try {
|
||||
scoped_ostream stream = ostream_file(get_save_index_file());
|
||||
if (preferences::save_compression_format() != compression::NONE) {
|
||||
// TODO: maybe allow writing this using bz2 too?
|
||||
write_gz(*stream, data());
|
||||
} else {
|
||||
write(*stream, data());
|
||||
}
|
||||
} catch(io_exception& e) {
|
||||
ERR_SAVE << "error writing to save index file: '" << e.what() << "'" << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
save_index_class()
|
||||
: loaded_(false)
|
||||
, data_()
|
||||
, modified_()
|
||||
{
|
||||
}
|
||||
private:
|
||||
config& data(const std::string& name) {
|
||||
config& cfg = data();
|
||||
if (config& sv = cfg.find_child("save", "save", name)) {
|
||||
return sv;
|
||||
}
|
||||
|
||||
config& res = cfg.add_child("save");
|
||||
res["save"] = name;
|
||||
return res;
|
||||
}
|
||||
config& data() {
|
||||
if(loaded_ == false) {
|
||||
try {
|
||||
scoped_istream stream = istream_file(get_save_index_file());
|
||||
try {
|
||||
read_gz(data_, *stream);
|
||||
} catch (boost::iostreams::gzip_error&) {
|
||||
stream->seekg(0);
|
||||
read(data_, *stream);
|
||||
}
|
||||
} catch(io_exception& e) {
|
||||
ERR_SAVE << "error reading save index: '" << e.what() << "'" << std::endl;
|
||||
} catch(config::error& e) {
|
||||
ERR_SAVE << "error parsing save index config file:\n" << e.message << std::endl;
|
||||
data_.clear();
|
||||
}
|
||||
loaded_ = true;
|
||||
}
|
||||
return data_;
|
||||
}
|
||||
private:
|
||||
bool loaded_;
|
||||
config data_;
|
||||
std::map< std::string, time_t > modified_;
|
||||
} save_index_manager;
|
||||
|
||||
class filename_filter {
|
||||
public:
|
||||
filename_filter(const std::string& filter) : filter_(filter) {
|
||||
}
|
||||
bool operator()(const std::string& filename) const {
|
||||
return filename.end() == std::search(filename.begin(), filename.end(),
|
||||
filter_.begin(), filter_.end());
|
||||
}
|
||||
private:
|
||||
std::string filter_;
|
||||
};
|
||||
|
||||
class create_save_info {
|
||||
public:
|
||||
create_save_info(const std::string* d = NULL) : dir(d ? *d : get_saves_dir()) {
|
||||
}
|
||||
save_info operator()(const std::string& filename) const {
|
||||
std::string name = filename;
|
||||
replace_underbar2space(name);
|
||||
time_t modified = file_create_time(dir + "/" + filename);
|
||||
save_index_manager.set_modified(name, modified);
|
||||
return save_info(name, modified);
|
||||
}
|
||||
const std::string dir;
|
||||
};
|
||||
|
||||
/** Get a list of available saves. */
|
||||
std::vector<save_info> get_saves_list(const std::string* dir, const std::string* filter)
|
||||
{
|
||||
create_save_info creator(dir);
|
||||
|
||||
std::vector<std::string> filenames;
|
||||
get_files_in_dir(creator.dir,&filenames);
|
||||
|
||||
if (filter) {
|
||||
filenames.erase(std::remove_if(filenames.begin(), filenames.end(),
|
||||
filename_filter(*filter)),
|
||||
filenames.end());
|
||||
}
|
||||
|
||||
std::vector<save_info> result;
|
||||
std::transform(filenames.begin(), filenames.end(),
|
||||
std::back_inserter(result), creator);
|
||||
std::sort(result.begin(),result.end(),save_info_less_time());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
const config& save_info::summary() const {
|
||||
return save_index_manager.get(name());
|
||||
}
|
||||
|
||||
std::string save_info::format_time_local() const
|
||||
{
|
||||
char time_buf[256] = {0};
|
||||
tm* tm_l = localtime(&modified());
|
||||
if (tm_l) {
|
||||
const size_t res = strftime(time_buf,sizeof(time_buf),
|
||||
(preferences::use_twelve_hour_clock_format() ? _("%a %b %d %I:%M %p %Y") : _("%a %b %d %H:%M %Y")),
|
||||
tm_l);
|
||||
if(res == 0) {
|
||||
time_buf[0] = 0;
|
||||
}
|
||||
} else {
|
||||
LOG_SAVE << "localtime() returned null for time " << this->modified() << ", save " << name();
|
||||
}
|
||||
|
||||
return time_buf;
|
||||
}
|
||||
|
||||
std::string save_info::format_time_summary() const
|
||||
{
|
||||
time_t t = modified();
|
||||
return util::format_time_summary(t);
|
||||
}
|
||||
|
||||
bool save_info_less_time::operator() (const save_info& a, const save_info& b) const {
|
||||
if (a.modified() > b.modified()) {
|
||||
return true;
|
||||
} else if (a.modified() < b.modified()) {
|
||||
return false;
|
||||
// Special funky case; for files created in the same second,
|
||||
// a replay file sorts less than a non-replay file. Prevents
|
||||
// a timing-dependent bug where it may look like, at the end
|
||||
// of a scenario, the replay and the autosave for the next
|
||||
// scenario are displayed in the wrong order.
|
||||
} else if (a.name().find(_(" replay"))==std::string::npos && b.name().find(_(" replay"))!=std::string::npos) {
|
||||
return true;
|
||||
} else if (a.name().find(_(" replay"))!=std::string::npos && b.name().find(_(" replay"))==std::string::npos) {
|
||||
return false;
|
||||
} else {
|
||||
return a.name() > b.name();
|
||||
}
|
||||
}
|
||||
|
||||
static std::istream* find_save_file(const std::string &name, const std::string &alt_name, const std::vector<std::string> &suffixes) {
|
||||
BOOST_FOREACH(const std::string &suf, suffixes) {
|
||||
std::istream *file_stream = istream_file(get_saves_dir() + "/" + name + suf);
|
||||
if (file_stream->fail()) {
|
||||
delete file_stream;
|
||||
file_stream = istream_file(get_saves_dir() + "/" + alt_name + suf);
|
||||
}
|
||||
if (!file_stream->fail())
|
||||
return file_stream;
|
||||
else
|
||||
delete file_stream;
|
||||
}
|
||||
LOG_SAVE << "Could not open supplied filename '" << name << "'\n";
|
||||
throw game::load_game_failed();
|
||||
}
|
||||
|
||||
void read_save_file(const std::string& name, config& cfg, std::string* error_log)
|
||||
{
|
||||
std::string modified_name = name;
|
||||
replace_space2underbar(modified_name);
|
||||
|
||||
static const std::vector<std::string> suffixes = boost::assign::list_of("")(".gz")(".bz2");
|
||||
scoped_istream file_stream = find_save_file(modified_name, name, suffixes);
|
||||
|
||||
cfg.clear();
|
||||
try{
|
||||
/*
|
||||
* Test the modified name, since it might use a .gz
|
||||
* file even when not requested.
|
||||
*/
|
||||
if(is_gzip_file(modified_name)) {
|
||||
read_gz(cfg, *file_stream);
|
||||
} else if(is_bzip2_file(modified_name)) {
|
||||
read_bz2(cfg, *file_stream);
|
||||
} else {
|
||||
read(cfg, *file_stream);
|
||||
}
|
||||
} catch(const std::ios_base::failure& e) {
|
||||
LOG_SAVE << e.what();
|
||||
if(error_log) {
|
||||
*error_log += e.what();
|
||||
}
|
||||
throw game::load_game_failed();
|
||||
} catch(const config::error &err) {
|
||||
LOG_SAVE << err.message;
|
||||
if(error_log) {
|
||||
*error_log += err.message;
|
||||
}
|
||||
throw game::load_game_failed();
|
||||
}
|
||||
|
||||
if(cfg.empty()) {
|
||||
LOG_SAVE << "Could not parse file data into config\n";
|
||||
throw game::load_game_failed();
|
||||
}
|
||||
}
|
||||
|
||||
bool save_game_exists(const std::string& name, compression::format compressed)
|
||||
{
|
||||
std::string fname = name;
|
||||
|
@ -414,33 +82,6 @@ void clean_saves(const std::string& label)
|
|||
}
|
||||
}
|
||||
|
||||
void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
|
||||
{
|
||||
const std::string auto_save = _("Auto-Save");
|
||||
int countdown = autosavemax;
|
||||
if (countdown == infinite_auto_saves)
|
||||
return;
|
||||
|
||||
std::vector<save_info> games = get_saves_list(NULL, &auto_save);
|
||||
for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); ++i) {
|
||||
if (countdown-- <= 0) {
|
||||
LOG_SAVE << "Deleting savegame '" << i->name() << "'\n";
|
||||
delete_game(i->name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void delete_game(const std::string& name)
|
||||
{
|
||||
std::string modified_name = name;
|
||||
replace_space2underbar(modified_name);
|
||||
|
||||
remove((get_saves_dir() + "/" + name).c_str());
|
||||
remove((get_saves_dir() + "/" + modified_name).c_str());
|
||||
|
||||
save_index_manager.remove(name);
|
||||
}
|
||||
|
||||
loadgame::loadgame(display& gui, const config& game_config, game_state& gamestate)
|
||||
: game_config_(game_config)
|
||||
, gui_(gui)
|
||||
|
@ -1082,11 +723,6 @@ void ingame_savegame::create_filename()
|
|||
set_filename(stream.str());
|
||||
}
|
||||
|
||||
void ingame_savegame::before_save()
|
||||
{
|
||||
savegame::before_save();
|
||||
gamestate().write_snapshot(snapshot());
|
||||
}
|
||||
|
||||
void ingame_savegame::write_game(config_writer &out) {
|
||||
log_scope("write_game");
|
||||
|
|
|
@ -18,10 +18,8 @@
|
|||
|
||||
#include "filesystem.hpp"
|
||||
#include "gamestatus.hpp"
|
||||
#include "tod_manager.hpp"
|
||||
#include "show_dialog.hpp"
|
||||
#include "serialization/compression.hpp"
|
||||
|
||||
class config_writer;
|
||||
class game_display;
|
||||
|
||||
|
@ -30,52 +28,12 @@ struct illegal_filename_exception {};
|
|||
|
||||
namespace savegame {
|
||||
|
||||
/** Filename and modification date for a file list */
|
||||
class save_info {
|
||||
private:
|
||||
friend class create_save_info;
|
||||
private:
|
||||
save_info(const std::string& name, const time_t& modified) :
|
||||
name_(name), modified_(modified)
|
||||
{}
|
||||
|
||||
public:
|
||||
const std::string& name() const { return name_; }
|
||||
const time_t& modified() const { return modified_; }
|
||||
public:
|
||||
std::string format_time_summary() const;
|
||||
std::string format_time_local() const;
|
||||
const config& summary() const;
|
||||
private:
|
||||
std::string name_;
|
||||
time_t modified_;
|
||||
};
|
||||
|
||||
/**
|
||||
* A structure for comparing to save_info objects based on their modified time.
|
||||
* If the times are equal, will order based on the name.
|
||||
*/
|
||||
struct save_info_less_time {
|
||||
bool operator()(const save_info& a, const save_info& b) const;
|
||||
};
|
||||
|
||||
std::vector<save_info> get_saves_list(const std::string* dir = NULL, const std::string* filter = NULL);
|
||||
|
||||
/** Read the complete config information out of a savefile. */
|
||||
void read_save_file(const std::string& name, config& cfg, std::string* error_log);
|
||||
|
||||
/** Returns true if there is already a savegame with that name. */
|
||||
bool save_game_exists(const std::string& name, compression::format compressed);
|
||||
|
||||
/** Delete all autosaves of a certain scenario. */
|
||||
void clean_saves(const std::string& label);
|
||||
|
||||
/** Remove autosaves that are no longer needed (according to the autosave policy in the preferences). */
|
||||
void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves);
|
||||
|
||||
/** Delete a savegame. */
|
||||
void delete_game(const std::string& name);
|
||||
|
||||
/** The class for loading a savefile. */
|
||||
class loadgame
|
||||
{
|
||||
|
@ -186,7 +144,7 @@ protected:
|
|||
config& snapshot() { return snapshot_; }
|
||||
|
||||
/** If there needs to be some data fiddling before saving the game, this is the place to go. */
|
||||
virtual void before_save();
|
||||
void before_save();
|
||||
|
||||
/** Writing the savegame config to a file. */
|
||||
virtual void write_game(config_writer &out);
|
||||
|
@ -242,8 +200,6 @@ private:
|
|||
/** Create a filename for automatic saves */
|
||||
virtual void create_filename();
|
||||
|
||||
/** Builds the snapshot config. */
|
||||
virtual void before_save();
|
||||
|
||||
void write_game(config_writer &out);
|
||||
|
||||
|
|
|
@ -27,7 +27,6 @@
|
|||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
class display;
|
||||
class game_state;
|
||||
class vconfig;
|
||||
|
||||
namespace storyscreen {
|
||||
|
|
|
@ -29,7 +29,6 @@
|
|||
class config;
|
||||
class vconfig;
|
||||
class display;
|
||||
class game_state;
|
||||
|
||||
namespace storyscreen {
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#include "time_of_day.hpp"
|
||||
#include "savegame_config.hpp"
|
||||
|
||||
class game_state;
|
||||
class gamemap;
|
||||
class unit_map;
|
||||
|
||||
|
|
|
@ -231,7 +231,7 @@ struct variable_info
|
|||
std::string key; //the name of the internal attribute or child
|
||||
bool explicit_index; //true if query ended in [...] specifier
|
||||
size_t index; //the index of the child
|
||||
config *vars; //the containing node in game_state::variables
|
||||
config *vars; //the containing node in game_data s variables
|
||||
|
||||
/**
|
||||
* Results: after deciding the desired type, these methods can retrieve the result
|
||||
|
|
Loading…
Add table
Reference in a new issue