fix errors after cherry picking commits

This commit is contained in:
SoftFever 2023-08-27 22:44:37 +08:00
parent 85251de418
commit 9bab2e2efa
12 changed files with 522 additions and 245 deletions

View file

@ -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");
}

View file

@ -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 &section, const std::string &key, const std::string &value)
{
#ifndef NDEBUG

View file

@ -5,6 +5,8 @@
#include <set>
#include <mutex>
#include <boost/nowide/convert.hpp>
#include <wx/sizer.h>
#include <wx/button.h>
#include <wx/listctrl.h>
@ -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, int>(wxColour(0, 137, 123), StateColor::Pressed), std::pair<wxColour, int>(wxColour(38, 166, 154), StateColor::Hovered),
std::pair<wxColour, int>(wxColour(0, 150, 136), StateColor::Normal));
StateColor btn_bg_white(std::pair<wxColour, int>(wxColour(206, 206, 206), StateColor::Pressed), std::pair<wxColour, int>(wxColour(238, 238, 238), StateColor::Hovered),
std::pair<wxColour, int>(*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<boost::asio::ip::address>& 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);
}
}

View file

@ -1,9 +1,13 @@
#ifndef slic3r_BonjourDialog_hpp_
#define slic3r_BonjourDialog_hpp_
#include <cstddef>
#include <memory>
#include <boost/asio/ip/address.hpp>
#include <wx/dialog.h>
#include <wx/string.h>
#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<boost::asio::ip::address>& 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;
};
}

View file

@ -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<ConfigOptionEnum<PrintHostType>>("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<wxTextCtrl*>(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<ConfigOptionEnum<AuthorizationType>>("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<std::string>{ "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<wxTextCtrl*>(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<Choice*>(ht);
choice->set_values(types);
auto set_to_choice_and_config = [this, choice](PrintHostType type) {
choice->set_value(static_cast<int>(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<PrintHostType>(htPrusaLink));
else if ("prusaconnect" == ht->m_opt.enum_values.at(index_in_choice))
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(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<PrintHostType>(host_type);
m_config->set_key_value("host_type", new ConfigOptionEnum<PrintHostType>(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<ConfigOptionEnum<PrintHostType>>("host_type")->value == htPrusaLink))
set_to_choice_and_config(htOctoPrint);
else
choice->set_value(m_config->option("host_type")->getInt());
}
}
void PhysicalPrinterDialog::update_printers()

View file

@ -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)

View file

@ -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<wxWindow*>(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<std::pair<std::string, std::string>>& ret)
@ -565,8 +566,11 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector)
auto* app_config = wxGetApp().app_config;
auto hasget = [app_config](const std::string& name, std::vector<int>& 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;
};

View file

@ -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;
};

View file

@ -7,12 +7,17 @@
#include <set>
#include <unordered_map>
#include <functional>
#include <boost/asio/ip/address.hpp>
#include <boost/asio.hpp>
#include <boost/asio/ip/address.hpp>
#include <boost/optional.hpp>
#include <boost/system/error_code.hpp>
#include <boost/shared_ptr.hpp>
namespace Slic3r {
struct BonjourReply
{
typedef std::unordered_map<std::string, std::string> 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<Bonjour> {
private:
@ -49,6 +53,7 @@ public:
typedef std::shared_ptr<Bonjour> Ptr;
typedef std::function<void(BonjourReply &&)> ReplyFn;
typedef std::function<void()> CompleteFn;
typedef std::function<void(const std::vector<BonjourReply>&)> ResolveFn;
typedef std::set<std::string> 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<priv> 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<char> m_data;
static boost::optional<BonjourRequest> make_PTR(const std::string& service, const std::string& protocol);
static boost::optional<BonjourRequest> make_A(const std::string& hostname);
static boost::optional<BonjourRequest> make_AAAA(const std::string& hostname);
private:
BonjourRequest(std::vector<char>&& 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<char> buffer;
boost::asio::ip::udp::endpoint remote_endpoint;
protected:
Bonjour::ReplyFn replyfn;
};
typedef std::shared_ptr<UdpSession> 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<BonjourRequest> 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<BonjourRequest> 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;
};
}

View file

@ -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<std::string>("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<std::string>("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<const ConfigOptionEnum<AuthorizationType>*>(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<std::string> &version_text) const
@ -541,26 +560,10 @@ bool SL1Host::validate_version_text(const boost::optional<std::string> &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<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
m_authorization_type(config->option<ConfigOptionEnum<AuthorizationType>>("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<std::string>& version_text) const
@ -664,7 +665,7 @@ bool PrusaLink::test(wxString& msg) const
const auto text = ptree.get_optional<std::string>("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<StorageInfo> 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<std::string>("name");
const auto path = section.second.get_optional<std::string>("path");
const auto space = section.second.get_optional<std::string>("free_space");
const auto read_only = section.second.get_optional<bool>("read_only");
const auto ro = section.second.get_optional<bool>("ro"); // In PrusaLink 0.7.0RC2 "read_only" value is stored under "ro".
const auto available = section.second.get_optional<bool>("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<std::string>("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<std::string>("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);
}
}

View file

@ -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<std::string> &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<std::string>& 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<std::string>& version_text) const override;
};
}
#endif

View file

@ -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);