The Battle for Wesnoth  1.19.7+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["name"] = id;
452  request_body["passphrase"] = cfg["passphrase"];
453  // needed in case of forum_auth authentication since the author stored on disk on the server is not necessarily the current primary author
454  request_body["uploader"] = cfg["uploader"];
455 
456  LOG_ADDONS << "requesting server to delete " << id;
457 
458  send_request(request_buf, response_buf);
459  wait_for_transfer_done(VGETTEXT("Removing add-on <i>$addon_title</i> from the server...", i18n_symbols));
460 
461  if(auto message_cfg = response_buf.optional_child("message")) {
462  response_message = message_cfg["message"].str();
463  LOG_ADDONS << "server response: " << response_message;
464  }
465 
466  return !is_error_response(response_buf);
467 }
468 
469 bool addons_client::download_addon(config& archive_cfg, const std::string& id, const std::string& title, const version_info& version, bool increase_downloads)
470 {
471  archive_cfg.clear();
472 
473  config request_buf;
474  config& request_body = request_buf.add_child("request_campaign");
475 
476  request_body["name"] = id;
477  request_body["increase_downloads"] = increase_downloads;
478  request_body["version"] = version.str();
479  request_body["from_version"] = get_addon_version_info(id);
480 
481  utils::string_map i18n_symbols;
482  i18n_symbols["addon_title"] = font::escape_text(title);
483 
484  LOG_ADDONS << "downloading " << id;
485 
486  send_request(request_buf, archive_cfg);
487  wait_for_transfer_done(VGETTEXT("Downloading add-on <i>$addon_title</i>...", i18n_symbols));
488 
489  return !is_error_response(archive_cfg);
490 }
491 
493 {
494  const cursor::setter cursor_setter(cursor::WAIT);
495 
496  utils::string_map i18n_symbols;
497  i18n_symbols["addon_title"] = font::escape_text(info.title);
498 
499  auto progress_dlg = gui2::dialogs::file_progress::display(_("Add-ons Manager"), VGETTEXT("Installing add-on <i>$addon_title</i>...", i18n_symbols));
500  auto progress_cb = [&progress_dlg](unsigned value) {
501  progress_dlg->update_progress(value);
502  };
503 
504  if(archive_cfg.has_child("removelist") || archive_cfg.has_child("addlist")) {
505  LOG_ADDONS << "Received an updatepack for the addon '" << info.id << "'";
506 
507  // A consistency check
508  for(const auto [key, cfg] : archive_cfg.all_children_view()) {
509  if(key == "removelist" || key == "addlist") {
510  if(!check_names_legal(cfg)) {
511  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has an invalid file or directory "
512  "name and cannot be installed.", i18n_symbols));
513  return false;
514  }
516  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has file or directory names "
517  "with case conflicts. This may cause problems.", i18n_symbols));
518  }
519  }
520  }
521 
522  for(const auto [key, cfg] : archive_cfg.all_children_view()) {
523  if(key == "removelist") {
524  purge_addon(cfg);
525  } else if(key == "addlist") {
526  unarchive_addon(cfg, progress_cb);
527  }
528  }
529 
530  LOG_ADDONS << "Update completed.";
531 
532  //#TODO: hash verification ???
533  } else {
534  LOG_ADDONS << "Received a full pack for the addon '" << info.id << "'";
535 
536  if(!check_names_legal(archive_cfg)) {
537  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has an invalid file or directory "
538  "name and cannot be installed.", i18n_symbols));
539  return false;
540  }
541  if(!check_case_insensitive_duplicates(archive_cfg)) {
542  gui2::show_error_message(VGETTEXT("The add-on <i>$addon_title</i> has file or directory names "
543  "with case conflicts. This may cause problems.", i18n_symbols));
544  }
545 
546  LOG_ADDONS << "unpacking " << info.id;
547 
548  // Remove any previously installed versions
549  if(!remove_local_addon(info.id)) {
550  WRN_ADDONS << "failed to uninstall previous version of " << info.id << "; the add-on may not work properly!";
551  }
552 
553  unarchive_addon(archive_cfg, progress_cb);
554  LOG_ADDONS << "unpacking finished";
555  }
556 
557  config info_cfg;
558  info.write_minimal(info_cfg);
559  write_addon_install_info(info.id, info_cfg);
560 
561  return true;
562 }
563 
565 {
566  config archive;
567 
568  if(!(
569  download_addon(archive, addon.id, addon.display_title_full(), addon.current_version, !is_addon_installed(addon.id)) &&
570  install_addon(archive, addon)
571  )) {
572  const std::string& server_error = get_last_server_error();
573  if(!server_error.empty()) {
575  _("The server responded with an error:") + "\n" + server_error);
576  }
577  return false;
578  } else {
579  return true;
580  }
581 }
582 
584 {
585  install_result result;
587  result.wml_changed = false;
588 
589  auto cursor_setter = std::make_unique<cursor::setter>(cursor::WAIT);
590 
591  // TODO: We don't currently check for the need to upgrade. I'll probably
592  // work on that when implementing dependency tiers later.
593 
594  const std::set<std::string>& deps = addon.resolve_dependencies(addons);
595 
596  std::vector<std::string> missing_deps;
597  std::vector<std::string> broken_deps;
598  // if two add-ons both have the same dependency and are being downloaded in a batch (such as via the adhoc connection)
599  // then the version cache will not be updated after the first is downloaded
600  // which will result in it being treated as version 0.0.0, which is then interpreted as being "upgradeable"
601  // which then causes the user to be prompted to download the same dependency multiple times
602  version_info unknown_version(0, 0, 0);
603 
604  for(const std::string& dep : deps) {
605  try {
607 
608  // ADDON_NONE means not installed.
609  if(info.state == ADDON_NONE) {
610  missing_deps.push_back(dep);
611  } else if(info.state == ADDON_INSTALLED_UPGRADABLE && info.installed_version != unknown_version) {
612  // Tight now, we don't need to distinguish the lists of missing
613  // and outdated addons, so just add them to missing.
614  missing_deps.push_back(dep);
615  }
616  } catch(const std::out_of_range&) {
617  // Dependency wasn't found on server, check locally directly.
618  if(!is_addon_installed(dep)) {
619  broken_deps.push_back(dep);
620  }
621  }
622  }
623 
624  cursor_setter.reset();
625 
626  if(!broken_deps.empty()) {
627  std::string broken_deps_report;
628 
629  broken_deps_report = _n(
630  "The selected add-on has the following dependency, which is not currently installed or available from the server. Do you wish to continue?",
631  "The selected add-on has the following dependencies, which are not currently installed or available from the server. Do you wish to continue?",
632  broken_deps.size());
633  broken_deps_report += "\n";
634 
635  for(const std::string& broken_dep_id : broken_deps) {
636  broken_deps_report += "\n " + font::unicode_bullet + " " + make_addon_title(broken_dep_id);
637  }
638 
639  if(gui2::show_message(_("Broken Dependencies"), broken_deps_report, gui2::dialogs::message::yes_no_buttons) != gui2::retval::OK) {
641  return result; // canceled by user
642  }
643  }
644 
645  if(missing_deps.empty()) {
646  // No dependencies to install, carry on.
647  return result;
648  }
649 
650  {
651  addons_list options;
652  for(const std::string& dep : missing_deps) {
653  options[dep] = addons.at(dep);
654  }
655 
656  if(!gui2::dialogs::install_dependencies::execute(options)) {
657  return result; // the user has chosen to continue without installing anything.
658  }
659  }
660 
661  //
662  // Install dependencies now.
663  //
664 
665  std::vector<std::string> failed_titles;
666 
667  for(const std::string& dep : missing_deps) {
668  const addon_info& missing_addon = addons.at(dep);
669 
670  if(!try_fetch_addon(missing_addon)) {
671  failed_titles.push_back(missing_addon.title);
672  } else {
673  result.wml_changed = true;
674  }
675  }
676 
677  if(!failed_titles.empty()) {
678  const std::string& failed_deps_report = _n(
679  "The following dependency could not be installed. Do you still wish to continue?",
680  "The following dependencies could not be installed. Do you still wish to continue?",
681  failed_titles.size()) + std::string("\n\n") + utils::bullet_list(failed_titles);
682 
683  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.
684  return result;
685  }
686 
687  return result;
688 }
689 
691 {
692  const std::string& addon_id = addon.id;
693 
694  const bool pbl = have_addon_pbl_info(addon_id);
695  const bool vcs = have_addon_in_vcs_tree(addon_id);
696 
697  if(!pbl && !vcs) {
698  return true;
699  }
700 
701  utils::string_map symbols;
702  symbols["addon"] = font::escape_text(addon.title);
703  std::string text;
704  std::vector<std::string> extra_items;
705 
706  text = VGETTEXT("The add-on '$addon|' is already installed and contains additional information that will be permanently lost if you continue:", symbols);
707  text += "\n\n";
708 
709  if(pbl) {
710  extra_items.push_back(_("Publishing information file (.pbl)"));
711  }
712 
713  if(vcs) {
714  extra_items.push_back(_("Version control system (VCS) information"));
715  }
716 
717  text += utils::bullet_list(extra_items) + "\n\n";
718  text += _("Do you really wish to continue?");
719 
721 }
722 
724 {
725  if(!(do_check_before_overwriting_addon(addon))) {
726  // Just do nothing and leave.
727  install_result result;
729  result.wml_changed = false;
730 
731  return result;
732  }
733 
734  // Resolve any dependencies
735  install_result res = do_resolve_addon_dependencies(addons, addon);
736  if(res.outcome != install_outcome::success) { // this function only returns SUCCESS and ABORT as outcomes
737  return res; // user aborted
738  }
739 
740  if(!try_fetch_addon(addon)) {
742  return res; //wml_changed should have whatever value was obtained in resolving dependencies
743  } else {
744  res.wml_changed = true;
745  return res; //we successfully installed something, so now the wml was definitely changed
746  }
747 }
748 
749 bool addons_client::is_error_response(const config& response_cfg)
750 {
751  if(auto error = response_cfg.optional_child("error")) {
752  if(error->has_attribute("status_code")) {
753  const auto& status_msg = translated_addon_check_status(error["status_code"].to_unsigned());
754  last_error_ = font::escape_text(status_msg);
755  } else {
756  last_error_ = font::escape_text(error["message"].str());
757  }
758  last_error_data_ = font::escape_text(error["extra_data"].str());
759  ERR_ADDONS << "server error: " << *error;
760  return true;
761  } else {
762  last_error_.clear();
763  last_error_data_.clear();
764  return false;
765  }
766 }
767 
769 {
770  last_error_.clear();
771  last_error_data_.clear();
772 }
773 
775 {
776  server_id_.clear();
777  server_version_.clear();
778  server_capabilities_.clear();
779  server_url_.clear();
780  license_notice_.clear();
781 }
782 
784 {
785  assert(conn_ != nullptr);
786  if(conn_ == nullptr) {
787  ERR_ADDONS << "not connected to server";
788  throw not_connected_to_server();
789  }
790 }
791 
792 void addons_client::send_request(const config& request, config& response)
793 {
794  check_connected();
795 
796  response.clear();
797  conn_->transfer(request, response);
798 }
799 
800 void addons_client::send_simple_request(const std::string& request_string, config& response)
801 {
802  config request;
803  request.add_child(request_string);
804  send_request(request, response);
805 }
806 struct read_addon_connection_data : public network_transmission::connection_data
807 {
809  : conn_(conn), client_(client) {}
810  std::size_t total() override { return conn_.bytes_to_read(); }
811  virtual std::size_t current() override { return conn_.bytes_read(); }
812  virtual bool finished() override { return conn_.done(); }
813  virtual void cancel() override { client_.connect(); }
814  virtual void poll() override { conn_.poll(); }
817 };
818 struct connect_connection_data : public network_transmission::connection_data
819 {
821  : conn_(conn), client_(client) {}
822  std::size_t total() override { return conn_.bytes_to_read(); }
823  std::size_t current() override { return conn_.bytes_read(); }
824  bool finished() override { return conn_.done(); }
825  void cancel() override { client_.disconnect(); }
826  void poll() override { conn_.poll(); }
829 };
830 struct write_addon_connection_data : public network_transmission::connection_data
831 {
833  : conn_(conn), client_(client) {}
834  std::size_t total() override { return conn_.bytes_to_write(); }
835  virtual std::size_t current() override { return conn_.bytes_written(); }
836  virtual bool finished() override { return conn_.done(); }
837  virtual void cancel() override { client_.connect(); }
838  virtual void poll() override { conn_.poll(); }
841 };
842 void addons_client::wait_for_transfer_done(const std::string& status_message, transfer_mode mode)
843 {
844  check_connected();
845  std::unique_ptr<network_transmission::connection_data> cd;
846  switch(mode) {
848  cd.reset(new read_addon_connection_data{*conn_, *this});
849  break;
851  cd.reset(new connect_connection_data{*conn_, *this});
852  break;
854  cd.reset(new write_addon_connection_data{*conn_, *this});
855  break;
856  default:
857  throw std::invalid_argument("Addon client: invalid transfer mode");
858  }
859 
860  gui2::dialogs::network_transmission stat(*cd, _("Add-ons Manager"), status_message);
861 
862  if(!stat.show()) {
863  // Notify the caller chain that the user aborted the operation.
864  if(mode == transfer_mode::connect) {
865  throw user_disconnect();
866  } else {
867  throw user_exit();
868  }
869  }
870 }
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:723
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:690
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:842
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:469
void send_request(const config &request, config &response)
Sends a request to the add-ons server.
Definition: client.cpp:792
bool try_fetch_addon(const addon_info &addon)
Definition: client.cpp:564
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:783
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:492
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:768
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:800
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:749
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:583
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:774
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:828
connect_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:820
std::size_t total() override
Definition: client.cpp:822
bool finished() override
Definition: client.cpp:824
void cancel() override
Definition: client.cpp:825
void poll() override
Definition: client.cpp:826
network_asio::connection & conn_
Definition: client.cpp:827
std::size_t current() override
Definition: client.cpp:823
network_asio::connection & conn_
Definition: client.cpp:815
virtual void poll() override
Definition: client.cpp:814
virtual void cancel() override
Definition: client.cpp:813
virtual std::size_t current() override
Definition: client.cpp:811
addons_client & client_
Definition: client.cpp:816
virtual bool finished() override
Definition: client.cpp:812
read_addon_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:808
std::size_t total() override
Definition: client.cpp:810
virtual void poll() override
Definition: client.cpp:838
virtual bool finished() override
Definition: client.cpp:836
std::size_t total() override
Definition: client.cpp:834
write_addon_connection_data(network_asio::connection &conn, addons_client &client)
Definition: client.cpp:832
virtual void cancel() override
Definition: client.cpp:837
addons_client & client_
Definition: client.cpp:840
virtual std::size_t current() override
Definition: client.cpp:835
network_asio::connection & conn_
Definition: client.cpp:839
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