The Battle for Wesnoth  1.17.0-dev
info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 - 2021
3  by Iris Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project https://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 "addon/info.hpp"
17 
18 #include "addon/manager.hpp"
19 #include "config.hpp"
20 #include "font/pango/escape.hpp"
21 #include "gettext.hpp"
22 #include "picture.hpp"
23 #include "log.hpp"
25 
26 static lg::log_domain log_addons_client("addons-client");
27 #define ERR_AC LOG_STREAM(err , log_addons_client)
28 #define LOG_AC LOG_STREAM(info, log_addons_client)
29 
30 namespace {
31  void resolve_deps_recursive(const addons_list& addons, const std::string& base_id, std::set<std::string>& dest)
32  {
33  addons_list::const_iterator it = addons.find(base_id);
34  if(it == addons.end()) {
35  LOG_AC << "resolve_deps_recursive(): " << base_id << " not in add-ons list\n";
36  return;
37  }
38 
39  const std::vector<std::string>& base_deps = it->second.depends;
40 
41  if(base_deps.empty()) {
42  return;
43  }
44 
45  for(const std::string& dep : base_deps) {
46  if(base_id == dep) {
47  LOG_AC << dep << " depends upon itself; breaking circular dependency\n";
48  continue;
49  } else if(dest.find(dep) != dest.end()) {
50  LOG_AC << dep << " already in dependency tree; breaking circular dependency\n";
51  continue;
52  }
53 
54  dest.insert(dep);
55 
56  resolve_deps_recursive(addons, dep, dest);
57  }
58  }
59 }
60 
62 {
63  supported = cfg["supported"].to_bool(true);
64  title = cfg["title"].str();
65  description = cfg["description"].str();
66 }
67 
69 {
70  cfg["supported"] = supported;
71  cfg["title"] = title;
72  cfg["description"] = description;
73 }
74 
75 void addon_info::read(const config& cfg)
76 {
77  id = cfg["name"].str();
78  title = cfg["title"].str();
79  description = cfg["description"].str();
80  icon = cfg["icon"].str();
81  current_version = cfg["version"].str();
82  versions.emplace(cfg["version"].str());
83  author = cfg["author"].str();
84  size = cfg["size"];
85  downloads = cfg["downloads"];
86  uploads = cfg["uploads"];
87  type = get_addon_type(cfg["type"].str());
88 
89  for(const config& version : cfg.child_range("version")) {
90  versions.emplace(version["version"].str());
91  }
92 
93  const config::const_child_itors& locales_as_configs = cfg.child_range("translation");
94 
95  for(const config& locale : locales_as_configs) {
96  if(locale["supported"].to_bool(true))
97  locales.emplace_back(locale["language"].str());
98  info_translations.emplace(locale["language"].str(), addon_info_translation(locale));
99  }
100 
101  core = cfg["core"].str();
102  depends = utils::split(cfg["dependencies"].str());
103  tags = utils::split(cfg["tags"].str());
104  feedback_url = cfg["feedback_url"].str();
105 
106  updated = cfg["timestamp"].to_time_t();
107  created = cfg["original_timestamp"].to_time_t();
108 
109  local_only = cfg["local_only"].to_bool();
110 }
111 
112 void addon_info::write(config& cfg) const
113 {
114  cfg["id"] = id;
115  cfg["title"] = title;
116  cfg["description"] = description;
117  cfg["icon"] = icon;
118  cfg["version"] = current_version.str();
119  cfg["author"] = author;
120  cfg["size"] = size;
121  cfg["downloads"] = downloads;
122  cfg["uploads"] = uploads;
123  cfg["type"] = get_addon_type_string(type);
124 
125  for(const version_info& version : versions) {
126  config& version_cfg = cfg.add_child("version");
127  version_cfg["version"] = version.str();
128  }
129 
130  for(const auto& element : info_translations) {
131  config& locale = cfg.add_child("translation");
132  locale["language"] = element.first;
133  element.second.write(locale);
134  }
135 
136  cfg["core"] = core;
137  cfg["dependencies"] = utils::join(depends);
138  cfg["tags"] = utils::join(tags);
139  cfg["feedback_url"] = feedback_url;
140 
141  cfg["timestamp"] = updated;
142  cfg["original_timestamp"] = created;
143 }
144 
146 {
147  cfg["version"] = current_version.str();
148  cfg["uploads"] = uploads;
149  cfg["type"] = get_addon_type_string(type);
150  cfg["title"] = title;
151  cfg["dependencies"] = utils::join(depends);
152  cfg["core"] = core;
153 }
154 
155 std::string addon_info::display_title() const
156 {
157  if(title.empty()) {
159  } else {
160  return font::escape_text(title);
161  }
162 }
163 
165 
167 {
169 
170  std::string lang_name_short = locale_info.language();
171  std::string lang_name_long = lang_name_short;
172  if(!locale_info.country().empty()) {
173  lang_name_long += '_';
174  lang_name_long += locale_info.country();
175  }
176  if(!locale_info.variant().empty()) {
177  lang_name_long += '@';
178  lang_name_long += locale_info.variant();
179  lang_name_short += '@';
180  lang_name_short += locale_info.variant();
181  }
182 
183  auto info = info_translations.find(lang_name_long);
184  if(info != info_translations.end()) {
185  return info->second;
186  }
187 
188  info = info_translations.find(lang_name_short);
189  if(info != info_translations.end()) {
190  return info->second;
191  }
192 
194 }
195 
197 {
198  addon_info_translation info = translated_info();
199 
200  if(info.valid()) {
201  return info.title;
202  }
203 
204  return "";
205 }
206 
208 {
209  std::string title = display_title_translated();
210  return title.empty() ? display_title() : title;
211 }
212 
214 {
215  addon_info_translation info = translated_info();
216 
217  if(info.valid() && !info.description.empty()) {
218  return info.description;
219  }
220 
221  return description;
222 }
223 
225 {
226  std::string local_title = display_title_translated();
227  if(local_title.empty())
228  return display_title();
229  return local_title + " (" + display_title() + ")";
230 }
231 
232 std::string addon_info::display_icon() const
233 {
234  std::string ret = icon;
235 
236  if(ret.empty()) {
237  ERR_AC << "add-on '" << id << "' doesn't have an icon path set" << std::endl;
238  } else if(!image::exists(ret)) {
239  ERR_AC << "add-on '" << id << "' has an icon which cannot be found: '" << ret << "'" << std::endl;
240  } else if(ret.find("units/") != std::string::npos && ret.find_first_of('~') == std::string::npos) {
241  // HACK: prevent magenta icons, because they look awful
242  LOG_AC << "add-on '" << id << "' uses a unit baseframe as icon without TC/RC specifications\n";
243  ret += "~RC(magenta>red)";
244  }
245 
246  return ret;
247 }
248 
249 std::string addon_info::display_type() const
250 {
251  switch (type) {
252  case ADDON_SP_CAMPAIGN:
253  return _("addon_type^Campaign");
254  case ADDON_SP_SCENARIO:
255  return _("addon_type^Scenario");
257  return _("addon_type^SP/MP Campaign");
258  case ADDON_MP_ERA:
259  return _("addon_type^MP era");
260  case ADDON_MP_FACTION:
261  return _("addon_type^MP faction");
262  case ADDON_MP_MAPS:
263  return _("addon_type^MP map-pack");
264  case ADDON_MP_SCENARIO:
265  return _("addon_type^MP scenario");
266  case ADDON_MP_CAMPAIGN:
267  return _("addon_type^MP campaign");
268  case ADDON_MOD:
269  return _("addon_type^Modification");
270  case ADDON_CORE:
271  return _("addon_type^Core");
272  case ADDON_MEDIA:
273  return _("addon_type^Resources");
274  case ADDON_OTHER:
275  return _("addon_type^Other");
276  default:
277  return _("addon_type^(unknown)");
278  }
279 }
280 
281 std::set<std::string> addon_info::resolve_dependencies(const addons_list& addons) const
282 {
283  std::set<std::string> deps;
284  resolve_deps_recursive(addons, id, deps);
285 
286  if(deps.find(id) != deps.end()) {
287  LOG_AC << id << " depends upon itself; breaking circular dependency\n";
288  deps.erase(id);
289  }
290 
291  return deps;
292 }
293 
294 void read_addons_list(const config& cfg, addons_list& dest)
295 {
296  dest.clear();
297 
298  /** @todo FIXME: get rid of this legacy "campaign"/"campaigns" silliness
299  */
300  const config::const_child_itors &addon_cfgs = cfg.child_range("campaign");
301  for(const config& addon_cfg : addon_cfgs) {
302  const std::string& id = addon_cfg["name"].str();
303  if(dest.find(id) != dest.end()) {
304  ERR_AC << "add-ons list has multiple entries for '" << id << "', not good; ignoring them" << std::endl;
305  continue;
306  }
307  dest[id].read(addon_cfg);
308  }
309 }
310 
311 std::string size_display_string(double size)
312 {
313  if(size > 0.0) {
314  return utils::si_string(size, true, _("unit_byte^B"));
315  } else {
316  return "";
317  }
318 }
319 
320 std::string make_addon_title(const std::string& id)
321 {
322  std::string ret(id);
323  std::replace(ret.begin(), ret.end(), '_', ' ');
324  return ret;
325 }
void read_addons_list(const config &cfg, addons_list &dest)
Parse the specified add-ons list WML into an actual addons_list object.
Definition: info.cpp:294
Modification of the game.
Definition: validation.hpp:110
Single-player scenario.
Definition: validation.hpp:102
void write(config &cfg) const
Definition: info.cpp:112
an add-on that fits in no other category
Definition: validation.hpp:113
std::string display_title_translated() const
Definition: info.cpp:196
Total Conversion Core.
Definition: validation.hpp:100
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
#define ERR_AC
Definition: info.cpp:27
std::string display_title_translated_or_original() const
Definition: info.cpp:207
std::string display_title() const
Get a title or automatic title for display.
Definition: info.cpp:155
logger & info()
Definition: log.cpp:89
static lg::log_domain log_addons_client("addons-client")
child_itors child_range(config_key_type key)
Definition: config.cpp:344
Multiplayer faction.
Definition: validation.hpp:108
std::string title
Definition: info.hpp:36
static std::string _(const char *str)
Definition: gettext.hpp:93
Definitions for the interface to Wesnoth Markup Language (WML).
void write_minimal(config &cfg) const
Write only minimal WML used for state tracking (_info.cfg) files.
Definition: info.cpp:145
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:1011
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
#define LOG_AC
Definition: info.cpp:28
static addon_info_translation invalid
Definition: info.hpp:33
std::string si_string(double input, bool base2, const std::string &unit)
Convert into a string with an SI-postfix.
std::string display_icon() const
Get an icon path fixed for display (e.g.
Definition: info.cpp:232
std::string description
Definition: info.hpp:37
std::string display_type() const
Get an add-on type identifier for display in the user&#39;s language.
Definition: info.cpp:249
ADDON_TYPE get_addon_type(const std::string &str)
Definition: validation.cpp:180
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
Definition: validation.hpp:112
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:206
void read(const config &cfg)
Definition: info.cpp:75
Multiplayer scenario.
Definition: validation.hpp:105
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
std::string escape_text(const std::string &text)
Escapes the pango markup characters in a text.
Definition: escape.hpp:33
Multiplayer era.
Definition: validation.hpp:107
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition: info.cpp:311
std::string display_title_full() const
Definition: info.cpp:224
Multiplayer plain (no WML) map pack.
Definition: validation.hpp:106
Represents version numbers.
config & add_child(config_key_type key)
Definition: config.cpp:514
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:320
Hybrid campaign.
Definition: validation.hpp:103
void read(const config &cfg)
Definition: info.cpp:61
std::vector< std::string > split(const config_attribute_value &val)
std::set< std::string > resolve_dependencies(const addons_list &addons) const
Resolve an add-on&#39;s dependency tree in a recursive fashion.
Definition: info.cpp:281
addon_info_translation translated_info() const
Definition: info.cpp:166
Standard logging facilities (interface).
std::string get_addon_type_string(ADDON_TYPE type)
Definition: validation.cpp:196
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
void write(config &cfg) const
Definition: info.cpp:68
std::map< std::string, addon_info > addons_list
Definition: info.hpp:28
Single-player campaign.
Definition: validation.hpp:101
Multiplayer campaign.
Definition: validation.hpp:104
std::string description_translated() const
Definition: info.cpp:213
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition: gettext.cpp:528