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