From 9bab2e2efa7bb52268867924f7bd61f321dd5d49 Mon Sep 17 00:00:00 2001 From: SoftFever Date: Sun, 27 Aug 2023 22:44:37 +0800 Subject: [PATCH] fix errors after cherry picking commits --- src/libslic3r/AppConfig.cpp | 3 + src/libslic3r/AppConfig.hpp | 2 + src/slic3r/GUI/BonjourDialog.cpp | 91 +++++---- src/slic3r/GUI/BonjourDialog.hpp | 22 ++- src/slic3r/GUI/PhysicalPrinterDialog.cpp | 51 ++--- src/slic3r/GUI/Plater.cpp | 52 +++-- src/slic3r/GUI/PrintHostDialogs.cpp | 34 ++-- src/slic3r/GUI/PrintHostDialogs.hpp | 3 +- src/slic3r/Utils/Bonjour.hpp | 211 +++++++++++++++++++- src/slic3r/Utils/OctoPrint.cpp | 242 +++++++++++++---------- src/slic3r/Utils/OctoPrint.hpp | 54 +++-- src/slic3r/Utils/PrintHost.hpp | 2 +- 12 files changed, 522 insertions(+), 245 deletions(-) diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp index b2dd67a74..2732a09cf 100644 --- a/src/libslic3r/AppConfig.cpp +++ b/src/libslic3r/AppConfig.cpp @@ -335,6 +335,9 @@ void AppConfig::set_defaults() // } // #endif + if (get("allow_ip_resolve").empty()) + set("allow_ip_resolve", "1"); + if (get("presets", "filament_colors").empty()) { set_str("presets", "filament_colors", "#F2754E"); } diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp index afa894b68..0e6a6d81a 100644 --- a/src/libslic3r/AppConfig.hpp +++ b/src/libslic3r/AppConfig.hpp @@ -80,6 +80,8 @@ public: { std::string value; this->get(section, key, value); return value; } std::string get(const std::string &key) const { std::string value; this->get("app", key, value); return value; } + bool get_bool(const std::string &key) const + { return this->get(key) == "true"; } void set(const std::string §ion, const std::string &key, const std::string &value) { #ifndef NDEBUG diff --git a/src/slic3r/GUI/BonjourDialog.cpp b/src/slic3r/GUI/BonjourDialog.cpp index 0c64650df..060643c1f 100644 --- a/src/slic3r/GUI/BonjourDialog.cpp +++ b/src/slic3r/GUI/BonjourDialog.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include #include @@ -15,8 +17,8 @@ #include "slic3r/GUI/GUI.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/I18N.hpp" +#include "slic3r/GUI/format.hpp" #include "slic3r/Utils/Bonjour.hpp" -#include "Widgets/Button.hpp" namespace Slic3r { @@ -61,8 +63,6 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) , timer_state(0) , tech(tech) { - SetBackgroundColour(*wxWHITE); - const int em = GUI::wxGetApp().em_unit(); list->SetMinSize(wxSize(80 * em, 30 * em)); @@ -81,39 +81,10 @@ BonjourDialog::BonjourDialog(wxWindow *parent, Slic3r::PrinterTechnology tech) vsizer->Add(list, 1, wxEXPAND | wxALL, em); - - auto button_sizer = new wxBoxSizer(wxHORIZONTAL); - - StateColor btn_bg_green(std::pair(wxColour(0, 137, 123), StateColor::Pressed), std::pair(wxColour(38, 166, 154), StateColor::Hovered), - std::pair(wxColour(0, 150, 136), StateColor::Normal)); - - StateColor btn_bg_white(std::pair(wxColour(206, 206, 206), StateColor::Pressed), std::pair(wxColour(238, 238, 238), StateColor::Hovered), - std::pair(*wxWHITE, StateColor::Normal)); - - auto m_button_ok = new Button(this, _L("OK")); - m_button_ok->SetBackgroundColor(btn_bg_green); - m_button_ok->SetBorderColor(*wxWHITE); - m_button_ok->SetTextColor(*wxWHITE); - m_button_ok->SetFont(Label::Body_12); - m_button_ok->SetSize(wxSize(FromDIP(58), FromDIP(24))); - m_button_ok->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); - m_button_ok->SetCornerRadius(FromDIP(12)); - - m_button_ok->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_OK); }); - - auto m_button_cancel = new Button(this, _L("Cancel")); - m_button_cancel->SetBackgroundColor(btn_bg_white); - m_button_cancel->SetBorderColor(wxColour(38, 46, 48)); - m_button_cancel->SetFont(Label::Body_12); - m_button_cancel->SetSize(wxSize(FromDIP(58), FromDIP(24))); - m_button_cancel->SetMinSize(wxSize(FromDIP(58), FromDIP(24))); - m_button_cancel->SetCornerRadius(FromDIP(12)); - - m_button_cancel->Bind(wxEVT_LEFT_DOWN, [this](auto &e) { this->EndModal(wxID_CANCEL); }); - - button_sizer->AddStretchSpacer(); - button_sizer->Add(m_button_ok, 0, wxALL, FromDIP(5)); - button_sizer->Add(m_button_cancel, 0, wxALL, FromDIP(5)); + wxBoxSizer *button_sizer = new wxBoxSizer(wxHORIZONTAL); + button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em); + button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em); + // ^ Note: The Ok/Cancel labels are translated by wxWidgets vsizer->Add(button_sizer, 0, wxALIGN_CENTER); SetSizerAndFit(vsizer); @@ -253,19 +224,61 @@ void BonjourDialog::on_timer(wxTimerEvent &) // explicitly (wxTimerEvent should not be created by user code). void BonjourDialog::on_timer_process() { - const auto search_str = _utf8(L("Searching for devices")); + const auto search_str = _L("Searching for devices"); if (timer_state > 0) { const std::string dots(timer_state, '.'); - label->SetLabel(GUI::from_u8((boost::format("%1% %2%") % search_str % dots).str())); + label->SetLabel(search_str + dots); timer_state = (timer_state) % 3 + 1; } else { - label->SetLabel(GUI::from_u8((boost::format("%1%: %2%") % search_str % (_utf8(L("Finished"))+".")).str())); + label->SetLabel(search_str + ": " + _L("Finished") + "."); timer->Stop(); } } +IPListDialog::IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector& ips, size_t& selected_index) + : wxDialog(parent, wxID_ANY, _(L("Multiple resolved IP addresses")), wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER) + , m_list(new wxListView(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_REPORT | wxSIMPLE_BORDER)) + , m_selected_index (selected_index) +{ + const int em = GUI::wxGetApp().em_unit(); + m_list->SetMinSize(wxSize(40 * em, 30 * em)); + wxBoxSizer* vsizer = new wxBoxSizer(wxVERTICAL); + auto* label = new wxStaticText(this, wxID_ANY, GUI::format_wxstr(_L("There are several IP addresses resolving to hostname %1%.\nPlease select one that should be used."), hostname)); + vsizer->Add(label, 0, wxEXPAND | wxTOP | wxLEFT | wxRIGHT, em); + + m_list->SetSingleStyle(wxLC_SINGLE_SEL); + m_list->AppendColumn(_(L("Address")), wxLIST_FORMAT_LEFT, 40 * em); + + for (size_t i = 0; i < ips.size(); i++) + m_list->InsertItem(i, boost::nowide::widen(ips[i].to_string())); + + m_list->Select(0); + + vsizer->Add(m_list, 1, wxEXPAND | wxALL, em); + + wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL); + button_sizer->Add(new wxButton(this, wxID_OK, "OK"), 0, wxALL, em); + button_sizer->Add(new wxButton(this, wxID_CANCEL, "Cancel"), 0, wxALL, em); + + vsizer->Add(button_sizer, 0, wxALIGN_CENTER); + SetSizerAndFit(vsizer); + + GUI::wxGetApp().UpdateDlgDarkUI(this); +} + +IPListDialog::~IPListDialog() +{ +} + +void IPListDialog::EndModal(int retCode) +{ + if (retCode == wxID_OK) { + m_selected_index = (size_t)m_list->GetFirstSelected(); + } + wxDialog::EndModal(retCode); +} } diff --git a/src/slic3r/GUI/BonjourDialog.hpp b/src/slic3r/GUI/BonjourDialog.hpp index def0838d7..8bfc076c4 100644 --- a/src/slic3r/GUI/BonjourDialog.hpp +++ b/src/slic3r/GUI/BonjourDialog.hpp @@ -1,9 +1,13 @@ #ifndef slic3r_BonjourDialog_hpp_ #define slic3r_BonjourDialog_hpp_ +#include #include +#include + #include +#include #include "libslic3r/PrintConfig.hpp" @@ -11,7 +15,7 @@ class wxListView; class wxStaticText; class wxTimer; class wxTimerEvent; - +class address; namespace Slic3r { @@ -41,12 +45,26 @@ private: unsigned timer_state; Slic3r::PrinterTechnology tech; - void on_reply(BonjourReplyEvent &); + virtual void on_reply(BonjourReplyEvent &); void on_timer(wxTimerEvent &); void on_timer_process(); }; +class IPListDialog : public wxDialog +{ +public: + IPListDialog(wxWindow* parent, const wxString& hostname, const std::vector& ips, size_t& selected_index); + IPListDialog(IPListDialog&&) = delete; + IPListDialog(const IPListDialog&) = delete; + IPListDialog& operator=(IPListDialog&&) = delete; + IPListDialog& operator=(const IPListDialog&) = delete; + ~IPListDialog(); + virtual void EndModal(int retCode) wxOVERRIDE; +private: + wxListView* m_list; + size_t& m_selected_index; +}; } diff --git a/src/slic3r/GUI/PhysicalPrinterDialog.cpp b/src/slic3r/GUI/PhysicalPrinterDialog.cpp index 015df7f46..b07b4a091 100644 --- a/src/slic3r/GUI/PhysicalPrinterDialog.cpp +++ b/src/slic3r/GUI/PhysicalPrinterDialog.cpp @@ -406,24 +406,36 @@ void PhysicalPrinterDialog::update(bool printer_change) // Only offer the host type selection for FFF, for SLA it's always the SL1 printer (at the moment) bool supports_multiple_printers = false; if (tech == ptFFF) { - update_host_type(printer_change); +update_host_type(printer_change); const auto opt = m_config->option>("host_type"); m_optgroup->show_field("host_type"); - if (opt->value == htPrusaLink) - { + + // hide PrusaConnect address + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_field->getWindow()); temp && temp->GetValue() == L"https://connect.prusa3d.com") { + temp->SetValue(wxString()); + } + } + if (opt->value == htPrusaLink) { // PrusaConnect does NOT allow http digest m_optgroup->show_field("printhost_authorization_type"); AuthorizationType auth_type = m_config->option>("printhost_authorization_type")->value; m_optgroup->show_field("printhost_apikey", auth_type == AuthorizationType::atKeyPassword); for (const char* opt_key : { "printhost_user", "printhost_password" }) - m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); + m_optgroup->show_field(opt_key, auth_type == AuthorizationType::atUserPassword); } else { m_optgroup->hide_field("printhost_authorization_type"); m_optgroup->show_field("printhost_apikey", true); for (const std::string& opt_key : std::vector{ "printhost_user", "printhost_password" }) m_optgroup->hide_field(opt_key); supports_multiple_printers = opt && opt->value == htRepetier; + if (opt->value == htPrusaConnect) { // automatically show default prusaconnect address + if (Field* printhost_field = m_optgroup->get_field("print_host"); printhost_field) { + if (wxTextCtrl* temp = dynamic_cast(printhost_field->getWindow()); temp && temp->GetValue().IsEmpty()) { + temp->SetValue(L"https://connect.prusa3d.com"); + } + } + } } - } else { m_optgroup->set_value("host_type", int(PrintHostType::htOctoPrint), false); @@ -453,30 +465,23 @@ void PhysicalPrinterDialog::update_host_type(bool printer_change) { if (m_config == nullptr) return; - bool all_presets_are_from_mk3_family = false; Field* ht = m_optgroup->get_field("host_type"); - wxArrayString types; - // Append localized enum_labels - assert(ht->m_opt.enum_labels.size() == ht->m_opt.enum_values.size()); - for (size_t i = 0; i < ht->m_opt.enum_labels.size(); i++) { - if (ht->m_opt.enum_values[i] == "prusalink" && !all_presets_are_from_mk3_family) - continue; - types.Add(_(ht->m_opt.enum_labels[i])); - } + int last_in_conf = m_config->option("host_type")->getInt(); // this is real position in last choice Choice* choice = dynamic_cast(ht); choice->set_values(types); - auto set_to_choice_and_config = [this, choice](PrintHostType type) { - choice->set_value(static_cast(type)); + int index_in_choice = (printer_change ? std::clamp(last_in_conf - ((int)ht->m_opt.enum_values.size() - (int)types.size()), 0, (int)ht->m_opt.enum_values.size() - 1) : last_in_conf); + choice->set_value(index_in_choice); + if ("prusalink" == ht->m_opt.enum_values.at(index_in_choice)) + m_config->set_key_value("host_type", new ConfigOptionEnum(htPrusaLink)); + else if ("prusaconnect" == ht->m_opt.enum_values.at(index_in_choice)) + m_config->set_key_value("host_type", new ConfigOptionEnum(htPrusaConnect)); + else { + int host_type = std::clamp(index_in_choice + ((int)ht->m_opt.enum_values.size() - (int)types.size()), 0, (int)ht->m_opt.enum_values.size() - 1); + PrintHostType type = static_cast(host_type); m_config->set_key_value("host_type", new ConfigOptionEnum(type)); - }; - if ((printer_change && all_presets_are_from_mk3_family) || all_presets_are_from_mk3_family) - set_to_choice_and_config(htPrusaLink); - else if ((printer_change && !all_presets_are_from_mk3_family) || (!all_presets_are_from_mk3_family && m_config->option>("host_type")->value == htPrusaLink)) - set_to_choice_and_config(htOctoPrint); - else - choice->set_value(m_config->option("host_type")->getInt()); + } } void PhysicalPrinterDialog::update_printers() diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index ec8e58304..43424d98b 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -10966,42 +10966,34 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn) upload_job.printhost->get_groups(groups); } - // orca merge todo - PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups); + // PrusaLink specific: Query the server for the list of file groups. + wxArrayString storage_paths; + wxArrayString storage_names; + { + wxBusyCursor wait; + try { + upload_job.printhost->get_storage(storage_paths, storage_names); + } catch (const Slic3r::IOError& ex) { + show_error(this, ex.what(), false); + return; + } + } + + PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups, storage_paths, storage_names); if (dlg.ShowModal() == wxID_OK) { upload_job.upload_data.upload_path = dlg.filename(); upload_job.upload_data.post_action = dlg.post_action(); upload_job.upload_data.group = dlg.group(); + upload_job.upload_data.storage = dlg.storage(); + + // Show "Is printer clean" dialog for PrusaConnect - Upload and print. + if (std::string(upload_job.printhost->get_name()) == "PrusaConnect" && upload_job.upload_data.post_action == PrintHostPostUploadAction::StartPrint) { + GUI::MessageDialog dlg(nullptr, _L("Is the printer ready? Is the print sheet in place, empty and clean?"), _L("Upload and Print"), wxOK | wxCANCEL); + if (dlg.ShowModal() != wxID_OK) + return; + } p->export_gcode(fs::path(), false, std::move(upload_job)); - - try { - json j; - switch (dlg.post_action()) { - case PrintHostPostUploadAction::None: - j["post_action"] = "Upload"; - break; - case PrintHostPostUploadAction::StartPrint: - j["post_action"] = "StartPrint"; - break; - case PrintHostPostUploadAction::StartSimulation: - j["post_action"] = "StartSimulation"; - break; - } - - PresetBundle *preset_bundle = wxGetApp().preset_bundle; - if (preset_bundle) { - j["gcode_printer_model"] = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle); - } - - if (physical_printer_config) { - j["printer_preset"] = physical_printer_config->opt_string("inherits"); - } - - NetworkAgent *agent = wxGetApp().getAgent(); - } catch (...) { - return; - } } } int Plater::send_gcode(int plate_idx, Export3mfProgressFn proFn) diff --git a/src/slic3r/GUI/PrintHostDialogs.cpp b/src/slic3r/GUI/PrintHostDialogs.cpp index ec93b7016..9dd9f115d 100644 --- a/src/slic3r/GUI/PrintHostDialogs.cpp +++ b/src/slic3r/GUI/PrintHostDialogs.cpp @@ -38,13 +38,14 @@ static const char *CONFIG_KEY_PATH = "printhost_path"; static const char *CONFIG_KEY_GROUP = "printhost_group"; static const char* CONFIG_KEY_STORAGE = "printhost_storage"; -PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage) +PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage_paths, const wxArrayString& storage_names) : MsgDialog(static_cast(wxGetApp().mainframe), _L("Send G-Code to printer host"), _L("Upload to Printer Host with the following filename:"), 0) // Set style = 0 to avoid default creation of the "OK" button. // All buttons will be added later in this constructor , txt_filename(new wxTextCtrl(this, wxID_ANY)) , combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr) - , combo_storage(storage.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage, wxCB_READONLY) : nullptr) + , combo_storage(storage_names.GetCount() > 1 ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, storage_names, wxCB_READONLY) : nullptr) , post_upload_action(PrintHostPostUploadAction::None) + , m_paths(storage_paths) { #ifdef __APPLE__ txt_filename->OSXDisableAllSmartSubstitutions(); @@ -70,18 +71,18 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo if (combo_storage != nullptr) { // PrusaLink specific: User needs to choose a storage - auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage:")); + auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ":"); content_sizer->Add(label_group); content_sizer->Add(combo_storage, 0, wxBOTTOM, 2 * VERT_SPACING); - combo_storage->SetValue(storage.front()); + combo_storage->SetValue(storage_names.front()); wxString recent_storage = from_u8(app_config->get("recent", CONFIG_KEY_STORAGE)); if (!recent_storage.empty()) combo_storage->SetValue(recent_storage); - } else if (storage.GetCount() == 1){ + } else if (storage_names.GetCount() == 1){ // PrusaLink specific: Show which storage has been detected. - auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage: ") + storage.front()); + auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage") + ": " + storage_names.front()); content_sizer->Add(label_group); - m_preselected_storage = storage.front(); + m_preselected_storage = storage_paths.front(); } @@ -196,7 +197,9 @@ std::string PrintHostSendDialog::storage() const { if (!combo_storage) return GUI::format("%1%", m_preselected_storage); - return boost::nowide::narrow(combo_storage->GetValue()); + if (combo_storage->GetSelection() < 0 || combo_storage->GetSelection() >= int(m_paths.size())) + return {}; + return boost::nowide::narrow(m_paths[combo_storage->GetSelection()]); } void PrintHostSendDialog::EndModal(int ret) @@ -226,8 +229,6 @@ void PrintHostSendDialog::EndModal(int ret) MsgDialog::EndModal(ret); } - - wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event); wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event); wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event); @@ -355,8 +356,6 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent) if (selected == wxNOT_FOUND) { return; } GUI::show_error(nullptr, job_list->GetTextValue(selected, COL_ERRORMSG)); }); - - wxGetApp().UpdateDlgDarkUI(this); } void PrintHostQueueDialog::append_job(const PrintHostJob &job) @@ -474,7 +473,7 @@ void PrintHostQueueDialog::on_error(Event &evt) set_state(evt.job_id, ST_ERROR); - auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.status.ToUTF8())).str()); + auto errormsg = format_wxstr("%1%\n%2%", _L("Error uploading to print host") + ":", evt.status); job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS); job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG); // Stashes the error message into a hidden column for later @@ -505,6 +504,7 @@ void PrintHostQueueDialog::on_cancel(Event &evt) void PrintHostQueueDialog::on_info(Event& evt) { + /* wxCHECK_RET(evt.job_id < (size_t)job_list->GetItemCount(), "Out of bounds access to job list"); if (evt.tag == L"resolve") { @@ -524,6 +524,7 @@ void PrintHostQueueDialog::on_info(Event& evt) } else if (evt.tag == L"set_complete_off") { wxGetApp().notification_manager()->set_upload_job_notification_comp_on_100(evt.job_id + 1, false); } + */ } void PrintHostQueueDialog::get_active_jobs(std::vector>& ret) @@ -565,8 +566,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector& vector) auto* app_config = wxGetApp().app_config; auto hasget = [app_config](const std::string& name, std::vector& vector)->bool { if (app_config->has(name)) { - vector.push_back(std::stoi(app_config->get(name))); - return true; + std::string val = app_config->get(name); + if (!val.empty() || val[0]!='\0') { + vector.push_back(std::stoi(val)); + return true; + } } return false; }; diff --git a/src/slic3r/GUI/PrintHostDialogs.hpp b/src/slic3r/GUI/PrintHostDialogs.hpp index 80e2a0f48..b3f550405 100644 --- a/src/slic3r/GUI/PrintHostDialogs.hpp +++ b/src/slic3r/GUI/PrintHostDialogs.hpp @@ -26,7 +26,7 @@ namespace GUI { class PrintHostSendDialog : public GUI::MsgDialog { public: - PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage); + PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage_paths, const wxArrayString& storage_names); boost::filesystem::path filename() const; PrintHostPostUploadAction post_action() const; std::string group() const; @@ -40,6 +40,7 @@ private: PrintHostPostUploadAction post_upload_action; wxString m_valid_suffix; wxString m_preselected_storage; + wxArrayString m_paths; }; diff --git a/src/slic3r/Utils/Bonjour.hpp b/src/slic3r/Utils/Bonjour.hpp index e61cd1833..50b71791f 100644 --- a/src/slic3r/Utils/Bonjour.hpp +++ b/src/slic3r/Utils/Bonjour.hpp @@ -7,12 +7,17 @@ #include #include #include -#include +#include +#include +#include +#include +#include namespace Slic3r { + struct BonjourReply { typedef std::unordered_map TxtData; @@ -40,7 +45,6 @@ struct BonjourReply std::ostream& operator<<(std::ostream &, const BonjourReply &); - /// Bonjour lookup performer class Bonjour : public std::enable_shared_from_this { private: @@ -49,6 +53,7 @@ public: typedef std::shared_ptr Ptr; typedef std::function ReplyFn; typedef std::function CompleteFn; + typedef std::function&)> ResolveFn; typedef std::set TxtKeys; Bonjour(std::string service); @@ -65,15 +70,217 @@ public: // ^ Note: By default there is 1 retry (meaning 1 broadcast is sent). // Timeout is per one retry, ie. total time spent listening = retries * timeout. // If retries > 1, then care needs to be taken as more than one reply from the same service may be received. + + // sets hostname queried by resolve() + Bonjour& set_hostname(const std::string& hostname); Bonjour& on_reply(ReplyFn fn); Bonjour& on_complete(CompleteFn fn); + Bonjour& on_resolve(ResolveFn fn); + // lookup all devices by given TxtKeys + // each correct reply is passed back in ReplyFn, finishes with CompleteFn Ptr lookup(); + // performs resolving of hostname into vector of ip adresses passed back by ResolveFn + // needs set_hostname and on_resolve to be called before. + Ptr resolve(); + // resolve on the current thread + void resolve_sync(); private: std::unique_ptr p; }; +struct BonjourRequest +{ + static const boost::asio::ip::address_v4 MCAST_IP4; + static const boost::asio::ip::address_v6 MCAST_IP6; + static const uint16_t MCAST_PORT; + + std::vector m_data; + + static boost::optional make_PTR(const std::string& service, const std::string& protocol); + static boost::optional make_A(const std::string& hostname); + static boost::optional make_AAAA(const std::string& hostname); +private: + BonjourRequest(std::vector&& data) : m_data(std::move(data)) {} +}; + + +class LookupSocket; +class ResolveSocket; + +// Session is created for each async_receive of socket. On receive, its handle_receive method is called (Thru io_service->post). +// ReplyFn is called if correct datagram was received. +class UdpSession +{ +public: + UdpSession(Bonjour::ReplyFn rfn); + virtual void handle_receive(const boost::system::error_code& error, size_t bytes) = 0; + std::vector buffer; + boost::asio::ip::udp::endpoint remote_endpoint; +protected: + Bonjour::ReplyFn replyfn; +}; +typedef std::shared_ptr SharedSession; +// Session for LookupSocket +class LookupSession : public UdpSession +{ +public: + LookupSession(const LookupSocket* sckt, Bonjour::ReplyFn rfn) : UdpSession(rfn), socket(sckt) {} + void handle_receive(const boost::system::error_code& error, size_t bytes) override; +protected: + // const pointer to socket to get needed data as txt_keys etc. + const LookupSocket* socket; +}; +// Session for ResolveSocket +class ResolveSession : public UdpSession +{ +public: + ResolveSession(const ResolveSocket* sckt, Bonjour::ReplyFn rfn) : UdpSession(rfn), socket(sckt) {} + void handle_receive(const boost::system::error_code& error, size_t bytes) override; +protected: + // const pointer to seocket to get hostname during handle_receive + const ResolveSocket* socket; +}; + +// Udp socket, starts receiving answers after first send() call until io_service is stopped. +class UdpSocket +{ +public: + // Two constructors: 1st is with interface which must be resolved before calling this + UdpSocket(Bonjour::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , const boost::asio::ip::address& interface_address + , std::shared_ptr< boost::asio::io_service > io_service); + + UdpSocket(Bonjour::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , std::shared_ptr< boost::asio::io_service > io_service); + + void send(); + void async_receive(); + void cancel() { socket.cancel(); } +protected: + void receive_handler(SharedSession session, const boost::system::error_code& error, size_t bytes); + virtual SharedSession create_session() const = 0; + + Bonjour::ReplyFn replyfn; + boost::asio::ip::address multicast_address; + boost::asio::ip::udp::socket socket; + boost::asio::ip::udp::endpoint mcast_endpoint; + std::shared_ptr< boost::asio::io_service > io_service; + std::vector requests; +}; + +class LookupSocket : public UdpSocket +{ +public: + LookupSocket(Bonjour::TxtKeys txt_keys + , std::string service + , std::string service_dn + , std::string protocol + , Bonjour::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , const boost::asio::ip::address& interface_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpSocket(replyfn, multicast_address, interface_address, io_service) + , txt_keys(txt_keys) + , service(service) + , service_dn(service_dn) + , protocol(protocol) + { + assert(!service.empty() && replyfn); + create_request(); + } + + LookupSocket(Bonjour::TxtKeys txt_keys + , std::string service + , std::string service_dn + , std::string protocol + , Bonjour::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpSocket(replyfn, multicast_address, io_service) + , txt_keys(txt_keys) + , service(service) + , service_dn(service_dn) + , protocol(protocol) + { + assert(!service.empty() && replyfn); + create_request(); + } + + const Bonjour::TxtKeys get_txt_keys() const { return txt_keys; } + const std::string get_service() const { return service; } + const std::string get_service_dn() const { return service_dn; } + +protected: + SharedSession create_session() const override; + void create_request() + { + requests.clear(); + // create PTR request + if (auto rqst = BonjourRequest::make_PTR(service, protocol); rqst) + requests.push_back(std::move(rqst.get())); + } + boost::optional request; + Bonjour::TxtKeys txt_keys; + std::string service; + std::string service_dn; + std::string protocol; +}; + +class ResolveSocket : public UdpSocket +{ +public: + ResolveSocket(const std::string& hostname + , Bonjour::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , const boost::asio::ip::address& interface_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpSocket(replyfn, multicast_address, interface_address, io_service) + , hostname(hostname) + + { + assert(!hostname.empty() && replyfn); + create_requests(); + } + + ResolveSocket(const std::string& hostname + , Bonjour::ReplyFn replyfn + , const boost::asio::ip::address& multicast_address + , std::shared_ptr< boost::asio::io_service > io_service) + : UdpSocket(replyfn, multicast_address, io_service) + , hostname(hostname) + + { + assert(!hostname.empty() && replyfn); + create_requests(); + } + + std::string get_hostname() const { return hostname; } +protected: + SharedSession create_session() const override; + void create_requests() + { + requests.clear(); + // BonjourRequest::make_A / AAAA is now implemented to add .local correctly after the hostname. + // If that is unsufficient, we need to change make_A / AAAA and pass full hostname. + std::string trimmed_hostname = hostname; + if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos) + trimmed_hostname = trimmed_hostname.substr(0, dot_pos); + if (auto rqst = BonjourRequest::make_A(trimmed_hostname); rqst) + requests.push_back(std::move(rqst.get())); + + trimmed_hostname = hostname; + if (size_t dot_pos = trimmed_hostname.find_first_of('.'); dot_pos != std::string::npos) + trimmed_hostname = trimmed_hostname.substr(0, dot_pos); + if (auto rqst = BonjourRequest::make_AAAA(trimmed_hostname); rqst) + requests.push_back(std::move(rqst.get())); + } + + std::string hostname; +}; } diff --git a/src/slic3r/Utils/OctoPrint.cpp b/src/slic3r/Utils/OctoPrint.cpp index 88be29292..1814a17a9 100644 --- a/src/slic3r/Utils/OctoPrint.cpp +++ b/src/slic3r/Utils/OctoPrint.cpp @@ -33,6 +33,38 @@ namespace Slic3r { namespace { #ifdef WIN32 +std::string get_host_from_url(const std::string& url_in) +{ + std::string url = url_in; + // add http:// if there is no scheme + size_t double_slash = url.find("//"); + if (double_slash == std::string::npos) + url = "http://" + url; + std::string out = url; + CURLU* hurl = curl_url(); + if (hurl) { + // Parse the input URL. + CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0); + if (rc == CURLUE_OK) { + // Replace the address. + char* host; + rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0); + if (rc == CURLUE_OK) { + out = host; + curl_free(host); + } + else + BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to get host form URL " << url; + } + else + BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to parse URL " << url; + curl_url_cleanup(hurl); + } + else + BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url"; + return out; +} + // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail. std::string substitute_host(const std::string& orig_addr, std::string sub_addr) { @@ -96,38 +128,6 @@ std::string substitute_host(const std::string& orig_addr, std::string sub_addr) return out; #endif } - -std::string get_host_from_url(const std::string& url_in) -{ - std::string url = url_in; - // add http:// if there is no scheme - size_t double_slash = url.find("//"); - if (double_slash == std::string::npos) - url = "http://" + url; - std::string out = url; - CURLU* hurl = curl_url(); - if (hurl) { - // Parse the input URL. - CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0); - if (rc == CURLUE_OK) { - // Replace the address. - char* host; - rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0); - if (rc == CURLUE_OK) { - out = host; - curl_free(host); - } - else - BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to get host form URL " << url; - } - else - BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to parse URL " << url; - curl_url_cleanup(hurl); - } - else - BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url"; - return out; -} #endif // WIN32 std::string escape_string(const std::string& unescaped) { @@ -170,7 +170,7 @@ const char* OctoPrint::get_name() const { return "OctoPrint"; } bool OctoPrint::test_with_resolved_ip(wxString &msg) const { // Since the request is performed synchronously here, - // it is ok to refer to `msg` from within the closure + // it is ok to refer to `msg` from within the closure const char* name = get_name(); bool res = true; // Msg contains ip string. @@ -179,7 +179,15 @@ bool OctoPrint::test_with_resolved_ip(wxString &msg) const BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; + std::string host = get_host_from_url(m_host); auto http = Http::get(url);//std::move(url)); + // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. + // And when creating Http object above, libcurl automatically includes "Host" header from address it got. + // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. + // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). + // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays. + // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + http.header("Host", host); set_auth(http); http .on_error([&](std::string body, std::string error, unsigned status) { @@ -203,7 +211,7 @@ bool OctoPrint::test_with_resolved_ip(wxString &msg) const const auto text = ptree.get_optional("text"); res = validate_version_text(text); if (!res) { - msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str()); + msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : name)); } } catch (const std::exception&) { @@ -228,7 +236,7 @@ bool OctoPrint::test(wxString& msg) const auto url = make_url("api/version"); BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; - + // Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself. auto http = Http::get(std::move(url)); set_auth(http); http.on_error([&](std::string body, std::string error, unsigned status) { @@ -252,7 +260,7 @@ bool OctoPrint::test(wxString& msg) const const auto text = ptree.get_optional("text"); res = validate_version_text(text); if (! res) { - msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str()); + msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : name)); } } catch (const std::exception &) { @@ -273,7 +281,6 @@ bool OctoPrint::test(wxString& msg) const return res; } - wxString OctoPrint::get_test_ok_msg () const { return _(L("Connection to OctoPrint works correctly.")); @@ -281,10 +288,10 @@ wxString OctoPrint::get_test_ok_msg () const wxString OctoPrint::get_test_failed_msg (wxString &msg) const { - return GUI::from_u8((boost::format("%s: %s\n\n%s") - % _utf8(L("Could not connect to OctoPrint")) - % std::string(msg.ToUTF8()) - % _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str()); + return GUI::format_wxstr("%s: %s\n\n%s" + , _L("Could not connect to OctoPrint") + , msg + , _L("Note: OctoPrint version at least 1.1.0 is required.")); } bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const @@ -300,7 +307,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro boost::asio::ip::address host_ip = boost::asio::ip::make_address(host, ec); if (!ec) { resolved_addr.push_back(host_ip); - } else if ( GUI::get_app_config()->get("allow_ip_resolve") == "1" && boost::algorithm::ends_with(host, ".local")){ + } else if ( GUI::get_app_config()->get_bool("allow_ip_resolve") && boost::algorithm::ends_with(host, ".local")){ Bonjour("octoprint") .set_hostname(host) .set_retries(5) // number of rounds of queries send @@ -340,7 +347,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro return true; } else { // There are multiple addresses - user needs to choose which to use. - size_t selected_index = resolved_addr.size(); + size_t selected_index = resolved_addr.size(); IPListDialog dialog(nullptr, boost::nowide::widen(m_host), resolved_addr, selected_index); if (dialog.ShowModal() == wxID_OK && selected_index < resolved_addr.size()) { return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr[selected_index]); @@ -379,7 +386,14 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr % upload_parent_path.string() % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"); + std::string host = get_host_from_url(m_host); auto http = Http::post(url);//std::move(url)); + // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. + // And when creating Http object above, libcurl automatically includes "Host" header from address it got. + // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. + // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). + // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + http.header("Host", host); set_auth(http); http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? @@ -397,7 +411,7 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr prorgess_fn(std::move(progress), cancel); if (cancel) { // Upload was canceled - BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled"; + BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled"; result = false; } }) @@ -428,7 +442,7 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p #ifdef WIN32 // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail. - if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || GUI::get_app_config()->get("allow_ip_resolve") != "1") + if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve")) #endif // _WIN32 { // If https is entered we assume signed ceritificate is being used @@ -458,6 +472,16 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p % (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false"); auto http = Http::post(std::move(url)); +#ifdef WIN32 + // "Host" header is necessary here. In the workaround above (two mDNS..) we have got IP address from test connection and subsituted it into "url" variable. + // And when creating Http object above, libcurl automatically includes "Host" header from address it got. + // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. + // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). + // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays. + // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + std::string host = get_host_from_url(m_host); + http.header("Host", host); +#endif // _WIN32 set_auth(http); http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false") .form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? @@ -474,7 +498,7 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p prorgess_fn(std::move(progress), cancel); if (cancel) { // Upload was canceled - BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled"; + BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled"; res = false; } }) @@ -513,11 +537,8 @@ std::string OctoPrint::make_url(const std::string &path) const } } -SL1Host::SL1Host(DynamicPrintConfig *config) : - OctoPrint(config), - m_authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value), - m_username(config->opt_string("printhost_user")), - m_password(config->opt_string("printhost_password")) +SL1Host::SL1Host(DynamicPrintConfig *config) + : PrusaLink(config) { } @@ -531,9 +552,7 @@ wxString SL1Host::get_test_ok_msg () const wxString SL1Host::get_test_failed_msg (wxString &msg) const { - return GUI::from_u8((boost::format("%s: %s") - % _utf8(L("Could not connect to Prusa SLA")) - % std::string(msg.ToUTF8())).str()); + return GUI::format_wxstr("%s: %s", _L("Could not connect to Prusa SLA"), msg); } bool SL1Host::validate_version_text(const boost::optional &version_text) const @@ -541,26 +560,10 @@ bool SL1Host::validate_version_text(const boost::optional &version_ return version_text ? boost::starts_with(*version_text, "Prusa SLA") : false; } -void SL1Host::set_auth(Http &http) const -{ - switch (m_authorization_type) { - case atKeyPassword: - http.header("X-Api-Key", get_apikey()); - break; - case atUserPassword: - http.auth_digest(m_username, m_password); - break; - } - - if (! get_cafile().empty()) { - http.ca_file(get_cafile()); - } -} - // PrusaLink PrusaLink::PrusaLink(DynamicPrintConfig* config, bool show_after_message) : OctoPrint(config), - m_authorization_type(dynamic_cast*>(config->option("printhost_authorization_type"))->value), + m_authorization_type(config->option>("printhost_authorization_type")->value), m_username(config->opt_string("printhost_user")), m_password(config->opt_string("printhost_password")), m_show_after_message(show_after_message) @@ -576,9 +579,7 @@ wxString PrusaLink::get_test_ok_msg() const wxString PrusaLink::get_test_failed_msg(wxString& msg) const { - return GUI::from_u8((boost::format("%s: %s") - % _utf8(L("Could not connect to PrusaLink")) - % std::string(msg.ToUTF8())).str()); + return GUI::format_wxstr("%s: %s", _L("Could not connect to PrusaLink"), msg); } bool PrusaLink::validate_version_text(const boost::optional& version_text) const @@ -664,7 +665,7 @@ bool PrusaLink::test(wxString& msg) const const auto text = ptree.get_optional("text"); res = validate_version_text(text); if (!res) { - msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str()); + msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint")); } } catch (const std::exception&) { @@ -685,7 +686,7 @@ bool PrusaLink::test(wxString& msg) const return res; } -bool PrusaLink::get_storage(wxArrayString& output) const +bool PrusaLink::get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const { const char* name = get_name(); @@ -693,17 +694,22 @@ bool PrusaLink::get_storage(wxArrayString& output) const auto url = make_url("api/v1/storage"); wxString error_msg; - struct StorageInfo{ + struct StorageInfo { + wxString path; wxString name; - bool read_only; - long long free_space; + bool read_only = false; + long long free_space = -1; }; std::vector storage; BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url; + wxString wlang = GUI::wxGetApp().current_language_code(); + std::string lang = GUI::format(wlang.SubString(0, 1)); + auto http = Http::get(std::move(url)); set_auth(http); + http.header("Accept-Language", lang); http.on_error([&](std::string body, std::string error, unsigned status) { BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting storage: %2%, HTTP %3%, body: `%4%`") % name % error % status % body; error_msg = L"\n\n" + boost::nowide::widen(error); @@ -716,7 +722,7 @@ bool PrusaLink::get_storage(wxArrayString& output) const res = true; }) - .on_complete([&, this](std::string body, unsigned) { + .on_complete([&](std::string body, unsigned) { BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body; try { @@ -731,14 +737,19 @@ bool PrusaLink::get_storage(wxArrayString& output) const } // each storage has own subtree of storage_list for (const auto& section : ptree.front().second) { + const auto name = section.second.get_optional("name"); const auto path = section.second.get_optional("path"); const auto space = section.second.get_optional("free_space"); const auto read_only = section.second.get_optional("read_only"); + const auto ro = section.second.get_optional("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro". const auto available = section.second.get_optional("available"); if (path && (!available || *available)) { StorageInfo si; - si.name = boost::nowide::widen(*path); - si.read_only = read_only ? *read_only : false; // If read_only is missing, assume it is NOT read only. + si.path = boost::nowide::widen(*path); + si.name = name ? boost::nowide::widen(*name) : wxString(); + // If read_only is missing, assume it is NOT read only. + // si.read_only = read_only ? *read_only : false; // version without "ro" + si.read_only = (read_only ? *read_only : (ro ? *ro : false)); si.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space. storage.emplace_back(std::move(si)); } @@ -756,19 +767,25 @@ bool PrusaLink::get_storage(wxArrayString& output) const .perform_sync(); for (const auto& si : storage) { - if (!si.read_only && si.free_space > 0) - output.push_back(si.name); + if (!si.read_only && si.free_space > 0) { + storage_path.push_back(si.path); + storage_name.push_back(si.name); + } } - if (res && output.empty()) - { + if (res && storage_path.empty()) { if (!storage.empty()) { // otherwise error_msg is already filled - error_msg = L"\n\n" + _L("Storages found:") + L" \n"; + error_msg = L"\n\n" + _L("Storages found") + L": \n"; for (const auto& si : storage) { - error_msg += si.name + L" : " + (si.read_only ? _L("read only") : _L("no free space")) + L"\n"; + error_msg += GUI::format_wxstr(si.read_only ? + // TRN %1% = storage path + _L("%1% : read only") : + // TRN %1% = storage path + _L("%1% : no free space"), si.path) + L"\n"; } } - std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%.%2%"), m_host, error_msg); + // TRN %1% = host + std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%."), m_host) + GUI::into_u8(error_msg); BOOST_LOG_TRIVIAL(error) << message; throw Slic3r::IOError(message); } @@ -787,7 +804,7 @@ bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) const auto url = make_url("api/version"); BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; - + // Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself. auto http = Http::get(std::move(url)); set_auth(http); http.on_error([&](std::string body, std::string error, unsigned status) { @@ -811,7 +828,7 @@ bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) const const auto text = ptree.get_optional("text"); res = validate_version_text(text); if (!res) { - msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str()); + msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint")); use_put = false; return; } @@ -860,7 +877,15 @@ bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_ BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url; + std::string host = get_host_from_url(m_host); auto http = Http::get(url);//std::move(url)); + // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. + // And when creating Http object above, libcurl automatically includes "Host" header from address it got. + // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. + // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). + // Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays. + // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + http.header("Host", host); set_auth(http); http .on_error([&](std::string body, std::string error, unsigned status) { @@ -884,7 +909,7 @@ bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_ const auto text = ptree.get_optional("text"); res = validate_version_text(text); if (!res) { - msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "OctoPrint")).str()); + msg = GUI::format_wxstr(_L("Mismatched type of print host: %s"), (text ? *text : "OctoPrint")); use_put = false; return; } @@ -968,12 +993,11 @@ bool PrusaLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p } std::string url; - bool res = true; std::string storage_path = (use_put ? "api/v1/files" : "api/files"); storage_path += (upload_data.storage.empty() ? "/local" : upload_data.storage); #ifdef WIN32 // Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail. - if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || GUI::get_app_config()->get("allow_ip_resolve") != "1") + if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve")) #endif // _WIN32 { // If https is entered we assume signed ceritificate is being used @@ -1015,13 +1039,20 @@ bool PrusaLink::put_inner(PrintHostUpload upload_data, std::string url, const st bool res = true; // Percent escape all filenames in on path and add it to the url. This is different from POST. url += "/" + escape_path_by_element(upload_data.upload_path); - Http http = Http::put(std::move(url)); +#ifdef WIN32 + // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. + // And when creating Http object above, libcurl automatically includes "Host" header from address it got. + // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. + // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). + // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + std::string host = get_host_from_url(m_host); + http.header("Host", host); +#endif // _WIN32 set_auth(http); // This is ugly, but works. There was an error at PrusaLink side that accepts any string at Print-After-Upload as true, thus False was also triggering print after upload. if (upload_data.post_action == PrintHostPostUploadAction::StartPrint) - http.header("Print-After-Upload", "True"); - + http.header("Print-After-Upload", "?1"); http.set_put_body(upload_data.source_path) .header("Content-Type", "text/x.gcode") .header("Overwrite", "?1") @@ -1057,7 +1088,17 @@ bool PrusaLink::post_inner(PrintHostUpload upload_data, std::string url, const s bool res = true; const auto upload_filename = upload_data.upload_path.filename(); const auto upload_parent_path = upload_data.upload_path.parent_path(); + Http http = Http::post(std::move(url)); +#ifdef WIN32 + // "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable. + // And when creating Http object above, libcurl automatically includes "Host" header from address it got. + // Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back. + // Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734). + // https://www.rfc-editor.org/rfc/rfc7230#section-5.4 + std::string host = get_host_from_url(m_host); + http.header("Host", host); +#endif // _WIN32 set_auth(http); set_http_post_header_args(http, upload_data.post_action); http.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ??? @@ -1128,14 +1169,11 @@ void PrusaConnect::set_http_post_header_args(Http& http, PrintHostPostUploadActi wxString PrusaConnect::get_test_ok_msg() const { - return _(L("Connection to PrusaConnect works correctly.")); + return _(L("Connection to Prusa Connect works correctly.")); } wxString PrusaConnect::get_test_failed_msg(wxString& msg) const { - return GUI::from_u8((boost::format("%s: %s") - % _utf8(L("Could not connect to PrusaConnect")) - % std::string(msg.ToUTF8())).str()); + return GUI::format_wxstr("%s: %s", _L("Could not connect to Prusa Connect"), msg); } - } diff --git a/src/slic3r/Utils/OctoPrint.hpp b/src/slic3r/Utils/OctoPrint.hpp index c2046dcf0..d9172f322 100644 --- a/src/slic3r/Utils/OctoPrint.hpp +++ b/src/slic3r/Utils/OctoPrint.hpp @@ -55,30 +55,6 @@ private: #endif }; -class SL1Host: public OctoPrint -{ -public: - SL1Host(DynamicPrintConfig *config); - ~SL1Host() override = default; - - const char* get_name() const override; - - wxString get_test_ok_msg() const override; - wxString get_test_failed_msg(wxString &msg) const override; - PrintHostPostUploadActions get_post_upload_actions() const override { return {}; } - -protected: - bool validate_version_text(const boost::optional &version_text) const override; - -private: - void set_auth(Http &http) const override; - - // Host authorization type. - AuthorizationType m_authorization_type; - // username and password for HTTP Digest Authentization (RFC RFC2617) - std::string m_username; - std::string m_password; -}; class PrusaLink : public OctoPrint { @@ -94,7 +70,7 @@ public: virtual PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; } // gets possible storage to be uploaded to. This allows different printer to have different storage. F.e. local vs sdcard vs usb. - bool get_storage(wxArrayString& /* storage */) const override; + bool get_storage(wxArrayString& storage_path, wxArrayString& storage_name) const override; protected: bool test(wxString& curl_msg) const override; bool validate_version_text(const boost::optional& version_text) const override; @@ -106,6 +82,12 @@ protected: bool upload_inner_with_resolved_ip(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn, const boost::asio::ip::address& resolved_addr) const override; #endif + // Host authorization type. + AuthorizationType m_authorization_type; + // username and password for HTTP Digest Authentization (RFC RFC2617) + std::string m_username; + std::string m_password; + private: bool test_with_method_check(wxString& curl_msg, bool& use_put) const; bool put_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const; @@ -113,11 +95,7 @@ private: #ifdef WIN32 bool test_with_resolved_ip_and_method_check(wxString& curl_msg, bool& use_put) const; #endif - // Host authorization type. - AuthorizationType m_authorization_type; - // username and password for HTTP Digest Authentization (RFC RFC2617) - std::string m_username; - std::string m_password; + bool m_show_after_message; #if 0 @@ -139,6 +117,22 @@ protected: void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override; }; +class SL1Host : public PrusaLink +{ +public: + SL1Host(DynamicPrintConfig* config); + ~SL1Host() override = default; + + const char* get_name() const override; + + wxString get_test_ok_msg() const override; + wxString get_test_failed_msg(wxString& msg) const override; + PrintHostPostUploadActions get_post_upload_actions() const override { return {}; } + +protected: + bool validate_version_text(const boost::optional& version_text) const override; +}; + } #endif diff --git a/src/slic3r/Utils/PrintHost.hpp b/src/slic3r/Utils/PrintHost.hpp index c39f86288..becaf138b 100644 --- a/src/slic3r/Utils/PrintHost.hpp +++ b/src/slic3r/Utils/PrintHost.hpp @@ -66,7 +66,7 @@ public: virtual bool get_printers(wxArrayString & /* printers */) const { return false; } // Support for PrusaLink uploading to different storage. Not supported by other print hosts. // Returns false if not supported or fail. - virtual bool get_storage(wxArrayString& /* storage */) const { return false; } + virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; } static PrintHost* get_print_host(DynamicPrintConfig *config);