The Battle for Wesnoth  1.19.8+dev
client.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/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  const std::string& server_error = get_last_server_error();
574  if(!server_error.empty()) {
576  _("The server responded with an error:") + "\n" + server_error);
577  }
578  return false;
579  } else {
580  return true;
581  }
582 }
583 
585 {
586  install_result result;
588  result.wml_changed = false;
589 
590  auto cursor_setter = std::make_unique<cursor::setter>(cursor::WAIT);
591 
592  // TODO: We don't currently check for the need to upgrade. I'll probably
593  // work on that when implementing dependency tiers later.
594 
595  const std::set<std::string>& deps = addon.resolve_dependencies(addons);
596 
597  std::vector<std::string> missing_deps;
598  std::vector<std::string> broken_deps;
599  // if two add-ons both have the same dependency and are being downloaded in a batch (such as via the adhoc connection)
600  // then the version cache will not be updated after the first is downloaded
601  // which will result in it being treated as version 0.0.0, which is then interpreted as being "upgradeable"
602  // which then causes the user to be prompted to download the same dependency multiple times
603  version_info unknown_version(0, 0, 0);
604 
605  for(const std::string& dep : deps) {
606  try {
608 
609  // ADDON_NONE means not installed.
610  if(info.state == ADDON_NONE) {
611  missing_deps.push_back(dep);
612  } else if(info.state == ADDON_INSTALLED_UPGRADABLE && info.installed_version != unknown_version) {
613  // Tight now, we don't need to distinguish the lists of missing
614  // and outdated addons, so just add them to missing.
615  missing_deps.push_back(dep);
616  }
617  } catch(const std::out_of_range&) {
618  // Dependency wasn't found on server, check locally directly.
619  if(!is_addon_installed(dep)) {
620  broken_deps.push_back(dep);
621  }
622  }
623  }
624 
625  cursor_setter.reset();
626 
627  if(!broken_deps.empty()) {
628  std::string broken_deps_report;
629 
630  broken_deps_report = _n(
631  "The selected add-on has the following dependency, which is not currently installed or available from the server. Do you wish to continue?",
632  "The selected add-on has the following dependencies, which are not currently installed or available from the server. Do you wish to continue?",
633  broken_deps.size());
634  broken_deps_report += "\n";
635 
636  for(const std::string& broken_dep_id : broken_deps) {
637  broken_deps_report += "\n " + font::unicode_bullet + " " + make_addon_title(broken_dep_id);
638  }
639 
640  if(gui2::show_message(_("Broken Dependencies"), broken_deps_report, gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
642  return result; // canceled by user
643  }
644  }
645 
646  if(missing_deps.empty()) {
647  // No dependencies to install, carry on.
648  return result;
649  }
650 
651  {
652  addons_list options;
653  for(const std::string& dep : missing_deps) {
654  options[dep] = addons.at(dep);
655  }
656 
657  if(!gui2::dialogs::install_dependencies::execute(options)) {
658  return result; // the user has chosen to continue without installing anything.
659  }
660  }
661 
662  //
663  // Install dependencies now.
664  //
665 
666  std::vector<std::string> failed_titles;
667 
668  for(const std::string& dep : missing_deps) {
669  const addon_info& missing_addon = addons.at(dep);
670 
671  if(!try_fetch_addon(missing_addon)) {
672  failed_titles.push_back(missing_addon.title);
673  } else {
674  result.wml_changed = true;
675  }
676  }
677 
678  if(!failed_titles.empty()) {
679  const std::string& failed_deps_report = _n(
680  "The following dependency could not be installed. Do you still wish to continue?",
681  "The following dependencies could not be installed. Do you still wish to continue?",
682  failed_titles.size()) + std::string("\n\n") + utils::bullet_list(failed_titles);
683 
684  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.
685  return result;
686  }
687 
688  return result;
689 }
690 
692 {
693  const std::string& addon_id = addon.id;
694 
695  const bool pbl = have_addon_pbl_info(addon_id);
696  const bool vcs = have_addon_in_vcs_tree(addon_id);
697 
698  if(!pbl && !vcs) {
699  return true;
700  }
701 
702  utils::string_map symbols;
703  symbols["addon"] = font::escape_text(addon.title);
704  std::string text;
705  std::vector<std::string> extra_items;
706 
707  text = VGETTEXT("The add-on '$addon|' is already installed and contains additional information that will be permanently lost if you continue:", symbols);
708  text += "\n\n";
709 
710  if(pbl) {
711  extra_items.push_back(_("Publishing information file (.pbl)"));
712  }
713 
714  if(vcs) {
715  extra_items.push_back(_("Version control system (VCS) information"));
716  }
717 
718  text += utils::bullet_list(extra_items) + "\n\n";
719  text += _("Do you really wish to continue?");
720 
722 }
723 
725 {
726  if(!(do_check_before_overwriting_addon(addon))) {
727  // Just do nothing and leave.
728  install_result result;
730  result.wml_changed = false;
731 
732  return result;
733  }
734 
735  // Resolve any dependencies
736  install_result res = do_resolve_addon_dependencies(addons, addon);
737  if(res.outcome != install_outcome::success) { // this function only returns SUCCESS and ABORT as outcomes
738  return res; // user aborted
739  }
740 
741  if(!try_fetch_addon(addon)) {
743  return res; //wml_changed should have whatever value was obtained in resolving dependencies
744  } else {
745  res.wml_changed = true;
746  return res; //we successfully installed something, so now the wml was definitely changed
747  }
748 }
749 
750 bool addons_client::is_error_response(const config& response_cfg)
751 {
752  if(auto error = response_cfg.optional_child("error")) {
753  if(error->has_attribute("status_code")) {
754  const auto& status_msg = translated_addon_check_status(error["status_code"].to_unsigned());
755  last_error_ = font::escape_text(status_msg);
756  } else {
757  last_error_ = font::escape_text(error["message"].str());
758  }
759  last_error_data_ = font::escape_text(error["extra_data"].str());
760  ERR_ADDONS << "server error: " << *error;
761  return true;
762  } else {
763  last_error_.clear();
764  last_error_data_.clear();
765  return false;
766  }
767 }
768 
770 {
771  last_error_.clear();
772  last_error_data_.clear();
773 }
774 
776 {
777  server_id_.clear();
778  server_version_.clear();
779  server_capabilities_.clear();
780  server_url_.clear();
781  license_notice_.clear();
782 }
783 
785 {
786  assert(conn_ != nullptr);
787  if(conn_ == nullptr) {
788  ERR_ADDONS << "not connected to server";
789  throw not_connected_to_server();
790  }
791 }
792 
793 void addons_client::send_request(const config& request, config& response)
794 {
795  check_connected();
796 
797  response.clear();
798  conn_->transfer(request, response);
799 }
800 
801 void addons_client::send_simple_request(const std::string& request_string, config& response)
802 {
803  config request;
804  request.add_child(request_string);
805  send_request(request, response);
806 }
807 struct read_addon_connection_data : public network_transmission::connection_data
808 {
810  : conn_(conn), client_(client) {}
811  std::size_t total() override { return conn_.bytes_to_read(); }
812  virtual std::size_t current() override { return conn_.bytes_read(); }
813  virtual bool finished() override { return conn_.done(); }
814  virtual void cancel() override { client_.connect(); }
815  virtual void poll() override { conn_.poll(); }
818 };
819 struct connect_connection_data : public network_transmission::connection_data
820 {
822  : conn_(conn), client_(client) {}
823  std::size_t total() override { return conn_.bytes_to_read(); }
824  std::size_t current() override { return conn_.bytes_read(); }
825  bool finished() override { return conn_.done(); }
826  void cancel() override { client_.disconnect(); }
827  void poll() override { conn_.poll(); }
830 };
831 struct write_addon_connection_data : public network_transmission::connection_data
832 {
834  : conn_(conn), client_(client) {}
835  std::size_t total() override { return conn_.bytes_to_write(); }
836  virtual std::size_t current() override { return conn_.bytes_written(); }
837  virtual bool finished() override { return conn_.done(); }
838  virtual void cancel() override { client_.connect(); }
839  virtual void poll() override { conn_.poll(); }
842 };
843 void addons_client::wait_for_transfer_done(const std::string& status_message, transfer_mode mode)
844 {
845  check_connected();
846  std::unique_ptr<network_transmission::connection_data> cd;
847  switch(mode) {
849  cd.reset(new read_addon_connection_data{*conn_, *this});
850  break;
852  cd.reset(new connect_connection_data{*conn_, *this});
853  break;
855  cd.reset(new write_addon_connection_data{*conn_, *this});
856  break;
857  default:
858  throw std::invalid_argument("Addon client: invalid transfer mode");
859  }
860 
861  gui2::dialogs::network_transmission stat(*cd, _("Add-ons Manager"), status_message);
862 
863  if(!stat.show()) {
864  // Notify the caller chain that the user aborted the operation.
865  if(mode == transfer_mode::connect) {
866  throw user_disconnect();
867  } else {
868  throw user_exit();
869  }
870  }
871 }
bool remove_local_addon(const std::string &addon)
Removes the specified add-on, deleting its full directory structure.
Definition: manager.cpp:144
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:342
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:377
void archive_addon(const std::string &addon_name, config &cfg)
Archives an add-on into a config object for campaignd transactions.
Definition: manager.cpp:296
void write_addon_install_info(const std::string &addon_name, const config &cfg)
Definition: manager.cpp:127
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:428
bool is_addon_installed(const std::string &addon_name)
Check whether the specified add-on is currently installed.
Definition: manager.cpp:220
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:724
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:691
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:843
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:793
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:784
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:769
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:801
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:750
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:584
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:775
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:203
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:366
const_attr_itors attribute_range() const
Definition: config.cpp:760
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:316
void clear()
Definition: config.cpp:828
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:384
config & add_child(config_key_type key)
Definition: config.cpp:440
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:1343
#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: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:323
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:319
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:224
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:284
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:829
connect_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:821
std::size_t total() override
Definition: client.cpp:823
bool finished() override
Definition: client.cpp:825
void cancel() override
Definition: client.cpp:826
void poll() override
Definition: client.cpp:827
network_asio::connection & conn_
Definition: client.cpp:828
std::size_t current() override
Definition: client.cpp:824
network_asio::connection & conn_
Definition: client.cpp:816
virtual void poll() override
Definition: client.cpp:815
virtual void cancel() override
Definition: client.cpp:814
virtual std::size_t current() override
Definition: client.cpp:812
addons_client & client_
Definition: client.cpp:817
virtual bool finished() override
Definition: client.cpp:813
read_addon_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:809
std::size_t total() override
Definition: client.cpp:811
virtual void poll() override
Definition: client.cpp:839
virtual bool finished() override
Definition: client.cpp:837
std::size_t total() override
Definition: client.cpp:835
write_addon_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:833
virtual void cancel() override
Definition: client.cpp:838
addons_client & client_
Definition: client.cpp:841
virtual std::size_t current() override
Definition: client.cpp:836
network_asio::connection & conn_
Definition: client.cpp:840
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