The Battle for Wesnoth  1.19.15+dev
client.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/info.hpp"
18 #include "addon/manager.hpp"
19 #include "addon/state.hpp"
20 #include "addon/validation.hpp"
21 #include "cursor.hpp"
22 #include "font/pango/escape.hpp"
23 #include "formula/string_utils.hpp"
24 #include "game_config.hpp"
25 #include "gettext.hpp"
29 #include "gui/dialogs/message.hpp"
30 #include "gui/widgets/retval.hpp"
31 #include "log.hpp"
36 
37 #include <stdexcept>
38 
39 #include "addon/client.hpp"
40 
41 static lg::log_domain log_addons_client("addons-client");
42 #define ERR_ADDONS LOG_STREAM(err , log_addons_client)
43 #define WRN_ADDONS LOG_STREAM(warn, log_addons_client)
44 #define LOG_ADDONS LOG_STREAM(info, log_addons_client)
45 #define DBG_ADDONS LOG_STREAM(debug, log_addons_client)
46 
48 
49 addons_client::addons_client(const std::string& address)
50  : addr_(address)
51  , host_()
52  , port_()
53  , conn_(nullptr)
54  , last_error_()
55  , last_error_data_()
56  , server_id_()
57  , server_version_()
58  , server_capabilities_()
59  , server_url_()
60  , license_notice_()
61 {
62  try {
63  std::tie(host_, port_) = parse_network_address(addr_, std::to_string(default_campaignd_port));
64  } catch(const std::runtime_error&) {
65  throw invalid_server_address();
66  }
67 }
68 
70 {
71  LOG_ADDONS << "connecting to server " << host_ << " on port " << port_;
72 
73  utils::string_map i18n_symbols;
74  i18n_symbols["server_address"] = addr_;
75 
77 
78  const auto& msg = VGETTEXT("Connecting to $server_address|...", i18n_symbols);
79 
81 
82  config response_buf;
83 
84  send_simple_request("server_id", response_buf);
86 
87  if(!is_error_response(response_buf)) {
88  if(auto info = response_buf.optional_child("server_id")) {
89  server_id_ = info["id"].str();
90  server_version_ = info["version"].str();
91 
92  for(const auto& cap : utils::split(info["cap"].str())) {
93  server_capabilities_.insert(cap);
94  }
95 
96  server_url_ = info["url"].str();
97  license_notice_ = info["license_notice"].str();
98  }
99  } else {
101  }
102 
103  if(server_version_.empty()) {
104  // An educated guess
105  server_capabilities_ = { "auth:legacy" };
106  }
107 
108  const std::string version_desc = server_version_.empty() ? "<1.15.7 or earlier>" : server_version_;
109  const std::string id_desc = server_id_.empty() ? "<id not provided>" : server_id_;
110 
111  LOG_ADDONS << "Server " << id_desc << " version " << version_desc
112  << " supports: " << utils::join(server_capabilities_, " ");
113 }
114 
115 std::map<std::string, int> addons_client::get_addon_count_by_type()
116 {
117  std::map<std::string, int> counts;
118 
119  config response;
120  config request;
121  request.add_child("addon_count_by_type");
122 
123  send_request(request, response);
124  wait_for_transfer_done(_("Requesting add-on counts by type..."));
125 
126  for(const auto& attr : response.attribute_range()) {
127  counts[attr.first] = attr.second.to_int();
128  }
129 
130  if(is_error_response(response)) {
131  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
132  return counts;
133  }
134 
135  return counts;
136 }
137 
139 {
140  config response;
141  config request;
142  config& child = request.add_child("addon_downloads_by_version");
143  child["addon"] = addon;
144 
145  send_request(request, response);
146  wait_for_transfer_done(_("Requesting add-on downloads by version..."));
147 
148  if(is_error_response(response)) {
149  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
150  config dummy;
151  return dummy;
152  }
153 
154  return response;
155 }
156 
158 {
159  config response;
160  config request;
161  request.add_child("forum_auth_usage");
162 
163  send_request(request, response);
164  wait_for_transfer_done(_("Requesting forum_auth usage..."));
165 
166  if(is_error_response(response)) {
167  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
168  config dummy;
169  return dummy;
170  }
171 
172  return response;
173 }
174 
176 {
177  config response;
178  config request;
179  request.add_child("admins_list");
180 
181  send_request(request, response);
182  wait_for_transfer_done(_("Requesting list of admins..."));
183 
184  if(is_error_response(response)) {
185  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
186  config dummy;
187  return dummy;
188  }
189 
190  return response;
191 }
192 
193 config addons_client::get_hidden_addons(const std::string& username, const std::string& passphrase)
194 {
195  config response;
196  config request;
197  config& child = request.add_child("list_hidden");
198  child["username"] = username;
199  child["passphrase"] = passphrase;
200 
201  send_request(request, response);
202  wait_for_transfer_done(_("Getting list of hidden add-ons..."));
203 
204  if(is_error_response(response)) {
205  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
206  config dummy;
207  return dummy;
208  }
209 
210  return response;
211 }
212 
213 bool addons_client::hide_addon(const std::string& addon, const std::string& username, const std::string& passphrase)
214 {
215  config response;
216  config request;
217  config& child = request.add_child("hide_addon");
218  child["addon"] = addon;
219  child["username"] = username;
220  child["passphrase"] = passphrase;
221 
222  send_request(request, response);
223  wait_for_transfer_done(_("Hiding add-on..."));
224 
225  if(is_error_response(response)) {
226  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
227  return false;
228  }
229 
230  return true;
231 }
232 
233 bool addons_client::unhide_addon(const std::string& addon, const std::string& username, const std::string& passphrase)
234 {
235  config response;
236  config request;
237  config& child = request.add_child("unhide_addon");
238  child["addon"] = addon;
239  child["username"] = username;
240  child["passphrase"] = passphrase;
241 
242  send_request(request, response);
243  wait_for_transfer_done(_("Unhiding add-on..."));
244 
245  if(is_error_response(response)) {
246  gui2::show_error_message(_("The server responded with an error:") + "\n" + get_last_server_error());
247  return false;
248  }
249 
250  return true;
251 }
252 
254 {
255  cfg.clear();
256 
257  config request;
258  config& req_child = request.add_child("request_campaign_list");
259  req_child["send_icons"] = icons;
260 
261  config response_buf;
262 
263  /** @todo FIXME: get rid of this legacy "campaign"/"campaigns" silliness
264  */
265 
266  send_request(request, response_buf);
267  wait_for_transfer_done(_("Downloading list of add-ons..."));
268 
269  std::swap(cfg, response_buf.mandatory_child("campaigns"));
270 
271  return !is_error_response(response_buf);
272 }
273 
275 {
276  if(!license_notice_.empty()) {
277  // Server identification supported, we already know the terms so this
278  // operation always succeeds without going through the server.
279  terms = license_notice_;
280  return true;
281  }
282 
283  terms.clear();
284 
285  config response_buf;
286 
287  send_simple_request("request_terms", response_buf);
288  wait_for_transfer_done(_("Requesting distribution terms..."));
289 
290  if(auto msg_cfg = response_buf.optional_child("message")) {
291  terms = msg_cfg["message"].str();
292  }
293 
294  return !is_error_response(response_buf);
295 }
296 
297 bool addons_client::upload_addon(const std::string& id, std::string& response_message, config& cfg, bool local_only)
298 {
299  LOG_ADDONS << "preparing to upload " << id;
300 
301  response_message.clear();
302 
303  utils::string_map i18n_symbols;
304  i18n_symbols["addon_title"] = font::escape_text(cfg["title"].str());
305  if(i18n_symbols["addon_title"].empty()) {
306  i18n_symbols["addon_title"] = font::escape_text(make_addon_title(id));
307  }
308 
309  if(!addon_name_legal(id)){
310  i18n_symbols["addon_id"] = font::escape_text(id);
311  last_error_ =
312  VGETTEXT("The add-on <i>$addon_title</i> has an invalid id '$addon_id' "
313  "and cannot be published.", i18n_symbols);
314  return false;
315  }
316 
317  cfg["name"] = id;
318 
319  config addon_data;
320  try {
321  archive_addon(id, addon_data);
322  } catch(const utf8::invalid_utf8_exception&){
323  last_error_ =
324  VGETTEXT("The add-on <i>$addon_title</i> has a file or directory "
325  "containing invalid characters and cannot be published.", i18n_symbols);
326  return false;
327  }
328 
329  std::vector<std::string> badnames;
330  if(!check_names_legal(addon_data, &badnames)){
331  last_error_ =
332  VGETTEXT("The add-on <i>$addon_title</i> has an invalid file or directory "
333  "name and cannot be published. "
334 
335  "File or directory names may not contain '..' or end with '.' or be longer than 255 characters. "
336  "It also may not contain whitespace, control characters, or any of the following characters:\n\n&quot; * / : &lt; &gt; ? \\ | ~"
337  , i18n_symbols);
339  return false;
340  }
341  if(!check_case_insensitive_duplicates(addon_data, &badnames)){
342  last_error_ =
343  VGETTEXT("The add-on <i>$addon_title</i> contains files or directories with case conflicts. "
344  "File or directory names may not be differently-cased versions of the same string.", i18n_symbols);
346  return false;
347  }
348 
349  if(cfg["forum_auth"].to_bool() && !conn_->using_tls() && !game_config::allow_insecure) {
350  last_error_ = VGETTEXT("The connection to the remote server is not secure. The add-on <i>$addon_title</i> cannot be uploaded.", i18n_symbols);
351  return false;
352  }
353 
354  if(addon_icon_too_large(cfg["icon"].str())) {
355  last_error_ = VGETTEXT("The file size for the icon for the add-on <i>$addon_title</i> is too large.", i18n_symbols);
356  return false;
357  }
358 
359  if(!local_only) {
360  // Try to make an upload pack if it's avaible on the server
361  config hashlist, hash_request;
362  config& request_body = hash_request.add_child("request_campaign_hash");
363  // We're requesting the latest version of an addon, so we may not specify it
364  // #TODO: Make a selection of the base version for the update ?
365  request_body["name"] = cfg["name"];
366  // request_body["from"] = ???
367  send_request(hash_request, hashlist);
368  wait_for_transfer_done(_("Requesting file index..."));
369 
370  // A silent error check
371  if(!hashlist.has_child("error")) {
372  if(!contains_hashlist(addon_data, hashlist) || !contains_hashlist(hashlist, addon_data)) {
373  LOG_ADDONS << "making an update pack for the add-on " << id;
374  config updatepack;
375  // The client shouldn't send the pack if the server is old due to the previous check,
376  // so the server should handle the new format in the `upload` request
377  make_updatepack(updatepack, hashlist, addon_data);
378 
379  config request_buf, response_buf;
380  request_buf.add_child("upload", cfg).append(std::move(updatepack));
381  // #TODO: Make a selection of the base version for the update ? ,
382  // For now, if it's unspecified we'll use the latest avaible before the upload version
383  send_request(request_buf, response_buf);
384  wait_for_transfer_done(VGETTEXT("Sending an update pack for the add-on <i>$addon_title</i>...", i18n_symbols
386 
387  if(auto message_cfg = response_buf.optional_child("message")) {
388  response_message = message_cfg["message"].str();
389  LOG_ADDONS << "server response: " << response_message;
390  }
391 
392  if(!is_error_response(response_buf))
393  return true;
394  }
395  }
396  }
397  // If there is an error including an unrecognised request for old servers or no hash data for new uploads we'll just send a full pack
398 
399  config request_buf, response_buf;
400  request_buf.add_child("upload", cfg).add_child("data", std::move(addon_data));
401 
402  LOG_ADDONS << "sending " << id;
403 
404  send_request(request_buf, response_buf);
405  wait_for_transfer_done(VGETTEXT("Sending add-on <i>$addon_title</i>...", i18n_symbols
407 
408  if(auto message_cfg = response_buf.optional_child("message")) {
409  response_message = message_cfg["message"].str();
410  LOG_ADDONS << "server response: " << response_message;
411  }
412 
413  return !is_error_response(response_buf);
414 
415 }
416 
417 bool addons_client::delete_remote_addon(const std::string& id, std::string& response_message, const std::set<std::string>& admin_set)
418 {
419  response_message.clear();
420 
421  config cfg;
422  if(admin_set.empty()) {
423  // No point in validating when we're deleting it.
424  cfg = get_addon_pbl_info(id, false);
425  } else {
426  cfg["primary_authors"] = utils::join(admin_set);
427  }
428 
429  utils::string_map i18n_symbols;
430  i18n_symbols["addon_title"] = font::escape_text(cfg["title"].str());
431  if(i18n_symbols["addon_title"].empty()) {
432  i18n_symbols["addon_title"] = font::escape_text(make_addon_title(id));
433  }
434 
435  config request_buf, response_buf;
436  config& request_body = request_buf.add_child("delete");
437 
438  // if the passphrase isn't provided from the _server.pbl, try to pre-populate it from the preferences before prompting for it
439  if(cfg["passphrase"].empty()) {
440  cfg["passphrase"] = prefs::get().password(prefs::get().campaign_server(), cfg["author"]);
441  if(!gui2::dialogs::addon_auth::execute(cfg)) {
442  config dummy;
443  config& error = dummy.add_child("error");
444  error["message"] = "Password not provided.";
445  return !is_error_response(dummy);
446  } else {
447  prefs::get().set_password(prefs::get().campaign_server(), cfg["author"], cfg["passphrase"]);
448  }
449  }
450 
451  request_body["admin"] = admin_set.size() > 0;
452  request_body["name"] = id;
453  request_body["passphrase"] = cfg["passphrase"];
454  // needed in case of forum_auth authentication since the author stored on disk on the server is not necessarily the current primary author
455  request_body["uploader"] = cfg["uploader"];
456 
457  LOG_ADDONS << "requesting server to delete " << id;
458 
459  send_request(request_buf, response_buf);
460  wait_for_transfer_done(VGETTEXT("Removing add-on <i>$addon_title</i> from the server...", i18n_symbols));
461 
462  if(auto message_cfg = response_buf.optional_child("message")) {
463  response_message = message_cfg["message"].str();
464  LOG_ADDONS << "server response: " << response_message;
465  }
466 
467  return !is_error_response(response_buf);
468 }
469 
470 bool addons_client::download_addon(config& archive_cfg, const std::string& id, const std::string& title, const version_info& version, bool increase_downloads)
471 {
472  archive_cfg.clear();
473 
474  config request_buf;
475  config& request_body = request_buf.add_child("request_campaign");
476 
477  request_body["name"] = id;
478  request_body["increase_downloads"] = increase_downloads;
479  request_body["version"] = version.str();
480  request_body["from_version"] = get_addon_version_info(id);
481 
482  utils::string_map i18n_symbols;
483  i18n_symbols["addon_title"] = font::escape_text(title);
484 
485  LOG_ADDONS << "downloading " << id;
486 
487  send_request(request_buf, archive_cfg);
488  wait_for_transfer_done(VGETTEXT("Downloading add-on <i>$addon_title</i>...", i18n_symbols));
489 
490  return !is_error_response(archive_cfg);
491 }
492 
494 {
495  const cursor::setter cursor_setter(cursor::WAIT);
496 
497  utils::string_map i18n_symbols;
498  i18n_symbols["addon_title"] = font::escape_text(info.title);
499 
500  auto progress_dlg = gui2::dialogs::file_progress::display(_("Add-ons Manager"), VGETTEXT("Installing add-on <i>$addon_title</i>...", i18n_symbols));
501  auto progress_cb = [&progress_dlg](unsigned value) {
502  progress_dlg->update_progress(value);
503  };
504 
505  if(archive_cfg.has_child("removelist") || archive_cfg.has_child("addlist")) {
506  LOG_ADDONS << "Received an updatepack for the addon '" << info.id << "'";
507 
508  // A consistency check
509  for(const auto [key, cfg] : archive_cfg.all_children_view()) {
510  if(key == "removelist" || key == "addlist") {
511  if(!check_names_legal(cfg)) {
512  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has an invalid file or directory "
513  "name and cannot be installed.", i18n_symbols));
514  return false;
515  }
517  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has file or directory names "
518  "with case conflicts. This may cause problems.", i18n_symbols));
519  }
520  }
521  }
522 
523  for(const auto [key, cfg] : archive_cfg.all_children_view()) {
524  if(key == "removelist") {
525  purge_addon(cfg);
526  } else if(key == "addlist") {
527  unarchive_addon(cfg, progress_cb);
528  }
529  }
530 
531  LOG_ADDONS << "Update completed.";
532 
533  //#TODO: hash verification ???
534  } else {
535  LOG_ADDONS << "Received a full pack for the addon '" << info.id << "'";
536 
537  if(!check_names_legal(archive_cfg)) {
538  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has an invalid file or directory "
539  "name and cannot be installed.", i18n_symbols));
540  return false;
541  }
542  if(!check_case_insensitive_duplicates(archive_cfg)) {
543  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has file or directory names "
544  "with case conflicts. This may cause problems.", i18n_symbols));
545  }
546 
547  LOG_ADDONS << "unpacking " << info.id;
548 
549  // Remove any previously installed versions
550  if(!remove_local_addon(info.id)) {
551  WRN_ADDONS << "failed to uninstall previous version of " << info.id << "; the add-on may not work properly!";
552  }
553 
554  unarchive_addon(archive_cfg, progress_cb);
555  LOG_ADDONS << "unpacking finished";
556  }
557 
558  config info_cfg;
559  info.write_minimal(info_cfg);
560  write_addon_install_info(info.id, info_cfg);
561 
562  return true;
563 }
564 
566 {
567  config archive;
568 
569  if(!(
570  download_addon(archive, addon.id, addon.display_title_full(), addon.current_version, !is_addon_installed(addon.id)) &&
571  install_addon(archive, addon)
572  ))
573  {
574  const std::string& server_error = get_last_server_error();
575  if(!server_error.empty()) {
577  _("The server responded with an error:") + "\n" + server_error);
578  }
579  return false;
580  } else {
581  return true;
582  }
583 }
584 
586 {
587  install_result result;
589  result.wml_changed = false;
590 
591  auto cursor_setter = std::make_unique<cursor::setter>(cursor::WAIT);
592 
593  // TODO: We don't currently check for the need to upgrade. I'll probably
594  // work on that when implementing dependency tiers later.
595 
596  const std::set<std::string>& deps = addon.resolve_dependencies(addons);
597 
598  std::vector<std::string> missing_deps;
599  std::vector<std::string> broken_deps;
600  // if two add-ons both have the same dependency and are being downloaded in a batch (such as via the adhoc connection)
601  // then the version cache will not be updated after the first is downloaded
602  // which will result in it being treated as version 0.0.0, which is then interpreted as being "upgradeable"
603  // which then causes the user to be prompted to download the same dependency multiple times
604  version_info unknown_version(0, 0, 0);
605 
606  for(const std::string& dep : deps) {
607  try {
609 
610  // ADDON_NONE means not installed.
611  if(info.state == ADDON_NONE) {
612  missing_deps.push_back(dep);
613  } else if(info.state == ADDON_INSTALLED_UPGRADABLE && info.installed_version != unknown_version) {
614  // Tight now, we don't need to distinguish the lists of missing
615  // and outdated addons, so just add them to missing.
616  missing_deps.push_back(dep);
617  }
618  } catch(const std::out_of_range&) {
619  // Dependency wasn't found on server, check locally directly.
620  if(!is_addon_installed(dep)) {
621  broken_deps.push_back(dep);
622  }
623  }
624  }
625 
626  cursor_setter.reset();
627 
628  if(!broken_deps.empty()) {
629  std::string broken_deps_report;
630 
631  broken_deps_report = _n(
632  "The selected add-on has the following dependency, which is not currently installed or available from the server. Do you wish to continue?",
633  "The selected add-on has the following dependencies, which are not currently installed or available from the server. Do you wish to continue?",
634  broken_deps.size());
635  broken_deps_report += "\n";
636 
637  for(const std::string& broken_dep_id : broken_deps) {
638  broken_deps_report += "\n " + font::unicode_bullet + " " + make_addon_title(broken_dep_id);
639  }
640 
641  if(gui2::show_message(_("Broken Dependencies"), broken_deps_report, gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
643  return result; // canceled by user
644  }
645  }
646 
647  if(missing_deps.empty()) {
648  // No dependencies to install, carry on.
649  return result;
650  }
651 
652  {
653  addons_list options;
654  for(const std::string& dep : missing_deps) {
655  options[dep] = addons.at(dep);
656  }
657 
658  if(!gui2::dialogs::install_dependencies::execute(options)) {
659  return result; // the user has chosen to continue without installing anything.
660  }
661  }
662 
663  //
664  // Install dependencies now.
665  //
666 
667  std::vector<std::string> failed_titles;
668 
669  for(const std::string& dep : missing_deps) {
670  const addon_info& missing_addon = addons.at(dep);
671 
672  if(!try_fetch_addon(missing_addon)) {
673  failed_titles.push_back(missing_addon.title);
674  } else {
675  result.wml_changed = true;
676  }
677  }
678 
679  if(!failed_titles.empty()) {
680  const std::string& failed_deps_report = _n(
681  "The following dependency could not be installed. Do you still wish to continue?",
682  "The following dependencies could not be installed. Do you still wish to continue?",
683  failed_titles.size()) + std::string("\n\n") + utils::bullet_list(failed_titles);
684 
685  result.outcome = gui2::show_message(_("Dependencies Installation Failed"), failed_deps_report, gui2::dialogs::message::yes_no_buttons) == gui2::retval::OK ? install_outcome::success : install_outcome::abort; // If the user cancels, return abort. Otherwise, return success, since the user chose to ignore the failure.
686  return result;
687  }
688 
689  return result;
690 }
691 
693 {
694  const std::string& addon_id = addon.id;
695 
696  const bool pbl = have_addon_pbl_info(addon_id);
697  const bool vcs = have_addon_in_vcs_tree(addon_id);
698 
699  if(!pbl && !vcs) {
700  return true;
701  }
702 
703  utils::string_map symbols;
704  symbols["addon"] = font::escape_text(addon.title);
705  std::string text;
706  std::vector<std::string> extra_items;
707 
708  text = VGETTEXT("The add-on '$addon|' is already installed and contains additional information that will be permanently lost if you continue:", symbols);
709  text += "\n\n";
710 
711  if(pbl) {
712  extra_items.push_back(_("Publishing information file (.pbl)"));
713  }
714 
715  if(vcs) {
716  extra_items.push_back(_("Version control system (VCS) information"));
717  }
718 
719  text += utils::bullet_list(extra_items) + "\n\n";
720  text += _("Do you really wish to continue?");
721 
723 }
724 
726 {
727  if(!(do_check_before_overwriting_addon(addon))) {
728  // Just do nothing and leave.
729  install_result result;
731  result.wml_changed = false;
732 
733  return result;
734  }
735 
736  // Resolve any dependencies
737  install_result res = do_resolve_addon_dependencies(addons, addon);
738  if(res.outcome != install_outcome::success) { // this function only returns SUCCESS and ABORT as outcomes
739  return res; // user aborted
740  }
741 
742  if(!try_fetch_addon(addon)) {
744  return res; //wml_changed should have whatever value was obtained in resolving dependencies
745  } else {
746  res.wml_changed = true;
747  return res; //we successfully installed something, so now the wml was definitely changed
748  }
749 }
750 
751 bool addons_client::is_error_response(const config& response_cfg)
752 {
753  if(auto error = response_cfg.optional_child("error")) {
754  if(error->has_attribute("status_code")) {
755  const auto& status_msg = translated_addon_check_status(error["status_code"].to_unsigned());
756  last_error_ = font::escape_text(status_msg);
757  } else {
758  last_error_ = font::escape_text(error["message"].str());
759  }
760  last_error_data_ = font::escape_text(error["extra_data"].str());
761  ERR_ADDONS << "server error: " << *error;
762  return true;
763  } else {
764  last_error_.clear();
765  last_error_data_.clear();
766  return false;
767  }
768 }
769 
771 {
772  last_error_.clear();
773  last_error_data_.clear();
774 }
775 
777 {
778  server_id_.clear();
779  server_version_.clear();
780  server_capabilities_.clear();
781  server_url_.clear();
782  license_notice_.clear();
783 }
784 
786 {
787  assert(conn_ != nullptr);
788  if(conn_ == nullptr) {
789  ERR_ADDONS << "not connected to server";
790  throw not_connected_to_server();
791  }
792 }
793 
794 void addons_client::send_request(const config& request, config& response)
795 {
796  check_connected();
797 
798  response.clear();
799  conn_->transfer(request, response);
800 }
801 
802 void addons_client::send_simple_request(const std::string& request_string, config& response)
803 {
804  config request;
805  request.add_child(request_string);
806  send_request(request, response);
807 }
808 struct read_addon_connection_data : public network_transmission::connection_data
809 {
811  : conn_(conn), client_(client) {}
812  std::size_t total() override { return conn_.bytes_to_read(); }
813  virtual std::size_t current() override { return conn_.bytes_read(); }
814  virtual bool finished() override { return conn_.done(); }
815  virtual void cancel() override { client_.connect(); }
816  virtual void poll() override { conn_.poll(); }
819 };
820 struct connect_connection_data : public network_transmission::connection_data
821 {
823  : conn_(conn), client_(client) {}
824  std::size_t total() override { return conn_.bytes_to_read(); }
825  std::size_t current() override { return conn_.bytes_read(); }
826  bool finished() override { return conn_.done(); }
827  void cancel() override { client_.disconnect(); }
828  void poll() override { conn_.poll(); }
831 };
832 struct write_addon_connection_data : public network_transmission::connection_data
833 {
835  : conn_(conn), client_(client) {}
836  std::size_t total() override { return conn_.bytes_to_write(); }
837  virtual std::size_t current() override { return conn_.bytes_written(); }
838  virtual bool finished() override { return conn_.done(); }
839  virtual void cancel() override { client_.connect(); }
840  virtual void poll() override { conn_.poll(); }
843 };
844 void addons_client::wait_for_transfer_done(const std::string& status_message, transfer_mode mode)
845 {
846  check_connected();
847  std::unique_ptr<network_transmission::connection_data> cd;
848  switch(mode) {
850  cd.reset(new read_addon_connection_data{*conn_, *this});
851  break;
853  cd.reset(new connect_connection_data{*conn_, *this});
854  break;
856  cd.reset(new write_addon_connection_data{*conn_, *this});
857  break;
858  default:
859  throw std::invalid_argument("Addon client: invalid transfer mode");
860  }
861 
862  gui2::dialogs::network_transmission stat(*cd, _("Add-ons Manager"), status_message);
863 
864  if(!stat.show()) {
865  // Notify the caller chain that the user aborted the operation.
866  if(mode == transfer_mode::connect) {
867  throw user_disconnect();
868  } else {
869  throw user_exit();
870  }
871  }
872 }
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition: manager.cpp:138
config get_addon_pbl_info(const std::string &addon_name, bool do_validate)
Gets the publish information for an add-on.
Definition: manager.cpp:71
void unarchive_addon(const config &cfg, std::function< void(unsigned)> progress_callback)
Definition: manager.cpp:336
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 purge_addon(const config &removelist)
Removes the listed files from the addon.
Definition: manager.cpp:371
void archive_addon(const std::string &addon_name, config &cfg)
Archives an add-on into a config object for campaignd transactions.
Definition: manager.cpp:290
void write_addon_install_info(const std::string &addon_name, const config &cfg)
Definition: manager.cpp:121
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
version_info get_addon_version_info(const std::string &addon)
Returns a particular installed add-on's version information.
Definition: manager.cpp:422
bool is_addon_installed(const std::string &addon_name)
Check whether the specified add-on is currently installed.
Definition: manager.cpp:214
static auto & dummy
Add-ons (campaignd) client class.
Definition: client.hpp:41
std::map< std::string, int > get_addon_count_by_type()
Definition: client.cpp:115
std::string addr_
Definition: client.hpp:244
void disconnect()
Disconnects from the add-on server.
Definition: client.hpp:66
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
bool unhide_addon(const std::string &addon, const std::string &username, const std::string &passphrase)
Definition: client.cpp:233
bool do_check_before_overwriting_addon(const addon_info &addon)
Checks whether the given add-on has local .pbl or VCS information and asks before overwriting it.
Definition: client.cpp:692
void wait_for_transfer_done(const std::string &status_message, transfer_mode mode=transfer_mode::download)
Waits for a network transfer, displaying a status window.
Definition: client.cpp:844
config get_forum_auth_usage()
Definition: client.cpp:157
std::unique_ptr< network_asio::connection > conn_
Definition: client.hpp:247
@ success
The add-on was correctly installed.
@ failure
The add-on could not be downloaded from the server.
@ abort
User aborted the operation because of an issue with dependencies or chose not to overwrite the add-on...
std::string port_
Definition: client.hpp:246
bool download_addon(config &archive_cfg, const std::string &id, const std::string &title, const version_info &version, bool increase_downloads=true)
Downloads the specified add-on from the server.
Definition: client.cpp:470
void send_request(const config &request, config &response)
Sends a request to the add-ons server.
Definition: client.cpp:794
bool try_fetch_addon(const addon_info &addon)
Definition: client.cpp:565
std::string server_url_
Definition: client.hpp:254
config get_addon_downloads_by_version(const std::string &addon)
Definition: client.cpp:138
bool hide_addon(const std::string &addon, const std::string &username, const std::string &passphrase)
Definition: client.cpp:213
std::string host_
Definition: client.hpp:245
void check_connected() const
Makes sure the add-ons server connection is working.
Definition: client.cpp:785
std::set< std::string > server_capabilities_
Definition: client.hpp:253
std::string server_id_
Definition: client.hpp:251
bool install_addon(config &archive_cfg, const addon_info &info)
Installs the specified add-on using an archive received from the server.
Definition: client.cpp:493
config get_hidden_addons(const std::string &username, const std::string &passphrase)
Definition: client.cpp:193
bool delete_remote_addon(const std::string &id, std::string &response_message, const std::set< std::string > &admin_set={})
Requests the specified add-on to be removed from the server.
Definition: client.cpp:417
std::string license_notice_
Definition: client.hpp:255
void clear_last_error()
Definition: client.cpp:770
std::string last_error_
Definition: client.hpp:248
addons_client(const addons_client &)=delete
std::string last_error_data_
Definition: client.hpp:249
std::string server_version_
Definition: client.hpp:252
void connect()
Tries to establish a connection to the add-ons server.
Definition: client.cpp:69
void send_simple_request(const std::string &request_string, config &response)
Sends a simple request message to the add-ons server.
Definition: client.cpp:802
bool is_error_response(const config &response_cfg)
If the response has the [error] child, then check for the status_code attribute.
Definition: client.cpp:751
config get_addon_admins()
Definition: client.cpp:175
bool request_distribution_terms(std::string &terms)
Request the add-ons server distribution terms message.
Definition: client.cpp:274
install_result do_resolve_addon_dependencies(const addons_list &addons, const addon_info &addon)
Warns the user about unresolved dependencies and installs them if they choose to do so.
Definition: client.cpp:585
const std::string & get_last_server_error() const
Returns the last error message sent by the server, or an empty string.
Definition: client.hpp:77
void clear_server_info()
Definition: client.cpp:776
bool request_addons_list(config &cfg, bool icons)
Request the add-ons list from the server.
Definition: client.cpp:253
bool upload_addon(const std::string &id, std::string &response_message, config &cfg, bool local_only)
Uploads an add-on to the server.
Definition: client.cpp:297
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
void append(const config &cfg)
Append data from another config object to this one.
Definition: config.cpp:188
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:362
const_attr_itors attribute_range() const
Definition: config.cpp:756
auto all_children_view() const
In-order iteration over all children.
Definition: config.hpp:796
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:312
void clear()
Definition: config.cpp:818
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:380
config & add_child(config_key_type key)
Definition: config.cpp:436
static auto display(T &&... args)
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
bool show(const unsigned auto_close_time=0)
Shows the window.
Dialog that tracks network transmissions.
A class that represents a TCP/IP connection.
std::size_t bytes_to_write() const
std::size_t bytes_read() const
std::size_t poll()
Handle all pending asynchronous events and return.
std::size_t bytes_to_read() const
std::size_t bytes_written() const
bool done() const
True if connected and no high-level operation is in progress.
static prefs & get()
void set_password(const std::string &server, const std::string &login, const std::string &key)
std::string password(const std::string &server, const std::string &login)
Thrown by operations encountering invalid UTF-8 data.
Represents version numbers.
std::string str() const
Serializes the version number into string form.
#define ERR_ADDONS
Definition: client.cpp:42
static lg::log_domain log_addons_client("addons-client")
#define WRN_ADDONS
Definition: client.cpp:43
#define LOG_ADDONS
Definition: client.cpp:44
Networked add-ons (campaignd) client interface.
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1333
const config * cfg
#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:101
static std::string _(const char *str)
Definition: gettext.hpp:97
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
std::map< std::string, addon_info > addons_list
Definition: info.hpp:27
Standard logging facilities (interface).
@ WAIT
Definition: cursor.hpp:28
std::string escape_text(std::string_view text)
Escapes the pango markup characters in a text.
Definition: escape.hpp:33
const std::string unicode_bullet
Definition: constants.cpp:47
bool allow_insecure
Definition: game_config.cpp:78
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
logger & info()
Definition: log.cpp:351
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::string join(const T &v, const std::string &s=",")
Generates a new string joining container items in a list.
std::map< std::string, t_string > string_map
std::vector< std::string > split(const config_attribute_value &val)
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::pair< std::string, std::string > parse_network_address(const std::string &address, const std::string &default_port)
Parse a host:port style network address, supporting [] notation for ipv6 addresses.
addon_tracking_info get_addon_tracking_info(const addon_info &addon)
Get information about an add-on comparing its local state with the add-ons server entry.
Definition: state.cpp:25
@ ADDON_NONE
Add-on is not installed.
Definition: state.hpp:24
@ ADDON_INSTALLED_UPGRADABLE
Version in the server is newer than local installation.
Definition: state.hpp:28
version_info current_version
Definition: info.hpp:81
std::string title
Definition: info.hpp:76
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::string id
Definition: info.hpp:75
Stores additional status information about add-ons.
Definition: state.hpp:47
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
bool wml_changed
Specifies if WML on disk was altered and needs to be reloaded.
Definition: client.hpp:147
addons_client & client_
Definition: client.cpp:830
connect_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:822
std::size_t total() override
Definition: client.cpp:824
bool finished() override
Definition: client.cpp:826
void cancel() override
Definition: client.cpp:827
void poll() override
Definition: client.cpp:828
network_asio::connection & conn_
Definition: client.cpp:829
std::size_t current() override
Definition: client.cpp:825
network_asio::connection & conn_
Definition: client.cpp:817
virtual void poll() override
Definition: client.cpp:816
virtual void cancel() override
Definition: client.cpp:815
virtual std::size_t current() override
Definition: client.cpp:813
addons_client & client_
Definition: client.cpp:818
virtual bool finished() override
Definition: client.cpp:814
read_addon_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:810
std::size_t total() override
Definition: client.cpp:812
virtual void poll() override
Definition: client.cpp:840
virtual bool finished() override
Definition: client.cpp:838
std::size_t total() override
Definition: client.cpp:836
write_addon_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:834
virtual void cancel() override
Definition: client.cpp:839
addons_client & client_
Definition: client.cpp:842
virtual std::size_t current() override
Definition: client.cpp:837
network_asio::connection & conn_
Definition: client.cpp:841
bool addon_name_legal(const std::string &name)
Checks whether an add-on id/name is legal or not.
Definition: validation.cpp:56
bool addon_icon_too_large(const std::string &icon)
Checks whether an add-on icon is too large.
Definition: validation.cpp:74
void make_updatepack(config &pack, const config &from, const config &to)
&from, &to are the top directories of their structures; addlist/removelist tag is treated as [dir]
Definition: validation.cpp:374
bool check_names_legal(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for illegal names.
Definition: validation.cpp:170
bool contains_hashlist(const config &from, const config &to)
Definition: validation.cpp:292
const unsigned short default_campaignd_port
Default port number for the addon server.
Definition: validation.cpp:27
std::string translated_addon_check_status(unsigned int code)
Definition: validation.cpp:548
bool check_case_insensitive_duplicates(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for case-conflicts.
Definition: validation.cpp:179