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