The Battle for Wesnoth  1.19.0-dev
manager_ui.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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"
24 #include "formula/string_utils.hpp"
25 #include "preferences/game.hpp"
26 #include "gettext.hpp"
30 #include "gui/dialogs/message.hpp"
32 #include "gui/widgets/retval.hpp"
33 #include "log.hpp"
34 #include "wml_exception.hpp"
35 
36 static lg::log_domain log_config("config");
37 static lg::log_domain log_network("network");
38 static lg::log_domain log_filesystem("filesystem");
39 static 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 
51 namespace {
52 
53 bool get_addons_list(addons_client& client, addons_list& list)
54 {
55  list.clear();
56 
57  config cfg;
58  client.request_addons_list(cfg);
59 
60  read_addons_list(cfg, list);
61 
62  return true;
63 }
64 
65 bool addons_manager_ui(const std::string& remote_address)
66 {
67  bool need_wml_cache_refresh = false;
68 
69  preferences::set_campaign_server(remote_address);
70 
71  try {
72  addons_client client(remote_address);
73  client.connect();
74 
75  gui2::dialogs::addon_manager dlg(client);
76  dlg.show();
77 
78  need_wml_cache_refresh = dlg.get_need_wml_cache_refresh();
79  } catch(const config::error& e) {
80  ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"";
81  gui2::show_error_message(_("Network communication error."));
82  } catch(const network_asio::error& e) {
83  ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"";
84  gui2::show_error_message(_("Remote host disconnected."));
85  } catch(const filesystem::io_exception& e) {
86  ERR_FS << "filesystem::io_exception thrown while installing an addon; \"" << e.what() << "\"";
87  gui2::show_error_message(_("A problem occurred when trying to create the files necessary to install this add-on."));
88  } catch(const invalid_pbl_exception& e) {
89  ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message;
90 
91  utils::string_map symbols;
92  symbols["path"] = e.path;
93  symbols["msg"] = e.message;
94 
96  VGETTEXT("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
97  } catch(const wml_exception& e) {
98  e.show();
99  } catch(const addons_client::user_exit&) {
100  LOG_AC << "initial connection canceled by user";
101  } catch(const addons_client::user_disconnect&) {
102  LOG_AC << "attempt to reconnect canceled by user";
103  } catch(const addons_client::invalid_server_address&) {
104  gui2::show_error_message(_("The add-ons server address specified is not valid."));
105  }
106 
107  return need_wml_cache_refresh;
108 }
109 
110 bool uninstall_local_addons()
111 {
112  const std::string list_lead = "\n\n";
113 
114  const std::vector<std::string>& addons = installed_addons();
115 
116  if(addons.empty()) {
117  gui2::show_error_message(_("You have no add-ons installed."));
118  return false;
119  }
120 
121  std::map<std::string, std::string> addon_titles_map;
122 
123  for(const std::string& id : addons) {
124  std::string title;
125 
126  if(have_addon_install_info(id)) {
127  // _info.cfg may have the add-on's title starting with 1.11.7,
128  // if the add-on was downloading using the revised _info.cfg writer.
129  config info_cfg;
130  get_addon_install_info(id, info_cfg);
131 
132  if(!info_cfg.empty()) {
133  title = info_cfg["title"].str();
134  }
135  }
136 
137  if(title.empty()) {
138  // Transform the id into a title as a last resort.
139  title = make_addon_title(id);
140  }
141 
142  addon_titles_map[id] = title;
143  }
144 
145  int res;
146 
147  std::vector<std::string> remove_ids;
148  std::set<std::string> remove_names;
149 
150  do {
151  gui2::dialogs::addon_uninstall_list dlg(addon_titles_map);
152  dlg.show();
153 
154  remove_ids = dlg.selected_addons();
155  if(remove_ids.empty()) {
156  return false;
157  }
158 
159  remove_names.clear();
160 
161  for(const std::string& id : remove_ids) {
162  remove_names.insert(addon_titles_map[id]);
163  }
164 
165  const std::string confirm_message = _n(
166  "Are you sure you want to remove the following installed add-on?",
167  "Are you sure you want to remove the following installed add-ons?",
168  remove_ids.size()) + list_lead + utils::bullet_list(remove_names);
169 
170  res = gui2::show_message(
171  _("Confirm")
172  , confirm_message
174  } while (res != gui2::retval::OK);
175 
176  std::set<std::string> failed_names, skipped_names, succeeded_names;
177 
178  for(const std::string& id : remove_ids) {
179  const std::string& name = addon_titles_map[id];
180 
182  skipped_names.insert(name);
183  } else if(remove_local_addon(id)) {
184  succeeded_names.insert(name);
185  } else {
186  failed_names.insert(name);
187  }
188  }
189 
190  if(!skipped_names.empty()) {
191  const std::string dlg_msg = _n(
192  "The following add-on appears to have publishing or version control information stored locally, and will not be removed:",
193  "The following add-ons appear to have publishing or version control information stored locally, and will not be removed:",
194  skipped_names.size());
195 
197  dlg_msg + list_lead + utils::bullet_list(skipped_names));
198  }
199 
200  if(!failed_names.empty()) {
202  "The following add-on could not be deleted properly:",
203  "The following add-ons could not be deleted properly:",
204  failed_names.size()) + list_lead + utils::bullet_list(failed_names));
205  }
206 
207  if(!succeeded_names.empty()) {
208  const std::string dlg_title =
209  _n("Add-on Deleted", "Add-ons Deleted", succeeded_names.size());
210  const std::string dlg_msg = _n(
211  "The following add-on was successfully deleted:",
212  "The following add-ons were successfully deleted:",
213  succeeded_names.size());
214 
216  dlg_title,
217  dlg_msg + list_lead + utils::bullet_list(succeeded_names)
218  );
219 
220  return true;
221  }
222 
223  return false;
224 }
225 
226 } // end anonymous namespace
227 
229 {
230  static const int addon_download = 0;
231  // NOTE: the following two values are also known by WML, so don't change them.
232  static const int addon_uninstall = 2;
233 
234  std::string host_name = preferences::campaign_server();
235  const bool have_addons = !installed_addons().empty();
236 
237  gui2::dialogs::addon_connect addon_dlg(host_name, have_addons);
238  addon_dlg.show();
239  int res = addon_dlg.get_retval();
240 
241  if(res == gui2::retval::OK) {
242  res = addon_download;
243  }
244 
245  switch(res) {
246  case addon_download:
247  return addons_manager_ui(host_name);
248  case addon_uninstall:
249  return uninstall_local_addons();
250  default:
251  return false;
252  }
253 }
254 
255 bool ad_hoc_addon_fetch_session(const std::vector<std::string>& addon_ids)
256 {
257  std::string remote_address = preferences::campaign_server();
258 
259  // These exception handlers copied from addon_manager_ui fcn above.
260  try {
261 
262  addons_client client(remote_address);
263  client.connect();
264 
265  addons_list addons;
266 
267  if(!get_addons_list(client, addons)) {
268  gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
269  return false;
270  }
271 
272  bool return_value = true;
273  std::ostringstream os;
274  for(const std::string& addon_id : addon_ids) {
275  addons_list::const_iterator it = addons.find(addon_id);
276  if(it != addons.end()) {
277  const addon_info& addon = it->second;
278  const std::string addon_dir = filesystem::get_addons_dir()+"/"+addon_id;
279  const std::string info_cfg = addon_dir+"/_info.cfg";
280 
281  // 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
282  if(filesystem::file_exists(addon_dir) && !filesystem::file_exists(info_cfg)) {
283  INFO_CFG << "No _info.cfg exists for '" << addon_id << "', skipping update.\n";
284  continue;
285  }
286 
287  // if _info.cfg exists, compare the local vs remote add-on versions to determine whether a download is needed
288  if(filesystem::file_exists(info_cfg)) {
290  config info;
291  cache.get_config(info_cfg, info);
292  version_info installed_addon_version(info.child_or_empty("info")["version"]);
293 
294  // if the installed version is outdated, download the most recent version from the add-ons server
295  if(installed_addon_version >= addon.current_version) {
296  continue;
297  }
298  }
299 
300  // if the add-on exists locally and needs to be updated, or it doesn't exist and needs to be downloaded
301  addons_client::install_result res = client.install_addon_with_checks(addons, addon);
302  return_value = return_value && (res.outcome == addons_client::install_outcome::success);
303  } else {
304  if(!return_value) {
305  os << ", ";
306  }
307  os << addon_id;
308  return_value = false;
309  }
310  }
311 
312  if(!return_value) {
313  utils::string_map symbols;
314  symbols["addon_ids"] = os.str();
315  gui2::show_error_message(VGETTEXT("Could not find add-ons matching the ids $addon_ids on the add-on server.", symbols));
316  }
317 
318  return return_value;
319 
320  } catch(const config::error& e) {
321  ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"";
322  gui2::show_error_message(_("Network communication error."));
323  } catch(const network_asio::error& e) {
324  ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"";
325  gui2::show_error_message(_("Remote host disconnected."));
326  } catch(const filesystem::io_exception& e) {
327  ERR_FS << "io_exception thrown while installing an addon; \"" << e.what() << "\"";
328  gui2::show_error_message(_("A problem occurred when trying to create the files necessary to install this add-on."));
329  } catch(const invalid_pbl_exception& e) {
330  ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message;
331 
332  utils::string_map symbols;
333  symbols["path"] = e.path;
334  symbols["msg"] = e.message;
335 
337  VGETTEXT("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
338  } catch(const wml_exception& e) {
339  e.show();
340  } catch(const addons_client::user_exit&) {
341  LOG_AC << "initial connection canceled by user";
342  } catch(const addons_client::invalid_server_address&) {
343  gui2::show_error_message(_("The add-ons server address specified is not valid."));
344  }
345 
346  return false;
347 }
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition: manager.cpp:143
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:56
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:106
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:65
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:191
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:101
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:572
@ success
The add-on was correctly installed.
bool request_addons_list(config &cfg)
Request the add-ons list from the server.
Definition: client.cpp:116
void connect()
Tries to establish a connection to the add-ons server.
Definition: client.cpp:70
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
bool empty() const
Definition: config.cpp:852
Singleton class to manage game config file caching.
static config_cache & instance()
Get reference to the singleton object.
void get_config(const std::string &path, config &cfg, abstract_validator *validator=nullptr)
Gets a config object from given path.
This shows the dialog for managing addons and connecting to the addon server.
Definition: connect.hpp:34
Shows the list of addons on the server available for installation.
Definition: manager.hpp:50
bool get_need_wml_cache_refresh() const
Definition: manager.hpp:54
Dialog with a checkbox list for choosing installed add-ons to remove.
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() const
Returns the cached window exit code.
Represents version numbers.
Networked add-ons (campaignd) client interface.
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:97
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 make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:319
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).
static lg::log_domain log_filesystem("filesystem")
#define ERR_CFG
Definition: manager_ui.cpp:41
#define INFO_CFG
Definition: manager_ui.cpp:42
#define LOG_AC
Definition: manager_ui.cpp:48
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 ...
Definition: manager_ui.cpp:255
static lg::log_domain log_addons_client("addons-client")
static lg::log_domain log_network("network")
#define ERR_NET
Definition: manager_ui.cpp:44
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
Definition: manager_ui.cpp:228
#define ERR_FS
Definition: manager_ui.cpp:46
static lg::log_domain log_config("config")
static bool file_exists(const bfs::path &fpath)
Definition: filesystem.cpp:318
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:203
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:150
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
logger & info()
Definition: log.cpp:314
void set_campaign_server(const std::string &host)
Definition: game.cpp:408
std::string campaign_server()
Definition: game.cpp:399
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
version_info current_version
Definition: info.hpp:81
Contains the outcome of an add-on install operation.
Definition: client.hpp:125
install_outcome outcome
Overall outcome of the operation.
Definition: client.hpp:129
An exception object used when an IO error occurs.
Definition: filesystem.hpp:64
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