The Battle for Wesnoth  1.15.0-dev
manager_ui.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
3  2008 - 2015 by Iris Morelle <shadowm2006@gmail.com>
4  Part of the Battle for Wesnoth Project http://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/manager_ui.hpp"
17 
18 #include "addon/client.hpp"
19 #include "addon/info.hpp"
20 #include "addon/manager.hpp"
21 #include "filesystem.hpp"
22 #include "formula/string_utils.hpp"
23 #include "preferences/game.hpp"
24 #include "gettext.hpp"
28 #include "gui/dialogs/message.hpp"
30 #include "gui/widgets/retval.hpp"
31 #include "log.hpp"
32 #include "wml_exception.hpp"
33 
34 static lg::log_domain log_config("config");
35 static lg::log_domain log_network("network");
36 static lg::log_domain log_filesystem("filesystem");
37 static lg::log_domain log_addons_client("addons-client");
38 
39 #define ERR_CFG LOG_STREAM(err, log_config)
40 
41 #define ERR_NET LOG_STREAM(err, log_network)
42 
43 #define ERR_FS LOG_STREAM(err, log_filesystem)
44 
45 #define LOG_AC LOG_STREAM(info, log_addons_client)
46 
47 
48 namespace {
49 
50 bool get_addons_list(addons_client& client, addons_list& list)
51 {
52  list.clear();
53 
54  config cfg;
55  client.request_addons_list(cfg);
56 
57  if(!cfg) {
58  return false;
59  }
60 
61  read_addons_list(cfg, list);
62 
63  return true;
64 }
65 
66 bool addons_manager_ui(const std::string& remote_address)
67 {
68  bool need_wml_cache_refresh = false;
69 
70  preferences::set_campaign_server(remote_address);
71 
72  try {
73  addons_client client(remote_address);
74  client.connect();
75 
76  gui2::dialogs::addon_manager dlg(client);
77  dlg.show();
78 
79  need_wml_cache_refresh = dlg.get_need_wml_cache_refresh();
80  } catch(const config::error& e) {
81  ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"" << std::endl;
82  gui2::show_error_message(_("Network communication error."));
83  } catch(const network_asio::error& e) {
84  ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"" << std::endl;
85  gui2::show_error_message(_("Remote host disconnected."));
86  } catch(const filesystem::io_exception& e) {
87  ERR_FS << "filesystem::io_exception thrown while installing an addon; \"" << e.what() << "\"" << std::endl;
88  gui2::show_error_message(_("A problem occurred when trying to create the files necessary to install this add-on."));
89  } catch(const invalid_pbl_exception& e) {
90  ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message << std::endl;
91 
92  utils::string_map symbols;
93  symbols["path"] = e.path;
94  symbols["msg"] = e.message;
95 
97  VGETTEXT("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
98  } catch(const wml_exception& e) {
99  e.show();
100  } catch(const addons_client::user_exit&) {
101  LOG_AC << "initial connection canceled by user\n";
102  } catch(const addons_client::user_disconnect&) {
103  LOG_AC << "attempt to reconnect canceled by user\n";
104  } catch(const addons_client::invalid_server_address&) {
105  gui2::show_error_message(_("The add-ons server address specified is not valid."));
106  }
107 
108  return need_wml_cache_refresh;
109 }
110 
111 bool uninstall_local_addons()
112 {
113  const std::string list_lead = "\n\n";
114 
115  const std::vector<std::string>& addons = installed_addons();
116 
117  if(addons.empty()) {
118  gui2::show_error_message(_("You have no add-ons installed."));
119  return false;
120  }
121 
122  std::map<std::string, std::string> addon_titles_map;
123 
124  for(const std::string& id : addons) {
125  std::string title;
126 
127  if(have_addon_install_info(id)) {
128  // _info.cfg may have the add-on's title starting with 1.11.7,
129  // if the add-on was downloading using the revised _info.cfg writer.
130  config cfg;
131  get_addon_install_info(id, cfg);
132 
133  const config& info_cfg = cfg.child("info");
134 
135  if(info_cfg) {
136  title = info_cfg["title"].str();
137  }
138  }
139 
140  if(title.empty()) {
141  // Transform the id into a title as a last resort.
142  title = make_addon_title(id);
143  }
144 
145  addon_titles_map[id] = title;
146  }
147 
148  int res;
149 
150  std::vector<std::string> remove_ids;
151  std::set<std::string> remove_names;
152 
153  do {
154  gui2::dialogs::addon_uninstall_list dlg(addon_titles_map);
155  dlg.show();
156 
157  remove_ids = dlg.selected_addons();
158  if(remove_ids.empty()) {
159  return false;
160  }
161 
162  remove_names.clear();
163 
164  for(const std::string& id : remove_ids) {
165  remove_names.insert(addon_titles_map[id]);
166  }
167 
168  const std::string confirm_message = _n(
169  "Are you sure you want to remove the following installed add-on?",
170  "Are you sure you want to remove the following installed add-ons?",
171  remove_ids.size()) + list_lead + utils::bullet_list(remove_names);
172 
173  res = gui2::show_message(
174  _("Confirm")
175  , confirm_message
177  } while (res != gui2::retval::OK);
178 
179  std::set<std::string> failed_names, skipped_names, succeeded_names;
180 
181  for(const std::string& id : remove_ids) {
182  const std::string& name = addon_titles_map[id];
183 
185  skipped_names.insert(name);
186  } else if(remove_local_addon(id)) {
187  succeeded_names.insert(name);
188  } else {
189  failed_names.insert(name);
190  }
191  }
192 
193  if(!skipped_names.empty()) {
194  const std::string dlg_msg = _n(
195  "The following add-on appears to have publishing or version control information stored locally, and will not be removed:",
196  "The following add-ons appear to have publishing or version control information stored locally, and will not be removed:",
197  skipped_names.size());
198 
200  dlg_msg + list_lead + utils::bullet_list(skipped_names));
201  }
202 
203  if(!failed_names.empty()) {
205  "The following add-on could not be deleted properly:",
206  "The following add-ons could not be deleted properly:",
207  failed_names.size()) + list_lead + utils::bullet_list(failed_names));
208  }
209 
210  if(!succeeded_names.empty()) {
211  const std::string dlg_title =
212  _n("Add-on Deleted", "Add-ons Deleted", succeeded_names.size());
213  const std::string dlg_msg = _n(
214  "The following add-on was successfully deleted:",
215  "The following add-ons were successfully deleted:",
216  succeeded_names.size());
217 
219  dlg_title,
220  dlg_msg + list_lead + utils::bullet_list(succeeded_names), "", false, false);
221 
222  return true;
223  }
224 
225  return false;
226 }
227 
228 } // end anonymous namespace
229 
231 {
232  static const int addon_download = 0;
233  // NOTE: the following two values are also known by WML, so don't change them.
234  static const int addon_uninstall = 2;
235 
236  std::string host_name = preferences::campaign_server();
237  const bool have_addons = !installed_addons().empty();
238 
239  gui2::dialogs::addon_connect addon_dlg(host_name, have_addons);
240  addon_dlg.show();
241  int res = addon_dlg.get_retval();
242 
243  if(res == gui2::retval::OK) {
244  res = addon_download;
245  }
246 
247  switch(res) {
248  case addon_download:
249  return addons_manager_ui(host_name);
250  case addon_uninstall:
251  return uninstall_local_addons();
252  default:
253  return false;
254  }
255 }
256 
257 bool ad_hoc_addon_fetch_session(const std::vector<std::string>& addon_ids)
258 {
259  std::string remote_address = preferences::campaign_server();
260 
261  // These exception handlers copied from addon_manager_ui fcn above.
262  try {
263 
264  addons_client client(remote_address);
265  client.connect();
266 
267  addons_list addons;
268 
269  if(!get_addons_list(client, addons)) {
270  gui2::show_error_message(_("An error occurred while downloading the add-ons list from the server."));
271  return false;
272  }
273 
274  bool return_value = true;
275  for(const std::string & addon_id : addon_ids) {
276  addons_list::const_iterator it = addons.find(addon_id);
277  if(it != addons.end()) {
278  const addon_info& addon = it->second;
279  addons_client::install_result res = client.install_addon_with_checks(addons, addon);
280  return_value = return_value && (res.outcome == addons_client::install_outcome::success);
281  } else {
282  utils::string_map symbols;
283  symbols["addon_id"] = addon_id;
284  gui2::show_error_message(VGETTEXT("Could not find an add-on matching id $addon_id on the add-on server.", symbols));
285  return_value = false;
286  }
287  }
288 
289  return return_value;
290 
291  } catch(const config::error& e) {
292  ERR_CFG << "config::error thrown during transaction with add-on server; \""<< e.message << "\"" << std::endl;
293  gui2::show_error_message(_("Network communication error."));
294  } catch(const network_asio::error& e) {
295  ERR_NET << "network_asio::error thrown during transaction with add-on server; \""<< e.what() << "\"" << std::endl;
296  gui2::show_error_message(_("Remote host disconnected."));
297  } catch(const filesystem::io_exception& e) {
298  ERR_FS << "io_exception thrown while installing an addon; \"" << e.what() << "\"" << std::endl;
299  gui2::show_error_message(_("A problem occurred when trying to create the files necessary to install this add-on."));
300  } catch(const invalid_pbl_exception& e) {
301  ERR_CFG << "could not read .pbl file " << e.path << ": " << e.message << std::endl;
302 
303  utils::string_map symbols;
304  symbols["path"] = e.path;
305  symbols["msg"] = e.message;
306 
308  VGETTEXT("A local file with add-on publishing information could not be read.\n\nFile: $path\nError message: $msg", symbols));
309  } catch(const wml_exception& e) {
310  e.show();
311  } catch(const addons_client::user_exit&) {
312  LOG_AC << "initial connection canceled by user\n";
313  } catch(const addons_client::invalid_server_address&) {
314  gui2::show_error_message(_("The add-ons server address specified is not valid."));
315  }
316 
317  return false;
318 }
Shows the list of addons on the server.
Definition: manager.hpp:39
void read_addons_list(const config &cfg, addons_list &dest)
Definition: info.cpp:197
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
void set_campaign_server(const std::string &host)
Definition: game.cpp:414
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:423
std::map< std::string, t_string > string_map
const std::string path
Path to the faulty .pbl file.
Definition: manager.hpp:43
#define ERR_FS
Definition: manager_ui.cpp:43
#define ERR_CFG
Definition: manager_ui.cpp:39
std::string campaign_server()
Definition: game.cpp:405
Addon connect dialog.
Definition: connect.hpp:25
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
Exception thrown when the WML parser fails to read a .pbl file.
Definition: manager.hpp:30
#define LOG_AC
Definition: manager_ui.cpp:45
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:104
std::vector< std::string > selected_addons() const
bool have_addon_in_vcs_tree(const std::string &addon_name)
Returns true if the specified add-ons appear to be managed by a &#39;supported&#39; VCS.
Definition: manager.cpp:65
bool show(const unsigned auto_close_time=0)
Shows the window.
#define ERR_NET
Definition: manager_ui.cpp:41
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:99
void show() const
Shows the error in a dialog.
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:89
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:44
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:257
const std::string message
Error message to display.
Definition: manager.hpp:46
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.
static UNUSEDNOWARN std::string _n(const char *str1, const char *str2, int n)
Definition: gettext.hpp:93
std::vector< std::string > installed_addons()
Retrieves the names of all installed add-ons.
Definition: manager.cpp:147
Shows a yes and no button.
Definition: message.hpp:79
bool remove_local_addon(const std::string &addon)
Definition: manager.cpp:117
void connect()
Try to establish a connection to the add-ons server.
Definition: client.cpp:65
const char * what() const noexcept
Definition: exceptions.hpp:37
bool manage_addons()
Shows the add-ons server connection dialog, for access to the various management front-ends.
Definition: manager_ui.cpp:230
Add-ons (campaignd) client class.
Definition: client.hpp:29
Helper class, don&#39;t construct this directly.
static lg::log_domain log_addons_client("addons-client")
bool have_addon_pbl_info(const std::string &addon_name)
Returns true if there&#39;s a local .pbl file stored for the specified add-on.
Definition: manager.cpp:74
An exception object used when an IO error occurs.
Definition: filesystem.hpp:48
Declarations for File-IO.
#define VGETTEXT(msgid,...)
install_outcome outcome
Definition: client.hpp:36
std::string make_addon_title(const std::string &id)
Replaces underscores to dress up file or dirnames as add-on titles.
Definition: info.cpp:226
static lg::log_domain log_filesystem("filesystem")
Standard logging facilities (interface).
std::string message
Definition: exceptions.hpp:31
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:203
#define e
Dialog was closed with the OK button.
Definition: retval.hpp:34
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
std::map< std::string, addon_info > addons_list
Definition: info.hpp:26
install_result install_addon_with_checks(const addons_list &addons, const addon_info &addon)
Do a &#39;smart&#39; fetch of an add-on, checking to avoid overwrites for devs and resolving dependencies...
Definition: client.cpp:464
bool request_addons_list(config &cfg)
Request the add-ons list from the server.
Definition: client.cpp:79
static lg::log_domain log_config("config")