The Battle for Wesnoth  1.19.7+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(!image::exists(image::locator{ret}) && !ret.empty()) {
237  ERR_AC << "add-on '" << id << "' has an icon which cannot be found: '" << ret << "'";
238  } else if(ret.find("units/") != std::string::npos && ret.find_first_of('~') == std::string::npos) {
239  // HACK: prevent magenta icons, because they look awful
240  LOG_AC << "add-on '" << id << "' uses a unit baseframe as icon without TC/RC specifications";
241  ret += "~RC(magenta>red)";
242  }
243 
244  return ret;
245 }
246 
247 std::string addon_info::display_type() const
248 {
249  switch (type) {
250  case ADDON_SP_CAMPAIGN:
251  return _("addon_type^Campaign");
252  case ADDON_SP_SCENARIO:
253  return _("addon_type^Scenario");
255  return _("addon_type^SP/MP Campaign");
256  case ADDON_MP_ERA:
257  return _("addon_type^MP era");
258  case ADDON_MP_FACTION:
259  return _("addon_type^MP faction");
260  case ADDON_MP_MAPS:
261  return _("addon_type^MP map-pack");
262  case ADDON_MP_SCENARIO:
263  return _("addon_type^MP scenario");
264  case ADDON_MP_CAMPAIGN:
265  return _("addon_type^MP campaign");
266  case ADDON_MOD:
267  return _("addon_type^Modification");
268  case ADDON_CORE:
269  return _("addon_type^Core");
270  case ADDON_MEDIA:
271  return _("addon_type^Resources");
272  case ADDON_OTHER:
273  return _("addon_type^Other");
274  default:
275  return _("addon_type^(unknown)");
276  }
277 }
278 
279 std::set<std::string> addon_info::resolve_dependencies(const addons_list& addons) const
280 {
281  std::set<std::string> deps;
282  resolve_deps_recursive(addons, id, deps);
283 
284  if(deps.find(id) != deps.end()) {
285  LOG_AC << id << " depends upon itself; breaking circular dependency";
286  deps.erase(id);
287  }
288 
289  return deps;
290 }
291 
292 void read_addons_list(const config& cfg, addons_list& dest)
293 {
294  dest.clear();
295 
296  /** @todo FIXME: get rid of this legacy "campaign"/"campaigns" silliness
297  */
298  const config::const_child_itors &addon_cfgs = cfg.child_range("campaign");
299  for(const config& addon_cfg : addon_cfgs) {
300  const std::string& id = addon_cfg["name"].str();
301  if(dest.find(id) != dest.end()) {
302  ERR_AC << "add-ons list has multiple entries for '" << id << "', not good; ignoring them";
303  continue;
304  }
305  dest[id].read(addon_cfg);
306  }
307 }
308 
309 std::string size_display_string(double size)
310 {
311  if(size > 0.0) {
312  return utils::si_string(size, true, _("unit_byte^B"));
313  } else {
314  return "";
315  }
316 }
317 
318 std::string make_addon_title(const std::string& id)
319 {
320  std::string ret(id);
321  std::replace(ret.begin(), ret.end(), '_', ' ');
322  return ret;
323 }
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:309
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:318
#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:292
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(std::string_view 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(std::string_view 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:247
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:279
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