The Battle for Wesnoth  1.13.4+dev
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
save_index.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2016 by Jörg Hinrichs, refactored from various
3  places formerly created by David White <[email protected]>
4  Part of the Battle for Wesnoth Project http://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #include <boost/iostreams/filter/gzip.hpp>
17 
18 #include "save_index.hpp"
19 
20 #include "format_time_summary.hpp"
21 #include "formula/string_utils.hpp"
22 #include "game_end_exceptions.hpp"
23 #include "game_preferences.hpp"
24 #include "gettext.hpp"
25 #include "log.hpp"
27 #include "serialization/parser.hpp"
28 #include "team.hpp"
29 
30 #include "filesystem.hpp"
31 #include "config.hpp"
32 
33 static lg::log_domain log_engine("engine");
34 #define LOG_SAVE LOG_STREAM(info, log_engine)
35 #define ERR_SAVE LOG_STREAM(err, log_engine)
36 
37 static lg::log_domain log_enginerefac("enginerefac");
38 #define LOG_RG LOG_STREAM(info, log_enginerefac)
39 
41  std::replace(name.begin(),name.end(),'_',' ');
42 }
44  std::replace(name.begin(),name.end(),' ','_');
45 }
46 
47 namespace savegame {
48 
50 
52  std::string filename = name;
53  replace_space2underbar(filename);
54  time_t modified = filesystem::file_modified_time(filesystem::get_saves_dir() + "/" + filename);
55  rebuild(name, modified);
56 }
57 
58 void save_index_class::rebuild(const std::string& name, const time_t& modified) {
59  log_scope("load_summary_from_file");
60  config& summary = data(name);
61  try {
62  config full;
63  std::string dummy;
64  read_save_file(name, full, &dummy);
65  extract_summary_from_config(full, summary);
66  } catch(game::load_game_failed&) {
67  summary["corrupt"] = true;
68  }
69  summary["mod_time"] = std::to_string(static_cast<int>(modified));
71 }
72 
74  config& root = data();
75  root.remove_attribute(name);
77 }
78 
79 void save_index_class::set_modified(const std::string& name, const time_t& modified) {
80  modified_[name] = modified;
81 }
82 
84  config& result = data(name);
85  time_t m = modified_[name];
86  config::attribute_value& mod_time = result["mod_time"];
87  if (mod_time.empty() || static_cast<time_t>(mod_time.to_int()) != m) {
88  rebuild(name, m);
89  }
90  return result;
91 }
92 
94  log_scope("write_save_index()");
95  try {
98  // TODO: maybe allow writing this using bz2 too?
99  write_gz(*stream, data());
100  } else {
101  write(*stream, data());
102  }
103  } catch(filesystem::io_exception& e) {
104  ERR_SAVE << "error writing to save index file: '" << e.what() << "'" << std::endl;
105  }
106 }
107 
109  : loaded_(false)
110  , data_()
111  , modified_()
112 {
113 }
114 
116  config& cfg = data();
117  if (config& sv = cfg.find_child("save", "save", name)) {
118  return sv;
119  }
120  config& res = cfg.add_child("save");
121  res["save"] = name;
122  return res;
123 }
124 
126  if(loaded_ == false) {
127  try {
129  try {
130  read_gz(data_, *stream);
131  } catch (boost::iostreams::gzip_error&) {
132  stream->seekg(0);
133  read(data_, *stream);
134  }
135  } catch(filesystem::io_exception& e) {
136  ERR_SAVE << "error reading save index: '" << e.what() << "'" << std::endl;
137  } catch(config::error& e) {
138  ERR_SAVE << "error parsing save index config file:\n" << e.message << std::endl;
139  data_.clear();
140  }
141  loaded_ = true;
142  }
143  return data_;
144 }
145 
147 
149 public:
151  }
152  bool operator()(const std::string& filename) const {
153  return filename.end() == std::search(filename.begin(), filename.end(),
154  filter_.begin(), filter_.end());
155  }
156 private:
158 };
159 
160 /** Get a list of available saves. */
161 std::vector<save_info> get_saves_list(const std::string* dir, const std::string* filter)
162 {
163  create_save_info creator(dir);
164 
165  std::vector<std::string> filenames;
166  filesystem::get_files_in_dir(creator.dir,&filenames);
167 
168  if (filter) {
169 
170  // Replace the spaces in the filter
171  std::string filter_replaced(filter->begin(), filter->end());
172  replace_space2underbar(filter_replaced);
173 
174  filenames.erase(std::remove_if(filenames.begin(), filenames.end(),
175  filename_filter(filter_replaced)),
176  filenames.end());
177  }
178 
179  std::vector<save_info> result;
180  std::transform(filenames.begin(), filenames.end(),
181  std::back_inserter(result), creator);
182  std::sort(result.begin(),result.end(),save_info_less_time());
183  return result;
184 }
185 
186 
187 const config& save_info::summary() const {
188  return save_index_manager.get(name());
189 }
190 
192 {
193  char time_buf[256] = {0};
194  tm* tm_l = localtime(&modified());
195  if (tm_l) {
196  const std::string format = preferences::use_twelve_hour_clock_format() ? _("%a %b %d %I:%M %p %Y") : _("%a %b %d %H:%M %Y");
197  const size_t res = strftime(time_buf,sizeof(time_buf),
198  format.c_str(), tm_l);
199  if(res == 0) {
200  time_buf[0] = 0;
201  }
202  } else {
203  LOG_SAVE << "localtime() returned null for time " << this->modified() << ", save " << name();
204  }
205 
206  return time_buf;
207 }
208 
210 {
211  time_t t = modified();
212  return util::format_time_summary(t);
213 }
214 
216  if (a.modified() > b.modified()) {
217  return true;
218  } else if (a.modified() < b.modified()) {
219  return false;
220  // Special funky case; for files created in the same second,
221  // a replay file sorts less than a non-replay file. Prevents
222  // a timing-dependent bug where it may look like, at the end
223  // of a scenario, the replay and the autosave for the next
224  // scenario are displayed in the wrong order.
225  } else if (a.name().find(_(" replay"))==std::string::npos && b.name().find(_(" replay"))!=std::string::npos) {
226  return true;
227  } else if (a.name().find(_(" replay"))!=std::string::npos && b.name().find(_(" replay"))==std::string::npos) {
228  return false;
229  } else {
230  return a.name() > b.name();
231  }
232 }
233 
234 static std::istream* find_save_file(const std::string &name, const std::string &alt_name, const std::vector<std::string> &suffixes) {
235  for (const std::string &suf : suffixes) {
236  std::istream *file_stream = filesystem::istream_file(filesystem::get_saves_dir() + "/" + name + suf);
237  if (file_stream->fail()) {
238  delete file_stream;
239  file_stream = filesystem::istream_file(filesystem::get_saves_dir() + "/" + alt_name + suf);
240  }
241  if (!file_stream->fail())
242  return file_stream;
243  else
244  delete file_stream;
245  }
246  LOG_SAVE << "Could not open supplied filename '" << name << "'\n";
247  throw game::load_game_failed();
248 }
249 
250 void read_save_file(const std::string& name, config& cfg, std::string* error_log)
251 {
252  std::string modified_name = name;
253  replace_space2underbar(modified_name);
254 
255  static const std::vector<std::string> suffixes = {"", ".gz", ".bz2"};
256  filesystem::scoped_istream file_stream = find_save_file(modified_name, name, suffixes);
257 
258  cfg.clear();
259  try{
260  /*
261  * Test the modified name, since it might use a .gz
262  * file even when not requested.
263  */
264  if(filesystem::is_gzip_file(modified_name)) {
265  read_gz(cfg, *file_stream);
266  } else if(filesystem::is_bzip2_file(modified_name)) {
267  read_bz2(cfg, *file_stream);
268  } else {
269  read(cfg, *file_stream);
270  }
271  } catch(const std::ios_base::failure& e) {
272  LOG_SAVE << e.what();
273  if(error_log) {
274  *error_log += e.what();
275  }
276  throw game::load_game_failed();
277  } catch(const config::error &err) {
278  LOG_SAVE << err.message;
279  if(error_log) {
280  *error_log += err.message;
281  }
282  throw game::load_game_failed();
283  }
284 
285  if(cfg.empty()) {
286  LOG_SAVE << "Could not parse file data into config\n";
287  throw game::load_game_failed();
288  }
289 }
290 
291 void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
292 {
293  const std::string auto_save = _("Auto-Save");
294  int countdown = autosavemax;
295  if (countdown == infinite_auto_saves)
296  return;
297 
298  std::vector<save_info> games = get_saves_list(nullptr, &auto_save);
299  for (std::vector<save_info>::iterator i = games.begin(); i != games.end(); ++i) {
300  if (countdown-- <= 0) {
301  LOG_SAVE << "Deleting savegame '" << i->name() << "'\n";
302  delete_game(i->name());
303  }
304  }
305 }
306 
308 {
309  std::string modified_name = name;
310  replace_space2underbar(modified_name);
311 
313  filesystem::delete_file(filesystem::get_saves_dir() + "/" + modified_name);
314 
315  save_index_manager.remove(name);
316 }
317 
318 
319 
321  : dir(d ? *d : filesystem::get_saves_dir())
322 {
323 }
324 
326 {
327  std::string name = filename;
329  time_t modified = filesystem::file_modified_time(dir + "/" + filename);
330  save_index_manager.set_modified(name, modified);
331  return save_info(name, modified);
332 }
333 
334 void extract_summary_from_config(config& cfg_save, config& cfg_summary)
335 {
336  const config &cfg_snapshot = cfg_save.child("snapshot");
337  //Servergenerated replays contain [scenario] and no [replay_start]
338  const config &cfg_replay_start = cfg_save.child("replay_start") ? cfg_save.child("replay_start") : cfg_save.child("scenario") ;
339 
340  const config &cfg_replay = cfg_save.child("replay");
341  const bool has_replay = cfg_replay && !cfg_replay.empty();
342  const bool has_snapshot = cfg_snapshot && cfg_snapshot.child("side");
343 
344  cfg_summary["replay"] = has_replay;
345  cfg_summary["snapshot"] = has_snapshot;
346 
347  cfg_summary["label"] = cfg_save["label"];
348  cfg_summary["campaign_type"] = cfg_save["campaign_type"];
349 
350  if(cfg_save.has_child("carryover_sides_start")){
351  cfg_summary["scenario"] = cfg_save.child("carryover_sides_start")["next_scenario"];
352  } else {
353  cfg_summary["scenario"] = cfg_save["scenario"];
354  }
355 
356  cfg_summary["difficulty"] = cfg_save["difficulty"];
357  cfg_summary["random_mode"] = cfg_save["random_mode"];
358 
359 
360  cfg_summary["campaign"] = cfg_save["campaign"];
361  cfg_summary["version"] = cfg_save["version"];
362  cfg_summary["corrupt"] = "";
363 
364  if(has_snapshot) {
365  cfg_summary["turn"] = cfg_snapshot["turn_at"];
366  if (cfg_snapshot["turns"] != "-1") {
367  cfg_summary["turn"] = cfg_summary["turn"].str() + "/" + cfg_snapshot["turns"].str();
368  }
369  }
370 
371  // Find the first human leader so we can display their icon in the load menu.
372 
373  /** @todo Ideally we should grab all leaders if there's more than 1 human player? */
374  std::string leader;
375  std::string leader_image;
376  std::string leader_image_tc_modifier;
377 
378  //for (const config &p : cfg_save.child_range("player"))
379  //{
380  // if (p["canrecruit"].to_bool(false))) {
381  // leader = p["save_id"];
382  // }
383  //}
384 
385  bool shrouded = false;
386 
387  //if (!leader.empty())
388  //{
389  if (const config &snapshot = *(has_snapshot ? &cfg_snapshot : &cfg_replay_start))
390  {
391  for (const config &side : snapshot.child_range("side"))
392  {
393  if (side["controller"] != team::CONTROLLER::enum_to_string(team::CONTROLLER::HUMAN)) {
394  continue;
395  }
396 
397  if (side["shroud"].to_bool()) {
398  shrouded = true;
399  }
400 
401  if (side["canrecruit"].to_bool())
402  {
403  leader = side["id"].str();
404  leader_image = side["image"].str();
405  leader_image_tc_modifier = "~RC(magenta>" + side["color"].str() + ")"; // Hardcode magenta
406  break;
407  }
408 
409  for (const config &u : side.child_range("unit"))
410  {
411  if (u["canrecruit"].to_bool()) {
412  leader = u["id"].str();
413  leader_image = u["image"].str();
414  leader_image_tc_modifier = "~RC(" + u["flag_rgb"].str() + ">" + u["side"].str() + ")";
415  break;
416  }
417  }
418 
419  // Exit on first human leader
420  break;
421  }
422  }
423  //}
424 
425  cfg_summary["leader"] = leader;
426  // We need a binary path-independent path to the leader image here
427  // so it can be displayed for campaign-specific units in the dialog
428  // even when the campaign isn't loaded yet.
429  cfg_summary["leader_image"] = filesystem::get_independent_image_path(leader_image);
430  // Append the leader image's team coloring
431  cfg_summary["leader_image"] = cfg_summary["leader_image"].str() + leader_image_tc_modifier;
432 
433  if(!shrouded) {
434  if(has_snapshot) {
435  if (!cfg_snapshot.find_child("side", "shroud", "yes") && cfg_snapshot.has_attribute("map_data")) {
436  cfg_summary["map_data"] = cfg_snapshot["map_data"].str();
437  } else {
438  ERR_SAVE << "Not saving map because there is shroud" << std::endl;
439  }
440  } else if(has_replay) {
441  if (!cfg_replay_start.find_child("side","shroud","yes") && cfg_replay_start.has_attribute("map_data")) {
442  cfg_summary["map_data"] = cfg_replay_start["map_data"];
443  } else {
444  ERR_SAVE << "Not saving map because there is shroud" << std::endl;
445  }
446  }
447  }
448 }
449 
450 } // end namespace savegame
child_itors child_range(const std::string &key)
Definition: config.cpp:613
void write_gz(std::ostream &out, configr_of const &cfg)
Definition: parser.cpp:639
void remove_old_auto_saves(const int autosavemax, const int infinite_auto_saves)
Remove autosaves that are no longer needed (according to the autosave policy in the preferences)...
Definition: save_index.cpp:291
size_t strftime(char *str, size_t count, const std::string &format, const std::tm *time)
Definition: strftime.cpp:132
void remove_attribute(const std::string &key)
Definition: config.cpp:534
void rebuild(const std::string &name)
Definition: save_index.cpp:51
void read_save_file(const std::string &name, config &cfg, std::string *error_log)
Read the complete config information out of a savefile.
Definition: save_index.cpp:250
bool delete_file(const std::string &filename)
const char * what() const
Definition: exceptions.hpp:35
void extract_summary_from_config(config &, config &)
Definition: save_index.cpp:334
bool operator()(const std::string &filename) const
Definition: save_index.cpp:152
Error used when game loading fails.
Definition: game_errors.hpp:30
void remove(const std::string &name)
Definition: save_index.cpp:73
std::vector< save_info > get_saves_list(const std::string *dir, const std::string *filter)
Get a list of available saves.
Definition: save_index.cpp:161
static lg::log_domain log_engine("engine")
std::string format_time_summary(time_t t)
void clear()
Definition: config.cpp:1055
#define d
Contains the exception interfaces used to signal completion of a scenario, campaign or turn...
-file sdl_utils.hpp
bool empty() const
Definition: config.cpp:1105
bool empty() const
Tests for an attribute that either was never set or was set to "".
Definition: config.cpp:375
std::string get_saves_dir()
GLdouble GLdouble t
Definition: glew.h:1366
Definitions for the interface to Wesnoth Markup Language (WML).
void replace_underbar2space(std::string &name)
Definition: save_index.cpp:40
const std::string dir
Definition: save_index.hpp:73
std::string format_time_summary() const
Definition: save_index.cpp:209
Variant for storing WML attributes.
Definition: config.hpp:223
void read_gz(config &cfg, std::istream &file, abstract_validator *validator)
might throw a std::ios_base::failure especially a gzip_error
Definition: parser.cpp:454
#define ERR_SAVE
Definition: save_index.cpp:35
GLdouble GLdouble GLdouble b
Definition: glew.h:6966
GLuint GLenum GLenum transform
Definition: glew.h:11418
GLuint GLuint stream
Definition: glew.h:5239
bool has_child(const std::string &key) const
Determine whether a config has a child or not.
Definition: config.cpp:651
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:82
const config & summary() const
Definition: save_index.cpp:187
GLuint64EXT * result
Definition: glew.h:10727
std::istream * istream_file(const std::string &fname, bool treat_failure_as_error=true)
void delete_game(const std::string &name)
Delete a savegame.
Definition: save_index.cpp:307
GLboolean GLboolean GLboolean GLboolean a
Definition: glew.h:7319
static std::istream * find_save_file(const std::string &name, const std::string &alt_name, const std::vector< std::string > &suffixes)
Definition: save_index.cpp:234
std::ostream * ostream_file(std::string const &fname, bool create_directory=true)
std::string format_time_local() const
Definition: save_index.cpp:191
config & add_child(const std::string &key)
Definition: config.cpp:743
void read_bz2(config &cfg, std::istream &file, abstract_validator *validator)
might throw a std::ios_base::failure especially bzip2_error
Definition: parser.cpp:460
save_index_class save_index_manager
Definition: save_index.cpp:146
std::string get_independent_image_path(const std::string &filename)
Returns an image path to filename for binary path-independent use in saved games. ...
void replace_space2underbar(std::string &name)
Definition: save_index.cpp:43
bool is_gzip_file(const std::string &filename)
Returns true if the file ends with '.gz'.
GLuint res
Definition: glew.h:9258
time_t file_modified_time(const std::string &fname)
Get the modification time of a file.
config & get(const std::string &name)
Definition: save_index.cpp:83
void get_files_in_dir(const std::string &dir, std::vector< std::string > *files, std::vector< std::string > *dirs=nullptr, file_name_option mode=FILE_NAME_ONLY, file_filter_option filter=NO_FILTER, file_reorder_option reorder=DONT_REORDER, file_tree_checksum *checksum=nullptr)
Populates 'files' with all the files and 'dirs' with all the directories in dir.
GLint GLint GLsizei GLsizei GLsizei GLint GLenum format
Definition: glew.h:1222
logger & err()
Definition: log.cpp:79
An exception object used when an IO error occurs.
Definition: filesystem.hpp:40
#define log_scope(description)
Definition: log.hpp:185
size_t i
Definition: function.cpp:1057
std::map< std::string, time_t > modified_
Definition: save_index.hpp:96
Declarations for File-IO.
void read(config &cfg, std::istream &in, abstract_validator *validator)
Definition: parser.cpp:400
bool operator()(const save_info &a, const save_info &b) const
Definition: save_index.cpp:215
static int sort(lua_State *L)
Definition: ltablib.cpp:246
#define LOG_SAVE
Definition: save_index.cpp:34
void set_modified(const std::string &name, const time_t &modified)
Definition: save_index.cpp:79
std::string replace(std::string str, const std::string &src, const std::string &dst)
Replace all instances of src in str with dst.
GLuint const GLchar * name
Definition: glew.h:1782
compression::format save_compression_format()
GLint GLint GLint GLint GLint GLint GLint GLbitfield GLenum filter
Definition: glew.h:3448
bool is_bzip2_file(const std::string &filename)
Returns true if the file ends with '.bz2'.
Filename and modification date for a file list.
Definition: save_index.hpp:29
create_save_info(const std::string *d=nullptr)
Definition: save_index.cpp:320
bool has_attribute(const std::string &key) const
Definition: config.cpp:514
const GLdouble * m
Definition: glew.h:6968
const std::string & name() const
Definition: save_index.hpp:38
const time_t & modified() const
Definition: save_index.hpp:39
config & child(const std::string &key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:658
filename_filter(const std::string &filter)
Definition: save_index.cpp:150
config & find_child(const std::string &key, const std::string &name, const std::string &value)
Returns the first child of tag key with a name attribute containing value.
Definition: config.cpp:1010
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:29
save_info operator()(const std::string &filename) const
Definition: save_index.cpp:325
int to_int(int def=0) const
Definition: config.cpp:308
#define e
std::string::const_iterator iterator
Definition: tokenizer.hpp:21
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:83
static lg::log_domain log_enginerefac("enginerefac")
std::string get_save_index_file()
A structure for comparing to save_info objects based on their modified time.
Definition: save_index.hpp:53
void write(std::ostream &out, configr_of const &cfg, unsigned int level)
Definition: parser.cpp:621
GLsizei const GLcharARB ** string
Definition: glew.h:4503
bool use_twelve_hour_clock_format()