The Battle for Wesnoth  1.19.5+dev
info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 - 2024
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 "config.hpp"
19 #include "font/pango/escape.hpp"
20 #include "gettext.hpp"
21 #include "picture.hpp"
22 #include "log.hpp"
23 #include "serialization/chrono.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";
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";
48  continue;
49  } else if(dest.find(dep) != dest.end()) {
50  LOG_AC << dep << " already in dependency tree; breaking circular dependency";
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"].to_int();
85  downloads = cfg["downloads"].to_int();
86  uploads = cfg["uploads"].to_int();
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 = chrono::parse_timestamp(cfg["timestamp"]);
107  created = chrono::parse_timestamp(cfg["original_timestamp"]);
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"] = chrono::serialize_timestamp(updated);
142  cfg["original_timestamp"] = chrono::serialize_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 {
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 {
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";
238  } else if(!image::exists(image::locator{ret})) {
239  ERR_AC << "add-on '" << id << "' has an icon which cannot be found: '" << ret << "'";
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";
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";
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";
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 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:172
child_itors child_range(config_key_type key)
Definition: config.cpp:272
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:296
config & add_child(config_key_type key)
Definition: config.cpp:440
Generic locator abstracting the location of an image.
Definition: picture.hpp:59
Represents version numbers.
std::string str() const
Serializes the version number into string form.
Definitions for the interface to Wesnoth Markup Language (WML).
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:198
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition: info.cpp:311
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
#define LOG_AC
Definition: info.cpp:28
static lg::log_domain log_addons_client("addons-client")
#define ERR_AC
Definition: info.cpp:27
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
std::map< std::string, addon_info > addons_list
Definition: info.hpp:27
Standard logging facilities (interface).
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition: chrono.hpp:56
auto parse_timestamp(long long val)
Definition: chrono.hpp:46
std::string escape_text(const std::string &text)
Escapes the pango markup characters in a text.
Definition: escape.hpp:33
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:818
logger & info()
Definition: log.cpp:319
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition: gettext.cpp:576
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:85
std::string si_string(double input, bool base2, const std::string &unit)
Convert into a string with an SI-postfix.
std::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::vector< std::string > split(const config_attribute_value &val)
std::string title
Definition: info.hpp:35
std::string description
Definition: info.hpp:36
static addon_info_translation invalid
Definition: info.hpp:32
void read(const config &cfg)
Definition: info.cpp:61
void write(config &cfg) const
Definition: info.cpp:68
std::vector< std::string > tags
Definition: info.hpp:92
void read(const config &cfg)
Definition: info.cpp:75
version_info current_version
Definition: info.hpp:81
int uploads
Definition: info.hpp:88
std::string display_type() const
Get an add-on type identifier for display in the user's language.
Definition: info.cpp:249
std::map< std::string, addon_info_translation > info_translations
Definition: info.hpp:109
addon_info_translation translated_info() const
Definition: info.cpp:166
int downloads
Definition: info.hpp:87
std::string icon
Definition: info.hpp:79
std::string display_title_translated() const
Definition: info.cpp:196
std::string description_translated() const
Definition: info.cpp:213
void write(config &cfg) const
Definition: info.cpp:112
std::string display_icon() const
Get an icon path fixed for display (e.g.
Definition: info.cpp:232
std::chrono::system_clock::time_point updated
Definition: info.hpp:102
bool local_only
Definition: info.hpp:107
std::string display_title() const
Get a title or automatic title for display.
Definition: info.cpp:155
std::string title
Definition: info.hpp:76
std::string display_title_translated_or_original() const
Definition: info.cpp:207
std::chrono::system_clock::time_point created
Definition: info.hpp:103
std::vector< std::string > depends
Definition: info.hpp:97
std::string feedback_url
Definition: info.hpp:100
std::string author
Definition: info.hpp:84
std::string core
Definition: info.hpp:95
std::string description
Definition: info.hpp:77
std::string display_title_full() const
Definition: info.cpp:224
std::set< std::string > resolve_dependencies(const addons_list &addons) const
Resolve an add-on's dependency tree in a recursive fashion.
Definition: info.cpp:281
std::set< version_info, std::greater< version_info > > versions
Definition: info.hpp:82
int size
Definition: info.hpp:86
std::vector< std::string > locales
Definition: info.hpp:93
std::string id
Definition: info.hpp:75
ADDON_TYPE type
Definition: info.hpp:90
void write_minimal(config &cfg) const
Write only minimal WML used for state tracking (_info.cfg) files.
Definition: info.cpp:145
ADDON_TYPE get_addon_type(const std::string &str)
Definition: validation.cpp:180
std::string get_addon_type_string(ADDON_TYPE type)
Definition: validation.cpp:196
@ ADDON_SP_SCENARIO
Single-player scenario.
Definition: validation.hpp:105
@ ADDON_MP_SCENARIO
Multiplayer scenario.
Definition: validation.hpp:108
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
Definition: validation.hpp:107
@ ADDON_MP_FACTION
Multiplayer faction.
Definition: validation.hpp:111
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
Definition: validation.hpp:113
@ ADDON_MP_ERA
Multiplayer era.
Definition: validation.hpp:110
@ ADDON_CORE
Total Conversion Core.
Definition: validation.hpp:103
@ ADDON_SP_CAMPAIGN
Single-player campaign.
Definition: validation.hpp:104
@ ADDON_OTHER
an add-on that fits in no other category
Definition: validation.hpp:115
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
Definition: validation.hpp:106
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
Definition: validation.hpp:109
@ ADDON_MOD
Modification of the game.
Definition: validation.hpp:112