The Battle for Wesnoth  1.19.15+dev
info.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2012 - 2025
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 
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 
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  return title.empty() ? make_addon_title(id) : title;
158 }
159 
161 
163 {
165 
166  std::string lang_name_short = locale_info.language();
167  std::string lang_name_long = lang_name_short;
168  if(!locale_info.country().empty()) {
169  lang_name_long += '_';
170  lang_name_long += locale_info.country();
171  }
172  if(!locale_info.variant().empty()) {
173  lang_name_long += '@';
174  lang_name_long += locale_info.variant();
175  lang_name_short += '@';
176  lang_name_short += locale_info.variant();
177  }
178 
179  auto info = info_translations.find(lang_name_long);
180  if(info != info_translations.end()) {
181  return info->second;
182  }
183 
184  info = info_translations.find(lang_name_short);
185  if(info != info_translations.end()) {
186  return info->second;
187  }
188 
190 }
191 
193 {
195  return info.valid() ? info.title : "";
196 }
197 
199 {
200  std::string title = display_title_translated();
201  return title.empty() ? display_title() : title;
202 }
203 
205 {
207  return (info.valid() && !info.description.empty()) ? info.description : description;
208 }
209 
211 {
212  std::string local_title = display_title_translated();
213  return local_title.empty() ? display_title() : local_title + " (" + display_title() + ")";
214 }
215 
216 std::string addon_info::display_icon() const
217 {
218  std::string ret = icon;
219 
220  if(!image::exists(image::locator{ret}) && !ret.empty()) {
221  ERR_AC << "add-on '" << id << "' has an icon which cannot be found: '" << ret << "'";
222  ret = "";
223  } else if(ret.find("units/") != std::string::npos && ret.find_first_of('~') == std::string::npos) {
224  // HACK: prevent magenta icons, because they look awful
225  LOG_AC << "add-on '" << id << "' uses a unit baseframe as icon without TC/RC specifications";
226  ret += "~RC(magenta>red)";
227  }
228 
229  return ret;
230 }
231 
232 std::string addon_info::display_type() const
233 {
234  switch (type) {
235  case ADDON_SP_CAMPAIGN:
236  return _("addon_type^Campaign");
237  case ADDON_SP_SCENARIO:
238  return _("addon_type^Scenario");
240  return _("addon_type^SP/MP Campaign");
241  case ADDON_MP_ERA:
242  return _("addon_type^MP era");
243  case ADDON_MP_FACTION:
244  return _("addon_type^MP faction");
245  case ADDON_MP_MAPS:
246  return _("addon_type^MP map-pack");
247  case ADDON_MP_SCENARIO:
248  return _("addon_type^MP scenario");
249  case ADDON_MP_CAMPAIGN:
250  return _("addon_type^MP campaign");
251  case ADDON_MOD:
252  return _("addon_type^Modification");
253  case ADDON_CORE:
254  return _("addon_type^Core");
255  case ADDON_MEDIA:
256  return _("addon_type^Resources");
257  case ADDON_OTHER:
258  return _("addon_type^Other");
259  default:
260  return _("addon_type^(unknown)");
261  }
262 }
263 
264 std::set<std::string> addon_info::resolve_dependencies(const addons_list& addons) const
265 {
266  std::set<std::string> deps;
267  resolve_deps_recursive(addons, id, deps);
268 
269  if(deps.find(id) != deps.end()) {
270  LOG_AC << id << " depends upon itself; breaking circular dependency";
271  deps.erase(id);
272  }
273 
274  return deps;
275 }
276 
278 {
279  dest.clear();
280 
281  /** @todo FIXME: get rid of this legacy "campaign"/"campaigns" silliness
282  */
283  const config::const_child_itors &addon_cfgs = cfg.child_range("campaign");
284  for(const config& addon_cfg : addon_cfgs) {
285  const std::string& id = addon_cfg["name"].str();
286  if(dest.find(id) != dest.end()) {
287  ERR_AC << "add-ons list has multiple entries for '" << id << "', not good; ignoring them";
288  continue;
289  }
290  dest[id].read(addon_cfg);
291  }
292 }
293 
294 std::string size_display_string(double size)
295 {
296  return size > 0.0 ? utils::si_string(size, true, _("unit_byte^B")) : "";
297 }
298 
299 std::string make_addon_title(const std::string& id)
300 {
301  std::string ret(id);
302  std::replace(ret.begin(), ret.end(), '_', ' ');
303  return ret;
304 }
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
child_itors child_range(config_key_type key)
Definition: config.cpp:268
boost::iterator_range< const_child_iterator > const_child_itors
Definition: config.hpp:282
config & add_child(config_key_type key)
Definition: config.cpp:436
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).
const config * cfg
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:199
std::string size_display_string(double size)
Get a human-readable representation of the specified byte count.
Definition: info.cpp:294
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:299
#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:277
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:57
auto parse_timestamp(long long val)
Definition: chrono.hpp:47
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition: picture.cpp:840
logger & info()
Definition: log.cpp:351
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition: gettext.cpp:572
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:232
std::map< std::string, addon_info_translation > info_translations
Definition: info.hpp:109
addon_info_translation translated_info() const
Definition: info.cpp:162
int downloads
Definition: info.hpp:87
std::string icon
Definition: info.hpp:79
std::string display_title_translated() const
Definition: info.cpp:192
std::string description_translated() const
Definition: info.cpp:204
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:216
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:198
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:210
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:264
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:184
std::string get_addon_type_string(ADDON_TYPE type)
Definition: validation.cpp:200
@ ADDON_SP_SCENARIO
Single-player scenario.
Definition: validation.hpp:106
@ ADDON_MP_SCENARIO
Multiplayer scenario.
Definition: validation.hpp:109
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
Definition: validation.hpp:108
@ ADDON_MP_FACTION
Multiplayer faction.
Definition: validation.hpp:112
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
Definition: validation.hpp:114
@ ADDON_MP_ERA
Multiplayer era.
Definition: validation.hpp:111
@ ADDON_CORE
Total Conversion Core.
Definition: validation.hpp:104
@ ADDON_SP_CAMPAIGN
Single-player campaign.
Definition: validation.hpp:105
@ ADDON_OTHER
an add-on that fits in no other category
Definition: validation.hpp:116
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
Definition: validation.hpp:107
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
Definition: validation.hpp:110
@ ADDON_MOD
Modification of the game.
Definition: validation.hpp:113