Savegame reorganization Step 1: a simpler interface to saving and loading.

Move save_game_exists, open_save_game, delete_game, and get_saves_list
from gamestatus.cpp to savegame.cpp. Setup a savegame_manager class to
collect all those homeless functions. Fix bug #13364.
This commit is contained in:
Jörg Hinrichs 2009-04-15 22:18:15 +00:00
parent ba07627fb6
commit c221e1b5d0
8 changed files with 156 additions and 165 deletions

View file

@ -298,7 +298,7 @@ int get_save_name(display & disp,const std::string& message, const std::string&
continue;
}
if (res == 0 && save_game_exists(*fname)) {
if (res == 0 && savegame_manager::save_game_exists(*fname)) {
std::stringstream s;
s << _("Save already exists. Do you want to overwrite it?")
<< std::endl << _("Name: ") << *fname;
@ -357,7 +357,7 @@ gui::dialog_button_action::RESULT delete_save::button_pressed(int menu_selection
filter_.delete_item(menu_selection);
// Delete the file
delete_game(saves_[index].name);
savegame_manager::delete_game(saves_[index].name);
// Remove it from the list of saves
saves_.erase(saves_.begin() + index);
@ -421,7 +421,7 @@ void save_preview_pane::draw_contents()
config& summary = *(*summaries_)[index_];
if (summary["label"] == ""){
try {
save_summary::load_summary((*info_)[index_].name, summary, &dummy);
savegame_manager::load_summary((*info_)[index_].name, summary, &dummy);
*(*summaries_)[index_] = summary;
} catch(game::load_game_failed&) {
summary["corrupt"] = "yes";
@ -631,7 +631,7 @@ std::string load_game_dialog(display& disp, const config& game_config, bool* sho
std::vector<save_info> games;
{
cursor::setter cur(cursor::WAIT);
games = get_saves_list();
games = savegame_manager::get_saves_list();
}
if(games.empty()) {

View file

@ -24,7 +24,6 @@
#include "log.hpp"
#include "game_preferences.hpp"
#include "replay.hpp"
#include "savegame.hpp" //FIXME: only because of replace_space2underbar
#include "statistics.hpp"
#include "unit_id.hpp"
#include "wesconfig.h"
@ -588,90 +587,6 @@ void game_state::write_snapshot(config& cfg) const
}
}
/**
* 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 {
if (a.time_modified > b.time_modified) {
return true;
} else if (a.time_modified < b.time_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;
}
}
};
std::vector<save_info> get_saves_list(const std::string *dir, const std::string* filter)
{
// Don't use a reference, it seems to break on arklinux with GCC-4.3.
const std::string saves_dir = (dir) ? *dir : get_saves_dir();
std::vector<std::string> saves;
get_files_in_dir(saves_dir,&saves);
std::vector<save_info> res;
for(std::vector<std::string>::iterator i = saves.begin(); i != saves.end(); ++i) {
if(filter && std::search(i->begin(), i->end(), filter->begin(), filter->end()) == i->end()) {
continue;
}
const time_t modified = file_create_time(saves_dir + "/" + *i);
replace_underbar2space(*i);
res.push_back(save_info(*i,modified));
}
std::sort(res.begin(),res.end(),save_info_less_time());
return res;
}
bool save_game_exists(const std::string& name)
{
std::string fname = name;
replace_space2underbar(fname);
if(preferences::compress_saves()) {
fname += ".gz";
}
return file_exists(get_saves_dir() + "/" + fname);
}
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());
}
// Throws game::save_game_failed
scoped_ostream open_save_game(const std::string &label)
{
std::string name = label;
replace_space2underbar(name);
try {
return scoped_ostream(ostream_file(get_saves_dir() + "/" + name));
} catch(io_exception& e) {
throw game::save_game_failed(e.what());
}
}
namespace {
bool save_index_loaded = false;
config save_index_cfg;

View file

@ -300,37 +300,12 @@ private:
std::string generate_game_uuid();
/**
* Holds all the data needed to start a scenario.
*
* I.e. this is the object serialized to disk when saving/loading a game.
* It is also the object which needs to be created to start a new game.
*/
struct save_info {
save_info(const std::string& n, time_t t) : name(n), time_modified(t) {}
std::string name;
time_t time_modified;
};
/** Get a list of available saves. */
std::vector<save_info> get_saves_list(const std::string* dir = NULL, const std::string* filter = NULL);
void write_players(game_state& gamestate, config& cfg);
/** Returns true iff there is already a savegame with that name. */
bool save_game_exists(const std::string & name);
/** Throws game::save_game_failed. */
scoped_ostream open_save_game(const std::string &label);
/** Delete a savegame. */
void delete_game(const std::string& name);
config& save_summary(std::string save);
void write_save_index();
void replace_underbar2space(std::string &name);
void extract_summary_from_config(config& cfg_save, config& cfg_summary);
#endif

View file

@ -51,26 +51,6 @@
#define LOG_NG LOG_STREAM(info, engine)
#define DBG_NG LOG_STREAM(info, engine)
namespace {
void remove_old_auto_saves()
{
const std::string auto_save = _("Auto-Save");
int countdown = preferences::autosavemax();
if (countdown == preferences::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_NG << "Deleting savegame '" << i->name << "'\n";
delete_game(i->name);
}
}
}
} // end anonymous namespace
namespace events{
class delete_recall_unit : public gui::dialog_button_action
@ -629,7 +609,7 @@ private:
end = SDL_GetTicks();
LOG_NG << "Milliseconds to save " << save.filename() << ": " << end - start << "\n";
remove_old_auto_saves();
savegame_manager::remove_old_auto_saves();
}
void menu_handler::preferences()

View file

@ -808,7 +808,7 @@ void play_controller::expand_autosaves(std::vector<std::string>& items)
std::vector<std::string> newsaves;
for (unsigned int turn = status_.turn(); turn != 0; turn--) {
std::string name = gamestate_.label + "-" + _("Auto-Save") + lexical_cast<std::string>(turn);
if (save_game_exists(name)) {
if (savegame_manager::save_game_exists(name)) {
if(preferences::compress_saves()) {
newsaves.push_back(name + ".gz");
} else {

View file

@ -102,19 +102,6 @@ void play_replay(display& disp, game_state& gamestate, const config& game_config
}
}
static void clean_saves(const std::string &label)
{
std::vector<save_info> games = get_saves_list();
std::string prefix = label + "-" + _("Auto-Save");
std::cerr << "Cleaning saves with prefix '" << prefix << "'\n";
for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); i++) {
if (i->name.compare(0, prefix.length(), prefix) == 0) {
std::cerr << "Deleting savegame '" << i->name << "'\n";
delete_game(i->name);
}
}
}
static LEVEL_RESULT playsingle_scenario(const config& game_config,
const config* level, display& disp, game_state& state_of_game,
const config::const_child_itors &story, upload_log &log,
@ -395,12 +382,11 @@ LEVEL_RESULT play_game(display& disp, game_state& gamestate, const config& game_
// need to change this test.
if (res == VICTORY || (io_type != IO_NONE && res == DEFEAT)) {
if (preferences::delete_saves())
clean_saves(gamestate.label);
savegame_manager::clean_saves(gamestate.label);
if (preferences::save_replays()) {
replay_savegame save(gamestate);
save.save_game(""); //string is not used, noninteractive save
//::save_replay(gamestate);
save.save_game_interactive(disp, "", gui::OK_CANCEL, false, false);
}
}
@ -595,7 +581,7 @@ LEVEL_RESULT play_game(display& disp, game_state& gamestate, const config& game_
if (gamestate.campaign_type == "scenario"){
if (preferences::delete_saves())
clean_saves(gamestate.label);
savegame_manager::clean_saves(gamestate.label);
}
return VICTORY;
}

View file

@ -106,7 +106,32 @@
}
#endif /* _WIN32 */
static void read_save_file(const std::string& name, config& cfg, std::string* error_log)
/**
* 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 {
if (a.time_modified > b.time_modified) {
return true;
} else if (a.time_modified < b.time_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;
}
}
};
void savegame_manager::read_save_file(const std::string& name, config& cfg, std::string* error_log)
{
std::string modified_name = name;
replace_space2underbar(modified_name);
@ -135,7 +160,7 @@ static void read_save_file(const std::string& name, config& cfg, std::string* er
}
}
void save_summary::load_summary(const std::string& name, config& cfg_summary, std::string* error_log){
void savegame_manager::load_summary(const std::string& name, config& cfg_summary, std::string* error_log){
log_scope("load_game_summary");
config cfg;
@ -144,6 +169,81 @@ void save_summary::load_summary(const std::string& name, config& cfg_summary, st
::extract_summary_from_config(cfg, cfg_summary);
}
bool savegame_manager::save_game_exists(const std::string& name)
{
std::string fname = name;
replace_space2underbar(fname);
if(preferences::compress_saves()) {
fname += ".gz";
}
return file_exists(get_saves_dir() + "/" + fname);
}
std::vector<save_info> savegame_manager::get_saves_list(const std::string *dir, const std::string* filter)
{
// Don't use a reference, it seems to break on arklinux with GCC-4.3.
const std::string saves_dir = (dir) ? *dir : get_saves_dir();
std::vector<std::string> saves;
get_files_in_dir(saves_dir,&saves);
std::vector<save_info> res;
for(std::vector<std::string>::iterator i = saves.begin(); i != saves.end(); ++i) {
if(filter && std::search(i->begin(), i->end(), filter->begin(), filter->end()) == i->end()) {
continue;
}
const time_t modified = file_create_time(saves_dir + "/" + *i);
replace_underbar2space(*i);
res.push_back(save_info(*i,modified));
}
std::sort(res.begin(),res.end(),save_info_less_time());
return res;
}
void savegame_manager::clean_saves(const std::string &label)
{
std::vector<save_info> games = get_saves_list();
std::string prefix = label + "-" + _("Auto-Save");
std::cerr << "Cleaning saves with prefix '" << prefix << "'\n";
for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); i++) {
if (i->name.compare(0, prefix.length(), prefix) == 0) {
std::cerr << "Deleting savegame '" << i->name << "'\n";
delete_game(i->name);
}
}
}
void savegame_manager::remove_old_auto_saves()
{
const std::string auto_save = _("Auto-Save");
int countdown = preferences::autosavemax();
if (countdown == preferences::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 savegame_manager::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());
}
loadgame::loadgame(display& gui, const config& game_config, game_state& gamestate)
: game_config_(game_config)
, gui_(gui)
@ -188,7 +288,7 @@ void loadgame::load_game(std::string& filename, bool show_replay, bool cancel_or
throw load_game_cancelled_exception();
std::string error_log;
read_save_file(filename_, load_config_, &error_log);
savegame_manager::read_save_file(filename_, load_config_, &error_log);
if(!error_log.empty()) {
try {
@ -265,7 +365,7 @@ void loadgame::load_multiplayer_game()
cursor::setter cur(cursor::WAIT);
log_scope("load_game");
read_save_file(filename_, load_config_, &error_log);
savegame_manager::read_save_file(filename_, load_config_, &error_log);
copy_era(load_config_);
gamestate_ = game_state(load_config_);
@ -455,6 +555,19 @@ void savegame::finish_save_game(const config_writer &out)
}
}
// Throws game::save_game_failed
scoped_ostream savegame::open_save_game(const std::string &label)
{
std::string name = label;
replace_space2underbar(name);
try {
return scoped_ostream(ostream_file(get_saves_dir() + "/" + name));
} catch(io_exception& e) {
throw game::save_game_failed(e.what());
}
}
void savegame::extract_summary_data_from_save(config& out)
{
const bool has_replay = gamestate_.replay_data.empty() == false;

View file

@ -26,13 +26,37 @@ struct load_game_cancelled_exception
{
};
class save_summary
/**
* Holds all the data needed to start a scenario. YogiHH: really??
*
* I.e. this is the object serialized to disk when saving/loading a game.
* It is also the object which needs to be created to start a new game.
*/
struct save_info {
save_info(const std::string& n, time_t t) : name(n), time_modified(t) {}
std::string name;
time_t time_modified;
};
class savegame_manager
{
public:
save_summary() {}
virtual ~save_summary() {}
static void load_summary(const std::string& name, config& cfg_summary, std::string* error_log);
static 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. */
static bool save_game_exists(const std::string& name);
/** Get a list of available saves. */
static std::vector<save_info> get_saves_list(const std::string *dir = NULL, const std::string* filter = NULL);
static void clean_saves(const std::string &label);
static void remove_old_auto_saves();
/** Delete a savegame. */
static void delete_game(const std::string& name);
private:
/** Default-Konstruktor (don't instantiate this class) */
savegame_manager() {}
};
class loadgame
@ -117,6 +141,8 @@ private:
void write_game(config_writer &out) const;
void finish_save_game(const config_writer &out);
/** Throws game::save_game_failed. */
scoped_ostream open_save_game(const std::string &label);
void extract_summary_data_from_save(config& out);
game_state& gamestate_;
@ -199,10 +225,6 @@ private:
virtual void before_save();
};
#ifdef _WIN32
void conv_ansi_utf8(std::string &name, bool a2u);
#endif
void replace_underbar2space(std::string &name);
void replace_space2underbar(std::string &name);