The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
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"
24#include "game_config.hpp"
25#include "gettext.hpp"
31#include "log.hpp"
36
37#include <stdexcept>
38
39#include "addon/client.hpp"
40
41static 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
49addons_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&) {
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
115std::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(), true);
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(), true);
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(), true);
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(), true);
187 return dummy;
188 }
189
190 return response;
191}
192
193config 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(), true);
207 return dummy;
208 }
209
210 return response;
211}
212
213bool 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(), true);
227 return false;
228 }
229
230 return true;
231}
232
233bool 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(), true);
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
297bool 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);
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&){
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)){
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)){
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
417bool 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)) {
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
470bool 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
493bool addons_client::install_addon(config& archive_cfg, const addon_info& info)
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") {
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, true);
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 {
608 addon_tracking_info info = get_addon_tracking_info(addons.at(dep));
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{
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
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
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";
791 }
792}
793
794void addons_client::send_request(const config& request, config& response)
795{
797
798 response.clear();
799 conn_->transfer(request, response);
800}
801
802void 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}
808struct read_addon_connection_data : public network_transmission::connection_data
809{
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};
820struct connect_connection_data : public network_transmission::connection_data
821{
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};
832struct write_addon_connection_data : public network_transmission::connection_data
833{
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};
844void addons_client::wait_for_transfer_done(const std::string& status_message, transfer_mode mode)
845{
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
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
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
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
config & add_child(std::string_view key)
Definition config.cpp:436
void append(const config &cfg)
Append data from another config object to this one.
Definition config.cpp:188
optional_config_impl< config > optional_child(std::string_view key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition config.cpp:380
const_attr_itors attribute_range() const
Definition config.cpp:740
auto all_children_view() const
In-order iteration over all children.
Definition config.hpp:795
bool has_child(std::string_view key) const
Determine whether a config has a child or not.
Definition config.cpp:312
config & mandatory_child(std::string_view key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition config.cpp:362
void clear()
Definition config.cpp:802
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.
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:104
static std::string _(const char *str)
Definition gettext.hpp:100
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:28
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
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
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 Range &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)
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.
std::unique_ptr< MIX_Audio, decltype(&MIX_DestroyAudio)> value
Definition sound.cpp:139
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.
bool addon_icon_too_large(const std::string &icon)
Checks whether an add-on icon is too large.
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]
bool check_names_legal(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for illegal names.
bool contains_hashlist(const config &from, const config &to)
const unsigned short default_campaignd_port
Default port number for the addon server.
std::string translated_addon_check_status(unsigned int code)
bool check_case_insensitive_duplicates(const config &dir, std::vector< std::string > *badlist)
Scans an add-on archive for case-conflicts.