The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
manager_ui.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
3 by Iris Morelle <shadowm2006@gmail.com>
4 Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
5 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
6
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY.
13
14 See the COPYING file for more details.
15*/
16
17#include "addon/manager_ui.hpp"
18
19#include "addon/client.hpp"
20#include "addon/info.hpp"
21#include "addon/manager.hpp"
22#include "config_cache.hpp"
23#include "filesystem.hpp"
26#include "gettext.hpp"
33#include "log.hpp"
34#include "wml_exception.hpp"
35
36static lg::log_domain log_config("config");
37static lg::log_domain log_network("network");
38static lg::log_domain log_filesystem("filesystem");
39static lg::log_domain log_addons_client("addons-client");
40
41#define ERR_CFG LOG_STREAM(err, log_config)
42#define INFO_CFG LOG_STREAM(info, log_config)
43
44#define ERR_NET LOG_STREAM(err, log_network)
45
46#define ERR_FS LOG_STREAM(err, log_filesystem)
47
48#define LOG_AC LOG_STREAM(info, log_addons_client)
49
50
51namespace {
52
53std::string format_remote_disconnect_message(const network_asio::error& e)
54{
55 std::string message = _("Remote host disconnected.");
56 const std::string details = e.what();
57
58 if(details.empty()) {
59 return message;
60 }
61
62 message += "\n\n";
63 message += _("Raw network error:");
64 message += "\n";
65 message += details;
66 return message;
67}
68
69bool get_addons_list(addons_client& client, addons_list& list)
70{
71 list.clear();
72
73 config cfg;
74 client.request_addons_list(cfg, prefs::get().addon_icons());
75
76 read_addons_list(cfg, list);
77
78 return true;
79}
80
81bool addons_manager_ui(const std::string& remote_address)
82{
83 bool need_wml_cache_refresh = false;
84
85 prefs::get().set_campaign_server(remote_address);
86
87 try {
88 addons_client client(remote_address);
89 client.connect();
90
92 dlg.show();
93
94 need_wml_cache_refresh = dlg.get_need_wml_cache_refresh();
95 } catch(const config::error& e) {
96 ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"";
97 gui2::show_error_message(_("Network communication error."));
98 } catch(const network_asio::error& e) {
99 ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"";
100 gui2::show_error_message(format_remote_disconnect_message(e));
101 } catch(const filesystem::io_exception& e) {
102 ERR_FS << "filesystem::io_exception thrown while installing an addon; \"" << e.what() << "\"";
103 gui2::show_error_message(_("A problem occurred when trying to create the files necessary to install this add-on."));
104 } catch(const invalid_pbl_exception& e) {
105 ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message;
106
107 utils::string_map symbols;
108 symbols["path"] = e.path;
109 symbols["msg"] = e.message;
110
112 VGETTEXT("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
113 } catch(const wml_exception& e) {
114 e.show();
115 } catch(const addons_client::user_exit&) {
116 LOG_AC << "initial connection canceled by user";
117 } catch(const addons_client::user_disconnect&) {
118 LOG_AC << "attempt to reconnect canceled by user";
120 gui2::show_error_message(_("The add-ons server address specified is not valid."));
121 }
122
123 return need_wml_cache_refresh;
124}
125
126bool uninstall_local_addons()
127{
128 const std::string list_lead = "\n\n";
129
130 const std::vector<std::string>& addons = installed_addons();
131
132 if(addons.empty()) {
133 gui2::show_error_message(_("You have no add-ons installed."));
134 return false;
135 }
136
137 std::map<std::string, std::string> addon_titles_map;
138
139 for(const std::string& id : addons) {
140 std::string title;
141
143 // _info.cfg may have the add-on's title starting with 1.11.7,
144 // if the add-on was downloading using the revised _info.cfg writer.
145 config info_cfg;
146 get_addon_install_info(id, info_cfg);
147
148 if(!info_cfg.empty()) {
149 title = info_cfg["title"].str();
150 }
151 }
152
153 if(title.empty()) {
154 // Transform the id into a title as a last resort.
155 title = make_addon_title(id);
156 }
157
158 addon_titles_map[id] = title;
159 }
160
161 int res;
162
163 std::vector<std::string> remove_ids;
164 std::set<std::string> remove_names;
165
166 do {
167 gui2::dialogs::addon_uninstall_list dlg(addon_titles_map);
168 dlg.show();
169
170 remove_ids = dlg.selected_addons();
171 if(remove_ids.empty()) {
172 return false;
173 }
174
175 remove_names.clear();
176
177 for(const std::string& id : remove_ids) {
178 remove_names.insert(addon_titles_map[id]);
179 }
180
181 const std::string confirm_message = _n(
182 "Are you sure you want to remove the following installed add-on?",
183 "Are you sure you want to remove the following installed add-ons?",
184 remove_ids.size()) + list_lead + utils::bullet_list(remove_names);
185
186 res = gui2::show_message(
187 _("Confirm")
188 , confirm_message
190 } while (res != gui2::retval::OK);
191
192 std::set<std::string> failed_names, skipped_names, succeeded_names;
193
194 for(const std::string& id : remove_ids) {
195 const std::string& name = addon_titles_map[id];
196
198 skipped_names.insert(name);
199 } else if(remove_local_addon(id)) {
200 succeeded_names.insert(name);
201 } else {
202 failed_names.insert(name);
203 }
204 }
205
206 if(!skipped_names.empty()) {
207 const std::string dlg_msg = _n(
208 "The following add-on appears to have publishing or version control information stored locally, and will not be removed:",
209 "The following add-ons appear to have publishing or version control information stored locally, and will not be removed:",
210 skipped_names.size());
211
213 dlg_msg + list_lead + utils::bullet_list(skipped_names));
214 }
215
216 if(!failed_names.empty()) {
218 "The following add-on could not be deleted properly:",
219 "The following add-ons could not be deleted properly:",
220 failed_names.size()) + list_lead + utils::bullet_list(failed_names));
221 }
222
223 if(!succeeded_names.empty()) {
224 const std::string dlg_title =
225 _n("Add-on Deleted", "Add-ons Deleted", succeeded_names.size());
226 const std::string dlg_msg = _n(
227 "The following add-on was successfully deleted:",
228 "The following add-ons were successfully deleted:",
229 succeeded_names.size());
230
232 dlg_title,
233 dlg_msg + list_lead + utils::bullet_list(succeeded_names)
234 );
235
236 return true;
237 }
238
239 return false;
240}
241
242} // end anonymous namespace
243
245{
246 static const int addon_download = 0;
247 // NOTE: the following two values are also known by WML, so don't change them.
248 static const int addon_uninstall = 2;
249
250 std::string host_name = prefs::get().campaign_server();
251 const bool have_addons = !installed_addons().empty();
252
253 gui2::dialogs::addon_connect addon_dlg(host_name, have_addons);
254 addon_dlg.show();
255 int res = addon_dlg.get_retval();
256
257 if(res == gui2::retval::OK) {
258 res = addon_download;
259 }
260
261 switch(res) {
262 case addon_download:
263 return addons_manager_ui(host_name);
264 case addon_uninstall:
265 return uninstall_local_addons();
266 default:
267 return false;
268 }
269}
270
271bool ad_hoc_addon_fetch_session(const std::vector<std::string>& addon_ids)
272{
273 std::string remote_address = prefs::get().campaign_server();
274
275 // These exception handlers copied from addon_manager_ui fcn above.
276 try {
277
278 addons_client client(remote_address);
279 client.connect();
280
281 addons_list addons;
282
283 if(!get_addons_list(client, addons)) {
284 gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
285 return false;
286 }
287
288 bool return_value = true;
289 std::ostringstream os;
290 for(const std::string& addon_id : addon_ids) {
291 addons_list::const_iterator it = addons.find(addon_id);
292 if(it != addons.end()) {
293 const addon_info& addon = it->second;
294 const std::string addon_dir = filesystem::get_addons_dir()+"/"+addon_id;
295 const std::string info_cfg = addon_dir+"/_info.cfg";
296
297 // no _info.cfg, so either there's a _server.pbl or there's no version information available at all, so this add-on can be skipped
298 if(filesystem::file_exists(addon_dir) && !filesystem::file_exists(info_cfg)) {
299 INFO_CFG << "No _info.cfg exists for '" << addon_id << "', skipping update.\n";
300 continue;
301 }
302
303 // if _info.cfg exists, compare the local vs remote add-on versions to determine whether a download is needed
304 if(filesystem::file_exists(info_cfg)) {
306 version_info installed_addon_version(cache.get_config(info_cfg).child_or_empty("info")["version"]);
307
308 // if the installed version is outdated, download the most recent version from the add-ons server
309 if(installed_addon_version >= addon.current_version) {
310 continue;
311 }
312 }
313
314 // if the add-on exists locally and needs to be updated, or it doesn't exist and needs to be downloaded
316 return_value = return_value && (res.outcome == addons_client::install_outcome::success);
317 } else {
318 if(!return_value) {
319 os << ", ";
320 }
321 os << addon_id;
322 return_value = false;
323 }
324 }
325
326 if(!return_value) {
327 utils::string_map symbols;
328 symbols["addon_ids"] = os.str();
329 gui2::show_error_message(VGETTEXT("Could not find add-ons matching the ids $addon_ids on the add-on server.", symbols));
330 }
331
332 return return_value;
333
334 } catch(const config::error& e) {
335 ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"";
336 gui2::show_error_message(_("Network communication error."));
337 } catch(const network_asio::error& e) {
338 ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"";
339 gui2::show_error_message(format_remote_disconnect_message(e));
340 } catch(const filesystem::io_exception& e) {
341 ERR_FS << "io_exception thrown while installing an addon; \"" << e.what() << "\"";
342 gui2::show_error_message(_("A problem occurred when trying to create the files necessary to install this add-on."));
343 } catch(const invalid_pbl_exception& e) {
344 ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message;
345
346 utils::string_map symbols;
347 symbols["path"] = e.path;
348 symbols["msg"] = e.message;
349
351 VGETTEXT("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
352 } catch(const wml_exception& e) {
353 e.show();
354 } catch(const addons_client::user_exit&) {
355 LOG_AC << "initial connection canceled by user";
357 gui2::show_error_message(_("The add-ons server address specified is not valid."));
358 }
359
360 return false;
361}
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition manager.cpp:138
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns whether the specified add-on appears to be managed by a VCS or not.
Definition manager.cpp:57
void get_addon_install_info(const std::string &addon_name, config &cfg)
Gets the installation info (_info.cfg) for an add-on.
Definition manager.cpp:102
bool have_addon_pbl_info(const std::string &addon_name)
Returns whether a .pbl file is present for the specified add-on or not.
Definition manager.cpp:66
bool have_addon_install_info(const std::string &addon_name)
Returns true if there is a local installation info (_info.cfg) file for the add-on.
Definition manager.cpp:97
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition manager.cpp:186
Add-ons (campaignd) client class.
Definition client.hpp:41
install_result install_addon_with_checks(const addons_list &addons, const addon_info &addon)
Performs an add-on download and install cycle.
Definition client.cpp:725
@ success
The add-on was correctly installed.
void connect()
Tries to establish a connection to the add-ons server.
Definition client.cpp:69
bool request_addons_list(config &cfg, bool icons)
Request the add-ons list from the server.
Definition client.cpp:253
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
bool empty() const
Definition config.cpp:823
Singleton class to manage game config file caching.
static config_cache & instance()
Get reference to the singleton object.
bool get_need_wml_cache_refresh() const
Definition manager.hpp:39
std::vector< std::string > selected_addons() const
@ yes_no_buttons
Shows a yes and no button.
Definition message.hpp:81
bool show(const unsigned auto_close_time=0)
Shows the window.
int get_retval()
Definition window.hpp:394
static prefs & get()
void set_campaign_server(const std::string &host)
std::string campaign_server()
Represents version numbers.
Networked add-ons (campaignd) client interface.
const config * cfg
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
static std::string _n(const char *str1, const char *str2, int n)
Definition gettext.hpp:104
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 make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition info.cpp:299
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:28
Standard logging facilities (interface).
static lg::log_domain log_filesystem("filesystem")
#define ERR_CFG
#define INFO_CFG
#define LOG_AC
bool ad_hoc_addon_fetch_session(const std::vector< std::string > &addon_ids)
Conducts an ad-hoc add-ons server connection to download an add-on with a particular id and all it's ...
static lg::log_domain log_addons_client("addons-client")
static lg::log_domain log_network("network")
#define ERR_NET
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
#define ERR_FS
static lg::log_domain log_config("config")
bool file_exists(const std::string &name)
Returns true if a file or directory with such name already exists.
std::string get_addons_dir()
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition message.cpp:201
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition retval.hpp:35
std::string bullet_list(const T &v, std::size_t indent=4, const std::string &bullet=font::unicode_bullet)
Generates a new string containing a bullet list.
std::map< std::string, t_string > string_map
std::unordered_map< std::string, cache_item > cache
Definition sound.cpp:146
version_info current_version
Definition info.hpp:81
Contains the outcome of an add-on install operation.
Definition client.hpp:134
install_outcome outcome
Overall outcome of the operation.
Definition client.hpp:138
An exception object used when an IO error occurs.
Exception thrown when the WML parser fails to read a .pbl file.
Definition manager.hpp:45
Helper class, don't construct this directly.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define e