The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
25
26static 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
30namespace {
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
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
155std::string addon_info::display_title() const
156{
157 return title.empty() ? make_addon_title(id) : title;
158}
159
161
163{
164 const boost::locale::info& locale_info = translation::get_effective_locale_info();
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
216std::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
232std::string addon_info::display_type() const
233{
234 switch (type) {
236 return _("addon_type^Campaign");
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");
248 return _("addon_type^MP scenario");
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
264std::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
294std::string size_display_string(double size)
295{
296 return size > 0.0 ? utils::si_string(size, true, _("unit_byte^B")) : "";
297}
298
299std::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:157
config & add_child(std::string_view key)
Definition config.cpp:436
child_itors child_range(std::string_view key)
Definition config.cpp:268
boost::iterator_range< const_child_iterator > const_child_itors
Definition config.hpp:281
bool empty() const
Definition config.cpp:823
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:100
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::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition info.cpp:299
std::map< std::string, addon_info > addons_list
Definition info.hpp:28
Standard logging facilities (interface).
auto serialize_timestamp(const std::chrono::system_clock::time_point &time)
Definition chrono.hpp:58
auto parse_timestamp(long long val)
Definition chrono.hpp:48
bool exists(const image::locator &i_locator)
Returns true if the given image actually exists, without loading it.
Definition picture.cpp:855
const boost::locale::info & get_effective_locale_info()
A facet that holds general information about the effective locale.
Definition gettext.cpp:572
std::string si_string(double input, bool base2, const std::string &unit)
Convert into a string with an SI-postfix.
std::string join(const Range &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:160
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)
std::string get_addon_type_string(ADDON_TYPE type)
@ ADDON_SP_SCENARIO
Single-player scenario.
@ ADDON_MP_SCENARIO
Multiplayer scenario.
@ ADDON_MP_CAMPAIGN
Multiplayer campaign.
@ ADDON_MP_FACTION
Multiplayer faction.
@ ADDON_MEDIA
Miscellaneous content/media (unit packs, terrain packs, music packs, etc.).
@ ADDON_MP_ERA
Multiplayer era.
@ ADDON_CORE
Total Conversion Core.
@ ADDON_SP_CAMPAIGN
Single-player campaign.
@ ADDON_OTHER
an add-on that fits in no other category
@ ADDON_SP_MP_CAMPAIGN
Hybrid campaign.
@ ADDON_MP_MAPS
Multiplayer plain (no WML) map pack.
@ ADDON_MOD
Modification of the game.