From 294379a157767cdab4dad348332c392411411d9c Mon Sep 17 00:00:00 2001 From: gfgtdf Date: Fri, 6 Jun 2014 02:37:56 +0200 Subject: [PATCH] split savegame.cpp into 2 --- src/CMakeLists.txt | 1 + src/SConscript | 1 + src/dialogs.cpp | 1 + src/gui/dialogs/data_manage.cpp | 1 + src/gui/dialogs/data_manage.hpp | 2 +- src/gui/dialogs/game_load.cpp | 1 + src/gui/dialogs/game_load.hpp | 2 +- src/menu_events.cpp | 1 + src/save_index.cpp | 396 ++++++++++++++++++++++++++++++++ src/save_index.hpp | 108 +++++++++ src/savegame.cpp | 361 +---------------------------- src/savegame.hpp | 42 ---- 12 files changed, 513 insertions(+), 404 deletions(-) create mode 100644 src/save_index.cpp create mode 100644 src/save_index.hpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 0a82bb57df19..c3808cb60f23 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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 diff --git a/src/SConscript b/src/SConscript index fde9ae233d3d..859860513947 100644 --- a/src/SConscript +++ b/src/SConscript @@ -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 diff --git a/src/dialogs.cpp b/src/dialogs.cpp index 342c5ddab4a0..8a3dd84c3d58 100644 --- a/src/dialogs.cpp +++ b/src/dialogs.cpp @@ -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" diff --git a/src/gui/dialogs/data_manage.cpp b/src/gui/dialogs/data_manage.cpp index d790c1fa80ca..556188ae4872 100644 --- a/src/gui/dialogs/data_manage.cpp +++ b/src/gui/dialogs/data_manage.cpp @@ -38,6 +38,7 @@ #include "gui/widgets/window.hpp" #include "language.hpp" #include "preferences_display.hpp" +#include "savegame.hpp" #include "utils/foreach.tpp" #include diff --git a/src/gui/dialogs/data_manage.hpp b/src/gui/dialogs/data_manage.hpp index 481fc3339d5e..921fa4cfa002 100644 --- a/src/gui/dialogs/data_manage.hpp +++ b/src/gui/dialogs/data_manage.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 diff --git a/src/gui/dialogs/game_load.cpp b/src/gui/dialogs/game_load.cpp index f0ea59296253..f6d7c29f2928 100644 --- a/src/gui/dialogs/game_load.cpp +++ b/src/gui/dialogs/game_load.cpp @@ -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" diff --git a/src/gui/dialogs/game_load.hpp b/src/gui/dialogs/game_load.hpp index c5c0c78d0e19..cb372e0b5b4f 100644 --- a/src/gui/dialogs/game_load.hpp +++ b/src/gui/dialogs/game_load.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 diff --git a/src/menu_events.cpp b/src/menu_events.cpp index dfabe9632735..53de99dd37b6 100644 --- a/src/menu_events.cpp +++ b/src/menu_events.cpp @@ -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" diff --git a/src/save_index.cpp b/src/save_index.cpp new file mode 100644 index 000000000000..426e946ff09f --- /dev/null +++ b/src/save_index.cpp @@ -0,0 +1,396 @@ +/* + Copyright (C) 2003 - 2014 by Jörg Hinrichs, refactored from various + places formerly created by David White + 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 +#include + +#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 + +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 + + /** + * 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(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(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 get_saves_list(const std::string* dir, const std::string* filter) +{ + create_save_info creator(dir); + + std::vector 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 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 &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 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 games = get_saves_list(NULL, &auto_save); + for (std::vector::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); +} + +} + diff --git a/src/save_index.hpp b/src/save_index.hpp new file mode 100644 index 000000000000..7b1f32c9d432 --- /dev/null +++ b/src/save_index.hpp @@ -0,0 +1,108 @@ +/* + Copyright (C) 2003 - 2014 by Jörg Hinrichs, refactored from various + places formerly created by David White + 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 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 diff --git a/src/savegame.cpp b/src/savegame.cpp index 3b4b3a14bedd..efab69283769 100644 --- a/src/savegame.cpp +++ b/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 - - /** - * 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(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(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 get_saves_list(const std::string* dir, const std::string* filter) -{ - create_save_info creator(dir); - - std::vector 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 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 &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 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 games = get_saves_list(NULL, &auto_save); - for (std::vector::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) diff --git a/src/savegame.hpp b/src/savegame.hpp index f1b5cf787a80..b8c2173ea698 100644 --- a/src/savegame.hpp +++ b/src/savegame.hpp @@ -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 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 {