mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-07-08 23:46:24 -06:00
Upload changes
PrusaLink: Use PUT or POST based on information read during test connection (upload-by-put). If put - do additional GET for storage_list and let user to choose where to upload or show name if only 1 is possible. Allow PrusaLink for MK2.5 and MK2.5S. PrusaConnect: New host type PrusaConnect inherited from PrusaLink class with filled host address, disabled http diggest. After upload read header information - status message and pass it to notification and Printhost upload dialog via events, this message can be shown as warning notification and is recieved in localized lang. Pass accept-language shortcut in upload header. 3 option to upload. (upload, to queue, to print) Upload Notification: Showing status text, changes in text, not showing close button, Completed state on special call (not 100%) and other design changes. Right panel: Open URL button.
This commit is contained in:
parent
13deee3c8f
commit
9df97e004d
20 changed files with 1530 additions and 204 deletions
|
@ -74,6 +74,7 @@ CONFIG_OPTION_ENUM_DEFINE_STATIC_MAPS(PrinterTechnology)
|
||||||
|
|
||||||
static t_config_enum_values s_keys_map_PrintHostType {
|
static t_config_enum_values s_keys_map_PrintHostType {
|
||||||
{ "prusalink", htPrusaLink },
|
{ "prusalink", htPrusaLink },
|
||||||
|
{ "prusaconnect", htPrusaConnect },
|
||||||
{ "octoprint", htOctoPrint },
|
{ "octoprint", htOctoPrint },
|
||||||
{ "duet", htDuet },
|
{ "duet", htDuet },
|
||||||
{ "flashair", htFlashAir },
|
{ "flashair", htFlashAir },
|
||||||
|
@ -2403,6 +2404,7 @@ void PrintConfigDef::init_fff_params()
|
||||||
"the kind of the host.");
|
"the kind of the host.");
|
||||||
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
|
def->enum_keys_map = &ConfigOptionEnum<PrintHostType>::get_enum_values();
|
||||||
def->enum_values.push_back("prusalink");
|
def->enum_values.push_back("prusalink");
|
||||||
|
def->enum_values.push_back("prusaconnect");
|
||||||
def->enum_values.push_back("octoprint");
|
def->enum_values.push_back("octoprint");
|
||||||
def->enum_values.push_back("duet");
|
def->enum_values.push_back("duet");
|
||||||
def->enum_values.push_back("flashair");
|
def->enum_values.push_back("flashair");
|
||||||
|
@ -2410,6 +2412,7 @@ void PrintConfigDef::init_fff_params()
|
||||||
def->enum_values.push_back("repetier");
|
def->enum_values.push_back("repetier");
|
||||||
def->enum_values.push_back("mks");
|
def->enum_values.push_back("mks");
|
||||||
def->enum_labels.push_back("PrusaLink");
|
def->enum_labels.push_back("PrusaLink");
|
||||||
|
def->enum_labels.push_back("PrusaConnect");
|
||||||
def->enum_labels.push_back("Octo/Klipper");
|
def->enum_labels.push_back("Octo/Klipper");
|
||||||
def->enum_labels.push_back("Duet");
|
def->enum_labels.push_back("Duet");
|
||||||
def->enum_labels.push_back("FlashAir");
|
def->enum_labels.push_back("FlashAir");
|
||||||
|
|
|
@ -44,7 +44,7 @@ enum class FuzzySkinType {
|
||||||
};
|
};
|
||||||
|
|
||||||
enum PrintHostType {
|
enum PrintHostType {
|
||||||
htPrusaLink, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
|
htPrusaLink, htPrusaConnect, htOctoPrint, htDuet, htFlashAir, htAstroBox, htRepetier, htMKS
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AuthorizationType {
|
enum AuthorizationType {
|
||||||
|
|
|
@ -10966,6 +10966,7 @@ void Plater::send_gcode_legacy(int plate_idx, Export3mfProgressFn proFn)
|
||||||
upload_job.printhost->get_groups(groups);
|
upload_job.printhost->get_groups(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// orca merge todo
|
||||||
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups);
|
PrintHostSendDialog dlg(default_output_file, upload_job.printhost->get_post_upload_actions(), groups);
|
||||||
if (dlg.ShowModal() == wxID_OK) {
|
if (dlg.ShowModal() == wxID_OK) {
|
||||||
upload_job.upload_data.upload_path = dlg.filename();
|
upload_job.upload_data.upload_path = dlg.filename();
|
||||||
|
|
|
@ -27,6 +27,7 @@
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
#include "NotificationManager.hpp"
|
#include "NotificationManager.hpp"
|
||||||
#include "ExtraRenderers.hpp"
|
#include "ExtraRenderers.hpp"
|
||||||
|
#include "format.hpp"
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
|
|
||||||
|
@ -35,11 +36,14 @@ namespace GUI {
|
||||||
|
|
||||||
static const char *CONFIG_KEY_PATH = "printhost_path";
|
static const char *CONFIG_KEY_PATH = "printhost_path";
|
||||||
static const char *CONFIG_KEY_GROUP = "printhost_group";
|
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)
|
PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUploadActions post_actions, const wxArrayString &groups, const wxArrayString& storage)
|
||||||
: MsgDialog(static_cast<wxWindow*>(wxGetApp().mainframe), _L("Send to print"), _L("Upload to Printer Host with the following filename:"),0)
|
: 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))
|
, txt_filename(new wxTextCtrl(this, wxID_ANY))
|
||||||
, combo_groups(!groups.IsEmpty() ? new wxComboBox(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, groups, wxCB_READONLY) : nullptr)
|
, 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)
|
||||||
, post_upload_action(PrintHostPostUploadAction::None)
|
, post_upload_action(PrintHostPostUploadAction::None)
|
||||||
{
|
{
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
@ -50,9 +54,8 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
|
auto *label_dir_hint = new wxStaticText(this, wxID_ANY, _L("Use forward slashes ( / ) as a directory separator if needed."));
|
||||||
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
|
label_dir_hint->Wrap(CONTENT_WIDTH * wxGetApp().em_unit());
|
||||||
|
|
||||||
content_sizer->Add(txt_filename, 0, wxEXPAND | wxALL, FromDIP(10));
|
content_sizer->Add(txt_filename, 0, wxEXPAND);
|
||||||
content_sizer->Add(FromDIP(10), FromDIP(10), 0, 0);
|
content_sizer->Add(label_dir_hint);
|
||||||
content_sizer->Add(label_dir_hint, 0, 0, FromDIP(10));
|
|
||||||
content_sizer->AddSpacer(VERT_SPACING);
|
content_sizer->AddSpacer(VERT_SPACING);
|
||||||
|
|
||||||
if (combo_groups != nullptr) {
|
if (combo_groups != nullptr) {
|
||||||
|
@ -65,6 +68,23 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
combo_groups->SetValue(recent_group);
|
combo_groups->SetValue(recent_group);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (combo_storage != nullptr) {
|
||||||
|
// PrusaLink specific: User needs to choose a 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());
|
||||||
|
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){
|
||||||
|
// PrusaLink specific: Show which storage has been detected.
|
||||||
|
auto* label_group = new wxStaticText(this, wxID_ANY, _L("Upload to storage: ") + storage.front());
|
||||||
|
content_sizer->Add(label_group);
|
||||||
|
m_preselected_storage = storage.front();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
|
wxString recent_path = from_u8(app_config->get("recent", CONFIG_KEY_PATH));
|
||||||
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
|
if (recent_path.Length() > 0 && recent_path[recent_path.Length() - 1] != '/') {
|
||||||
recent_path += '/';
|
recent_path += '/';
|
||||||
|
@ -75,9 +95,9 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
const auto stem_len = stem.Length();
|
const auto stem_len = stem.Length();
|
||||||
|
|
||||||
txt_filename->SetValue(recent_path);
|
txt_filename->SetValue(recent_path);
|
||||||
txt_filename->SetFocus();
|
|
||||||
|
|
||||||
m_valid_suffix = recent_path.substr(recent_path.find_last_of('.'));
|
if (size_t extension_start = recent_path.find_last_of('.'); extension_start != std::string::npos)
|
||||||
|
m_valid_suffix = recent_path.substr(extension_start);
|
||||||
// .gcode suffix control
|
// .gcode suffix control
|
||||||
auto validate_path = [this](const wxString &path) -> bool {
|
auto validate_path = [this](const wxString &path) -> bool {
|
||||||
if (! path.Lower().EndsWith(m_valid_suffix.Lower())) {
|
if (! path.Lower().EndsWith(m_valid_suffix.Lower())) {
|
||||||
|
@ -88,13 +108,24 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto* btn_upload = add_button(wxID_YES, false, _L("Upload"));
|
auto* btn_ok = add_button(wxID_OK, true, _L("Upload"));
|
||||||
btn_upload->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
btn_ok->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||||
if (validate_path(txt_filename->GetValue())) {
|
if (validate_path(txt_filename->GetValue())) {
|
||||||
post_upload_action = PrintHostPostUploadAction::None;
|
post_upload_action = PrintHostPostUploadAction::None;
|
||||||
EndDialog(wxID_OK);
|
EndDialog(wxID_OK);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
txt_filename->SetFocus();
|
||||||
|
|
||||||
|
if (post_actions.has(PrintHostPostUploadAction::QueuePrint)) {
|
||||||
|
auto* btn_print = add_button(wxID_ADD, false, _L("Upload to Queue"));
|
||||||
|
btn_print->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||||
|
if (validate_path(txt_filename->GetValue())) {
|
||||||
|
post_upload_action = PrintHostPostUploadAction::QueuePrint;
|
||||||
|
EndDialog(wxID_OK);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (post_actions.has(PrintHostPostUploadAction::StartPrint)) {
|
if (post_actions.has(PrintHostPostUploadAction::StartPrint)) {
|
||||||
auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print"));
|
auto* btn_print = add_button(wxID_YES, false, _L("Upload and Print"));
|
||||||
|
@ -108,7 +139,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
|
|
||||||
if (post_actions.has(PrintHostPostUploadAction::StartSimulation)) {
|
if (post_actions.has(PrintHostPostUploadAction::StartSimulation)) {
|
||||||
// Using wxID_MORE as a button identifier to be different from the other buttons, wxID_MORE has no other meaning here.
|
// Using wxID_MORE as a button identifier to be different from the other buttons, wxID_MORE has no other meaning here.
|
||||||
auto* btn_simulate = add_button(wxID_MORE, false, _L("Simulate"));
|
auto* btn_simulate = add_button(wxID_MORE, false, _L("Upload and Simulate"));
|
||||||
btn_simulate->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
btn_simulate->Bind(wxEVT_BUTTON, [this, validate_path](wxCommandEvent&) {
|
||||||
if (validate_path(txt_filename->GetValue())) {
|
if (validate_path(txt_filename->GetValue())) {
|
||||||
post_upload_action = PrintHostPostUploadAction::StartSimulation;
|
post_upload_action = PrintHostPostUploadAction::StartSimulation;
|
||||||
|
@ -117,7 +148,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
add_button(wxID_CANCEL,false, L("Cancel"));
|
add_button(wxID_CANCEL);
|
||||||
finalize();
|
finalize();
|
||||||
|
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
|
@ -135,6 +166,7 @@ PrintHostSendDialog::PrintHostSendDialog(const fs::path &path, PrintHostPostUplo
|
||||||
// Another similar case where the function only works with EVT_SHOW + CallAfter,
|
// Another similar case where the function only works with EVT_SHOW + CallAfter,
|
||||||
// this time on Mac.
|
// this time on Mac.
|
||||||
CallAfter([=]() {
|
CallAfter([=]() {
|
||||||
|
txt_filename->SetInsertionPoint(0);
|
||||||
txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
|
txt_filename->SetSelection(recent_path_len, recent_path_len + stem_len);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -160,6 +192,13 @@ std::string PrintHostSendDialog::group() const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string PrintHostSendDialog::storage() const
|
||||||
|
{
|
||||||
|
if (!combo_storage)
|
||||||
|
return GUI::format("%1%", m_preselected_storage);
|
||||||
|
return boost::nowide::narrow(combo_storage->GetValue());
|
||||||
|
}
|
||||||
|
|
||||||
void PrintHostSendDialog::EndModal(int ret)
|
void PrintHostSendDialog::EndModal(int ret)
|
||||||
{
|
{
|
||||||
if (ret == wxID_OK) {
|
if (ret == wxID_OK) {
|
||||||
|
@ -178,6 +217,10 @@ void PrintHostSendDialog::EndModal(int ret)
|
||||||
wxString group = combo_groups->GetValue();
|
wxString group = combo_groups->GetValue();
|
||||||
app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group));
|
app_config->set("recent", CONFIG_KEY_GROUP, into_u8(group));
|
||||||
}
|
}
|
||||||
|
if (combo_storage != nullptr) {
|
||||||
|
wxString storage = combo_storage->GetValue();
|
||||||
|
app_config->set("recent", CONFIG_KEY_STORAGE, into_u8(storage));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MsgDialog::EndModal(ret);
|
MsgDialog::EndModal(ret);
|
||||||
|
@ -188,6 +231,7 @@ void PrintHostSendDialog::EndModal(int ret)
|
||||||
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
wxDEFINE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
||||||
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
wxDEFINE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
||||||
wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
wxDEFINE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
||||||
|
wxDEFINE_EVENT(EVT_PRINTHOST_INFO, PrintHostQueueDialog::Event);
|
||||||
|
|
||||||
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
|
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id)
|
||||||
: wxEvent(winid, eventType)
|
: wxEvent(winid, eventType)
|
||||||
|
@ -203,7 +247,14 @@ PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_
|
||||||
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
|
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString error)
|
||||||
: wxEvent(winid, eventType)
|
: wxEvent(winid, eventType)
|
||||||
, job_id(job_id)
|
, job_id(job_id)
|
||||||
, error(std::move(error))
|
, status(std::move(error))
|
||||||
|
{}
|
||||||
|
|
||||||
|
PrintHostQueueDialog::Event::Event(wxEventType eventType, int winid, size_t job_id, wxString tag, wxString status)
|
||||||
|
: wxEvent(winid, eventType)
|
||||||
|
, job_id(job_id)
|
||||||
|
, tag(std::move(tag))
|
||||||
|
, status(std::move(status))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
wxEvent *PrintHostQueueDialog::Event::Clone() const
|
wxEvent *PrintHostQueueDialog::Event::Clone() const
|
||||||
|
@ -216,16 +267,17 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
|
||||||
, on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
|
, on_progress_evt(this, EVT_PRINTHOST_PROGRESS, &PrintHostQueueDialog::on_progress, this)
|
||||||
, on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
|
, on_error_evt(this, EVT_PRINTHOST_ERROR, &PrintHostQueueDialog::on_error, this)
|
||||||
, on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this)
|
, on_cancel_evt(this, EVT_PRINTHOST_CANCEL, &PrintHostQueueDialog::on_cancel, this)
|
||||||
|
, on_info_evt(this, EVT_PRINTHOST_INFO, &PrintHostQueueDialog::on_info, this)
|
||||||
{
|
{
|
||||||
const auto em = GetTextExtent("m").x;
|
const auto em = GetTextExtent("m").x;
|
||||||
|
|
||||||
auto *topsizer = new wxBoxSizer(wxVERTICAL);
|
auto *topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
|
||||||
std::vector<int> widths;
|
std::vector<int> widths;
|
||||||
widths.reserve(6);
|
widths.reserve(7);
|
||||||
if (!load_user_data(UDT_COLS, widths)) {
|
if (!load_user_data(UDT_COLS, widths)) {
|
||||||
widths.clear();
|
widths.clear();
|
||||||
for (size_t i = 0; i < 6; i++)
|
for (size_t i = 0; i < 7; i++)
|
||||||
widths.push_back(-1);
|
widths.push_back(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,9 +298,10 @@ PrintHostQueueDialog::PrintHostQueueDialog(wxWindow *parent)
|
||||||
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
job_list->AppendProgressColumn(_L("Progress"), wxDATAVIEW_CELL_INERT, widths[1], wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE);
|
||||||
append_text_column(_L("Status"),widths[2]);
|
append_text_column(_L("Status"),widths[2]);
|
||||||
append_text_column(_L("Host"), widths[3]);
|
append_text_column(_L("Host"), widths[3]);
|
||||||
append_text_column(_CTX_utf8(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
|
append_text_column(_CTX(L_CONTEXT("Size", "OfFile"), "OfFile"), widths[4]);
|
||||||
append_text_column(_L("Filename"), widths[5]);
|
append_text_column(_L("Filename"), widths[5]);
|
||||||
append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
|
append_text_column(_L("Message"), widths[6]);
|
||||||
|
//append_text_column(_L("Error Message"), -1, wxALIGN_CENTER, wxDATAVIEW_COL_HIDDEN);
|
||||||
|
|
||||||
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
|
auto *btnsizer = new wxBoxSizer(wxHORIZONTAL);
|
||||||
btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));
|
btn_cancel = new wxButton(this, wxID_DELETE, _L("Cancel selected"));
|
||||||
|
@ -325,7 +378,7 @@ void PrintHostQueueDialog::append_job(const PrintHostJob &job)
|
||||||
} else
|
} else
|
||||||
stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB";
|
stream << std::fixed << std::setprecision(2) << ((float)size_i / 1024 / 1024) << "MB";
|
||||||
fields.push_back(wxVariant(stream.str()));
|
fields.push_back(wxVariant(stream.str()));
|
||||||
fields.push_back(wxVariant(job.upload_data.upload_path.string()));
|
fields.push_back(wxVariant(from_path(job.upload_data.upload_path)));
|
||||||
fields.push_back(wxVariant(""));
|
fields.push_back(wxVariant(""));
|
||||||
job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
|
job_list->AppendItem(fields, static_cast<wxUIntPtr>(ST_NEW));
|
||||||
// Both strings are UTF-8 encoded.
|
// Both strings are UTF-8 encoded.
|
||||||
|
@ -421,7 +474,7 @@ void PrintHostQueueDialog::on_error(Event &evt)
|
||||||
|
|
||||||
set_state(evt.job_id, ST_ERROR);
|
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.error.ToUTF8())).str());
|
auto errormsg = from_u8((boost::format("%1%\n%2%") % _utf8(L("Error uploading to print host:")) % std::string(evt.status.ToUTF8())).str());
|
||||||
job_list->SetValue(wxVariant(0), evt.job_id, COL_PROGRESS);
|
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
|
job_list->SetValue(wxVariant(errormsg), evt.job_id, COL_ERRORMSG); // Stashes the error message into a hidden column for later
|
||||||
|
|
||||||
|
@ -450,6 +503,29 @@ void PrintHostQueueDialog::on_cancel(Event &evt)
|
||||||
wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()));
|
wxGetApp().notification_manager()->upload_job_notification_show_canceled(evt.job_id + 1, boost::nowide::narrow(nm.GetString()), boost::nowide::narrow(hst.GetString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
wxVariant hst(evt.status);
|
||||||
|
job_list->SetValue(hst, evt.job_id, COL_HOST);
|
||||||
|
wxGetApp().notification_manager()->set_upload_job_notification_host(evt.job_id + 1, boost::nowide::narrow(evt.status));
|
||||||
|
} else if (evt.tag == L"complete") {
|
||||||
|
wxVariant hst(evt.status);
|
||||||
|
job_list->SetValue(hst, evt.job_id, COL_ERRORMSG);
|
||||||
|
wxGetApp().notification_manager()->set_upload_job_notification_completed(evt.job_id + 1);
|
||||||
|
wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, boost::nowide::narrow(evt.status));
|
||||||
|
} else if(evt.tag == L"complete_with_warning"){
|
||||||
|
wxVariant hst(evt.status);
|
||||||
|
job_list->SetValue(hst, evt.job_id, COL_ERRORMSG);
|
||||||
|
wxGetApp().notification_manager()->set_upload_job_notification_completed_with_warning(evt.job_id + 1);
|
||||||
|
wxGetApp().notification_manager()->set_upload_job_notification_status(evt.job_id + 1, boost::nowide::narrow(evt.status));
|
||||||
|
} 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)
|
void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, std::string>>& ret)
|
||||||
{
|
{
|
||||||
int ic = job_list->GetItemCount();
|
int ic = job_list->GetItemCount();
|
||||||
|
@ -461,7 +537,6 @@ void PrintHostQueueDialog::get_active_jobs(std::vector<std::pair<std::string, st
|
||||||
if(st == JobState::ST_NEW || st == JobState::ST_PROGRESS)
|
if(st == JobState::ST_NEW || st == JobState::ST_PROGRESS)
|
||||||
ret.emplace_back(upload_names[i]);
|
ret.emplace_back(upload_names[i]);
|
||||||
}
|
}
|
||||||
//job_list->data
|
|
||||||
}
|
}
|
||||||
void PrintHostQueueDialog::save_user_data(int udt)
|
void PrintHostQueueDialog::save_user_data(int udt)
|
||||||
{
|
{
|
||||||
|
@ -510,7 +585,7 @@ bool PrintHostQueueDialog::load_user_data(int udt, std::vector<int>& vector)
|
||||||
}
|
}
|
||||||
if (udt & UserDataType::UDT_COLS)
|
if (udt & UserDataType::UDT_COLS)
|
||||||
{
|
{
|
||||||
for (size_t i = 0; i < 6; i++)
|
for (size_t i = 0; i < 7; i++)
|
||||||
{
|
{
|
||||||
if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector))
|
if (!hasget("print_host_queue_dialog_column_" + std::to_string(i), vector))
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -26,17 +26,20 @@ namespace GUI {
|
||||||
class PrintHostSendDialog : public GUI::MsgDialog
|
class PrintHostSendDialog : public GUI::MsgDialog
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups);
|
PrintHostSendDialog(const boost::filesystem::path &path, PrintHostPostUploadActions post_actions, const wxArrayString& groups, const wxArrayString& storage);
|
||||||
boost::filesystem::path filename() const;
|
boost::filesystem::path filename() const;
|
||||||
PrintHostPostUploadAction post_action() const;
|
PrintHostPostUploadAction post_action() const;
|
||||||
std::string group() const;
|
std::string group() const;
|
||||||
|
std::string storage() const;
|
||||||
|
|
||||||
virtual void EndModal(int ret) override;
|
virtual void EndModal(int ret) override;
|
||||||
private:
|
private:
|
||||||
wxTextCtrl *txt_filename;
|
wxTextCtrl *txt_filename;
|
||||||
wxComboBox *combo_groups;
|
wxComboBox *combo_groups;
|
||||||
|
wxComboBox* combo_storage;
|
||||||
PrintHostPostUploadAction post_upload_action;
|
PrintHostPostUploadAction post_upload_action;
|
||||||
wxString m_valid_suffix;
|
wxString m_valid_suffix;
|
||||||
|
wxString m_preselected_storage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -48,11 +51,13 @@ public:
|
||||||
public:
|
public:
|
||||||
size_t job_id;
|
size_t job_id;
|
||||||
int progress = 0; // in percent
|
int progress = 0; // in percent
|
||||||
wxString error;
|
wxString tag;
|
||||||
|
wxString status;
|
||||||
|
|
||||||
Event(wxEventType eventType, int winid, size_t job_id);
|
Event(wxEventType eventType, int winid, size_t job_id);
|
||||||
Event(wxEventType eventType, int winid, size_t job_id, int progress);
|
Event(wxEventType eventType, int winid, size_t job_id, int progress);
|
||||||
Event(wxEventType eventType, int winid, size_t job_id, wxString error);
|
Event(wxEventType eventType, int winid, size_t job_id, wxString error);
|
||||||
|
Event(wxEventType eventType, int winid, size_t job_id, wxString tag, wxString status);
|
||||||
|
|
||||||
virtual wxEvent *Clone() const;
|
virtual wxEvent *Clone() const;
|
||||||
};
|
};
|
||||||
|
@ -108,6 +113,7 @@ private:
|
||||||
EventGuard on_progress_evt;
|
EventGuard on_progress_evt;
|
||||||
EventGuard on_error_evt;
|
EventGuard on_error_evt;
|
||||||
EventGuard on_cancel_evt;
|
EventGuard on_cancel_evt;
|
||||||
|
EventGuard on_info_evt;
|
||||||
|
|
||||||
JobState get_state(int idx);
|
JobState get_state(int idx);
|
||||||
void set_state(int idx, JobState);
|
void set_state(int idx, JobState);
|
||||||
|
@ -115,6 +121,7 @@ private:
|
||||||
void on_progress(Event&);
|
void on_progress(Event&);
|
||||||
void on_error(Event&);
|
void on_error(Event&);
|
||||||
void on_cancel(Event&);
|
void on_cancel(Event&);
|
||||||
|
void on_info(Event&);
|
||||||
// This vector keep adress and filename of uploads. It is used when checking for running uploads during exit.
|
// This vector keep adress and filename of uploads. It is used when checking for running uploads during exit.
|
||||||
std::vector<std::pair<std::string, std::string>> upload_names;
|
std::vector<std::pair<std::string, std::string>> upload_names;
|
||||||
void save_user_data(int);
|
void save_user_data(int);
|
||||||
|
@ -124,7 +131,7 @@ private:
|
||||||
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
wxDECLARE_EVENT(EVT_PRINTHOST_PROGRESS, PrintHostQueueDialog::Event);
|
||||||
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
wxDECLARE_EVENT(EVT_PRINTHOST_ERROR, PrintHostQueueDialog::Event);
|
||||||
wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
wxDECLARE_EVENT(EVT_PRINTHOST_CANCEL, PrintHostQueueDialog::Event);
|
||||||
|
wxDECLARE_EVENT(EVT_PRINTHOST_INFO, PrintHostQueueDialog::Event);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -92,7 +92,7 @@ wxString AstroBox::get_test_failed_msg (wxString &msg) const
|
||||||
% _utf8(L("Note: AstroBox version at least 1.1.0 is required."))).str());
|
% _utf8(L("Note: AstroBox version at least 1.1.0 is required."))).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
bool AstroBox::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
{
|
{
|
||||||
const char *name = get_name();
|
const char *name = get_name();
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ public:
|
||||||
bool test(wxString &curl_msg) const override;
|
bool test(wxString &curl_msg) const override;
|
||||||
wxString get_test_ok_msg () const override;
|
wxString get_test_ok_msg () const override;
|
||||||
wxString get_test_failed_msg (wxString &msg) const override;
|
wxString get_test_failed_msg (wxString &msg) const override;
|
||||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
bool has_auto_discovery() const override { return true; }
|
bool has_auto_discovery() const override { return true; }
|
||||||
bool can_test() const override { return true; }
|
bool can_test() const override { return true; }
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
|
|
@ -7,12 +7,11 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <boost/optional.hpp>
|
|
||||||
#include <boost/system/error_code.hpp>
|
|
||||||
#include <boost/endian/conversion.hpp>
|
#include <boost/endian/conversion.hpp>
|
||||||
#include <boost/asio.hpp>
|
|
||||||
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
#include <boost/date_time/posix_time/posix_time_duration.hpp>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <boost/bind.hpp>
|
||||||
|
|
||||||
using boost::optional;
|
using boost::optional;
|
||||||
using boost::system::error_code;
|
using boost::system::error_code;
|
||||||
|
@ -238,6 +237,7 @@ struct DnsRR_A
|
||||||
enum { TAG = 0x1 };
|
enum { TAG = 0x1 };
|
||||||
|
|
||||||
asio::ip::address_v4 ip;
|
asio::ip::address_v4 ip;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
|
static void decode(optional<DnsRR_A> &result, const DnsResource &rr)
|
||||||
{
|
{
|
||||||
|
@ -255,6 +255,7 @@ struct DnsRR_AAAA
|
||||||
enum { TAG = 0x1c };
|
enum { TAG = 0x1c };
|
||||||
|
|
||||||
asio::ip::address_v6 ip;
|
asio::ip::address_v6 ip;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
|
static void decode(optional<DnsRR_AAAA> &result, const DnsResource &rr)
|
||||||
{
|
{
|
||||||
|
@ -402,7 +403,7 @@ struct DnsMessage
|
||||||
|
|
||||||
DnsSDMap sdmap;
|
DnsSDMap sdmap;
|
||||||
|
|
||||||
static optional<DnsMessage> decode(const std::vector<char> &buffer, const Bonjour::TxtKeys &txt_keys)
|
static optional<DnsMessage> decode(const std::vector<char>& buffer, const Bonjour::TxtKeys& txt_keys)
|
||||||
{
|
{
|
||||||
const auto size = buffer.size();
|
const auto size = buffer.size();
|
||||||
if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
|
if (size < DnsHeader::SIZE + DnsQuestion::MIN_SIZE || size > MAX_SIZE) {
|
||||||
|
@ -426,20 +427,26 @@ struct DnsMessage
|
||||||
auto rr = DnsResource::decode(buffer, offset, dataoffset);
|
auto rr = DnsResource::decode(buffer, offset, dataoffset);
|
||||||
if (!rr) {
|
if (!rr) {
|
||||||
return boost::none;
|
return boost::none;
|
||||||
} else {
|
}
|
||||||
|
else {
|
||||||
res.parse_rr(buffer, std::move(*rr), dataoffset, txt_keys);
|
res.parse_rr(buffer, std::move(*rr), dataoffset, txt_keys);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::move(res);
|
return std::move(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void parse_rr(const std::vector<char> &buffer, DnsResource &&rr, size_t dataoffset, const Bonjour::TxtKeys &txt_keys)
|
void parse_rr(const std::vector<char>& buffer, DnsResource&& rr, size_t dataoffset, const Bonjour::TxtKeys& txt_keys)
|
||||||
{
|
{
|
||||||
switch (rr.type) {
|
switch (rr.type) {
|
||||||
case DnsRR_A::TAG: DnsRR_A::decode(this->rr_a, rr); break;
|
case DnsRR_A::TAG:
|
||||||
case DnsRR_AAAA::TAG: DnsRR_AAAA::decode(this->rr_aaaa, rr); break;
|
DnsRR_A::decode(this->rr_a, rr);
|
||||||
|
this->rr_a->name = rr.name;
|
||||||
|
break;
|
||||||
|
case DnsRR_AAAA::TAG:
|
||||||
|
DnsRR_AAAA::decode(this->rr_aaaa, rr);
|
||||||
|
this->rr_aaaa->name = rr.name;
|
||||||
|
break;
|
||||||
case DnsRR_SRV::TAG: {
|
case DnsRR_SRV::TAG: {
|
||||||
auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
|
auto srv = DnsRR_SRV::decode(buffer, rr, dataoffset);
|
||||||
if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
|
if (srv) { this->sdmap.insert_srv(std::move(rr.name), std::move(*srv)); }
|
||||||
|
@ -480,24 +487,11 @@ std::ostream& operator<<(std::ostream &os, const DnsMessage &msg)
|
||||||
return os << "])";
|
return os << "])";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const asio::ip::address_v4 BonjourRequest::MCAST_IP4{ 0xe00000fb };
|
||||||
struct BonjourRequest
|
const asio::ip::address_v6 BonjourRequest::MCAST_IP6 = asio::ip::make_address_v6("ff02::fb");
|
||||||
{
|
|
||||||
static const asio::ip::address_v4 MCAST_IP4;
|
|
||||||
static const uint16_t MCAST_PORT;
|
|
||||||
|
|
||||||
std::vector<char> data;
|
|
||||||
|
|
||||||
static optional<BonjourRequest> make(const std::string &service, const std::string &protocol);
|
|
||||||
|
|
||||||
private:
|
|
||||||
BonjourRequest(std::vector<char> &&data) : data(std::move(data)) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
const asio::ip::address_v4 BonjourRequest::MCAST_IP4{0xe00000fb};
|
|
||||||
const uint16_t BonjourRequest::MCAST_PORT = 5353;
|
const uint16_t BonjourRequest::MCAST_PORT = 5353;
|
||||||
|
|
||||||
optional<BonjourRequest> BonjourRequest::make(const std::string &service, const std::string &protocol)
|
optional<BonjourRequest> BonjourRequest::make_PTR(const std::string &service, const std::string &protocol)
|
||||||
{
|
{
|
||||||
if (service.size() > 15 || protocol.size() > 15) {
|
if (service.size() > 15 || protocol.size() > 15) {
|
||||||
return boost::none;
|
return boost::none;
|
||||||
|
@ -535,73 +529,227 @@ optional<BonjourRequest> BonjourRequest::make(const std::string &service, const
|
||||||
return BonjourRequest(std::move(data));
|
return BonjourRequest(std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
optional<BonjourRequest> BonjourRequest::make_A(const std::string& hostname)
|
||||||
// API - private part
|
|
||||||
|
|
||||||
struct Bonjour::priv
|
|
||||||
{
|
{
|
||||||
const std::string service;
|
// todo: why is this and what is real max
|
||||||
std::string protocol;
|
if (hostname.size() > 30) {
|
||||||
std::string service_dn;
|
return boost::none;
|
||||||
TxtKeys txt_keys;
|
}
|
||||||
unsigned timeout;
|
|
||||||
unsigned retries;
|
|
||||||
|
|
||||||
std::vector<char> buffer;
|
std::vector<char> data;
|
||||||
std::thread io_thread;
|
data.reserve(hostname.size() + 18);
|
||||||
Bonjour::ReplyFn replyfn;
|
|
||||||
Bonjour::CompleteFn completefn;
|
|
||||||
|
|
||||||
priv(std::string &&service);
|
// Add metadata
|
||||||
|
static const unsigned char rq_meta[] = {
|
||||||
|
0x00, 0x00, // Query ID (zero for mDNS)
|
||||||
|
0x00, 0x00, // Flags
|
||||||
|
0x00, 0x01, // One query
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs
|
||||||
|
};
|
||||||
|
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
|
||||||
|
|
||||||
std::string strip_service_dn(const std::string &service_name) const;
|
// Add hostname without .local
|
||||||
void udp_receive(udp::endpoint from, size_t bytes);
|
data.push_back(hostname.size());
|
||||||
void lookup_perform();
|
data.insert(data.end(), hostname.begin(), hostname.end());
|
||||||
};
|
|
||||||
|
|
||||||
Bonjour::priv::priv(std::string &&service)
|
// Add the rest of A record
|
||||||
: service(std::move(service))
|
static const unsigned char ptr_tail[] = {
|
||||||
, protocol("tcp")
|
0x05, // length of "local"
|
||||||
, timeout(10)
|
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00,// "local" string and terminator
|
||||||
, retries(1)
|
0x00, 0x01, // Type A
|
||||||
{
|
0x00, 0xff, // Class - 01 is internet 0xff is any
|
||||||
buffer.resize(DnsMessage::MAX_SIZE);
|
};
|
||||||
|
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
|
||||||
|
|
||||||
|
return BonjourRequest(std::move(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Bonjour::priv::strip_service_dn(const std::string &service_name) const
|
optional<BonjourRequest> BonjourRequest::make_AAAA(const std::string& hostname)
|
||||||
|
{
|
||||||
|
// todo: why is this and what is real max
|
||||||
|
if (hostname.size() > 30) {
|
||||||
|
return boost::none;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> data;
|
||||||
|
data.reserve(hostname.size() + 18);
|
||||||
|
|
||||||
|
// Add metadata
|
||||||
|
static const unsigned char rq_meta[] = {
|
||||||
|
0x00, 0x00, // Query ID (zero for mDNS)
|
||||||
|
0x00, 0x00, // Flags
|
||||||
|
0x00, 0x01, // One query
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Zero Answer, Authority, and Additional RRs
|
||||||
|
};
|
||||||
|
std::copy(rq_meta, rq_meta + sizeof(rq_meta), std::back_inserter(data));
|
||||||
|
|
||||||
|
// Add hostname without .local
|
||||||
|
data.push_back(hostname.size());
|
||||||
|
data.insert(data.end(), hostname.begin(), hostname.end());
|
||||||
|
|
||||||
|
// Add the rest of A record
|
||||||
|
static const unsigned char ptr_tail[] = {
|
||||||
|
0x05, // length of "local"
|
||||||
|
0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x00, // "local" string and terminator
|
||||||
|
0x00, 0x1c, // Type AAAA
|
||||||
|
0x00, 0xff, // Class - 01 is internet 0xff is any
|
||||||
|
};
|
||||||
|
std::copy(ptr_tail, ptr_tail + sizeof(ptr_tail), std::back_inserter(data));
|
||||||
|
|
||||||
|
return BonjourRequest(std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string strip_service_dn(const std::string& service_name, const std::string& service_dn)
|
||||||
{
|
{
|
||||||
if (service_name.size() <= service_dn.size()) {
|
if (service_name.size() <= service_dn.size()) {
|
||||||
return service_name;
|
return std::string();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto needle = service_name.rfind(service_dn);
|
auto needle = service_name.rfind(service_dn);
|
||||||
if (needle == service_name.size() - service_dn.size()) {
|
if (needle == service_name.size() - service_dn.size()) {
|
||||||
return service_name.substr(0, needle - 1);
|
return service_name.substr(0, needle - 1);
|
||||||
} else {
|
} else {
|
||||||
return service_name;
|
return std::string();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
UdpSession::UdpSession(Bonjour::ReplyFn rfn) : replyfn(rfn)
|
||||||
|
{
|
||||||
|
buffer.resize(DnsMessage::MAX_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
UdpSocket::UdpSocket( Bonjour::ReplyFn replyfn, const asio::ip::address& multicast_address, const asio::ip::address& interface_address, std::shared_ptr< boost::asio::io_service > io_service)
|
||||||
|
: replyfn(replyfn)
|
||||||
|
, multicast_address(multicast_address)
|
||||||
|
, socket(*io_service)
|
||||||
|
, io_service(io_service)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// open socket
|
||||||
|
boost::asio::ip::udp::endpoint listen_endpoint(multicast_address.is_v4() ? udp::v4() : udp::v6(), BonjourRequest::MCAST_PORT);
|
||||||
|
socket.open(listen_endpoint.protocol());
|
||||||
|
// set socket to listen
|
||||||
|
socket.set_option(udp::socket::reuse_address(true));
|
||||||
|
socket.bind(listen_endpoint);
|
||||||
|
if (interface_address.is_v4()) {
|
||||||
|
// listen for multicast on given interface
|
||||||
|
socket.set_option(boost::asio::ip::multicast::join_group(multicast_address.to_v4(), interface_address.to_v4()));
|
||||||
|
// send to interface
|
||||||
|
socket.set_option(asio::ip::multicast::outbound_interface(interface_address.to_v4()));
|
||||||
|
} else {
|
||||||
|
// listen for multicast on given interface
|
||||||
|
socket.set_option(boost::asio::ip::multicast::join_group(multicast_address.to_v6(), interface_address.to_v6().scope_id()));
|
||||||
|
// send to interface
|
||||||
|
socket.set_option(asio::ip::multicast::outbound_interface(interface_address.to_v6().scope_id()));
|
||||||
|
}
|
||||||
|
mcast_endpoint = udp::endpoint(multicast_address, BonjourRequest::MCAST_PORT);
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Socket created. Multicast: " << multicast_address << ". Interface: " << interface_address;
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
|
|
||||||
|
UdpSocket::UdpSocket( Bonjour::ReplyFn replyfn, const asio::ip::address& multicast_address, std::shared_ptr< boost::asio::io_service > io_service)
|
||||||
|
: replyfn(replyfn)
|
||||||
|
, multicast_address(multicast_address)
|
||||||
|
, socket(*io_service)
|
||||||
|
, io_service(io_service)
|
||||||
{
|
{
|
||||||
|
try {
|
||||||
|
// open socket
|
||||||
|
boost::asio::ip::udp::endpoint listen_endpoint(multicast_address.is_v4() ? udp::v4() : udp::v6(), BonjourRequest::MCAST_PORT);
|
||||||
|
socket.open(listen_endpoint.protocol());
|
||||||
|
// set socket to listen
|
||||||
|
socket.set_option(udp::socket::reuse_address(true));
|
||||||
|
socket.bind(listen_endpoint);
|
||||||
|
socket.set_option(boost::asio::ip::multicast::join_group(multicast_address));
|
||||||
|
mcast_endpoint = udp::endpoint(multicast_address, BonjourRequest::MCAST_PORT);
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Socket created. Multicast: " << multicast_address;
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UdpSocket::send()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
for (const auto& request : requests)
|
||||||
|
socket.send_to(asio::buffer(request.m_data), mcast_endpoint);
|
||||||
|
|
||||||
|
// Should we care if this is called while already receiving? (async_receive call from receive_handler)
|
||||||
|
async_receive();
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UdpSocket::async_receive()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// our session to hold the buffer + endpoint
|
||||||
|
auto session = create_session();
|
||||||
|
socket.async_receive_from(asio::buffer(session->buffer, session->buffer.size())
|
||||||
|
, session->remote_endpoint
|
||||||
|
, boost::bind(&UdpSocket::receive_handler, this, session, asio::placeholders::error, asio::placeholders::bytes_transferred));
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UdpSocket::receive_handler(SharedSession session, const boost::system::error_code& error, size_t bytes)
|
||||||
|
{
|
||||||
|
// let io_service to handle the datagram on session
|
||||||
|
// from boost documentation io_service::post:
|
||||||
|
// The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked.
|
||||||
|
io_service->post(boost::bind(&UdpSession::handle_receive, session, error, bytes));
|
||||||
|
// immediately accept new datagrams
|
||||||
|
async_receive();
|
||||||
|
}
|
||||||
|
|
||||||
|
SharedSession LookupSocket::create_session() const
|
||||||
|
{
|
||||||
|
return std::shared_ptr< LookupSession >(new LookupSession(this, replyfn));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LookupSession::handle_receive(const error_code& error, size_t bytes)
|
||||||
|
{
|
||||||
|
assert(socket);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << error.message();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (bytes == 0 || !replyfn) {
|
if (bytes == 0 || !replyfn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.resize(bytes);
|
buffer.resize(bytes);
|
||||||
auto dns_msg = DnsMessage::decode(buffer, txt_keys);
|
auto dns_msg = DnsMessage::decode(buffer, socket->get_txt_keys());
|
||||||
if (dns_msg) {
|
if (dns_msg) {
|
||||||
asio::ip::address ip = from.address();
|
asio::ip::address ip = remote_endpoint.address();
|
||||||
if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; }
|
if (dns_msg->rr_a) { ip = dns_msg->rr_a->ip; }
|
||||||
else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; }
|
else if (dns_msg->rr_aaaa) { ip = dns_msg->rr_aaaa->ip; }
|
||||||
|
|
||||||
for (auto &sdpair : dns_msg->sdmap) {
|
for (auto& sdpair : dns_msg->sdmap) {
|
||||||
if (! sdpair.second.srv) {
|
if (!sdpair.second.srv) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &srv = *sdpair.second.srv;
|
const auto& srv = *sdpair.second.srv;
|
||||||
auto service_name = strip_service_dn(sdpair.first);
|
|
||||||
|
auto service_name = strip_service_dn(sdpair.first, socket->get_service_dn());
|
||||||
|
if (service_name.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
std::string path;
|
std::string path;
|
||||||
std::string version;
|
std::string version;
|
||||||
|
@ -617,64 +765,282 @@ void Bonjour::priv::udp_receive(udp::endpoint from, size_t bytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SharedSession ResolveSocket::create_session() const
|
||||||
|
{
|
||||||
|
return std::shared_ptr< ResolveSession > (new ResolveSession(this, replyfn));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void ResolveSession::handle_receive(const error_code& error, size_t bytes)
|
||||||
|
{
|
||||||
|
assert(socket);
|
||||||
|
if (error) {
|
||||||
|
// todo: what level? do we even log? There might be callbacks when timer runs out
|
||||||
|
BOOST_LOG_TRIVIAL(info) << error.message();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (bytes == 0 || !replyfn) {
|
||||||
|
// todo: log something?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.resize(bytes);
|
||||||
|
#if 0
|
||||||
|
std::string str;
|
||||||
|
char const hex_chars[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||||
|
for (size_t i = 0; i < buffer.size(); i++) {
|
||||||
|
const char ch = buffer[i];
|
||||||
|
str += hex_chars[(ch & 0xF0) >> 4];
|
||||||
|
str += hex_chars[(ch & 0x0F) >> 0];
|
||||||
|
}
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << remote_endpoint.address()<< " " << str;
|
||||||
|
#endif
|
||||||
|
// decode buffer, txt keys are not needed for A / AAAA answer
|
||||||
|
auto dns_msg = DnsMessage::decode(buffer, Bonjour::TxtKeys());
|
||||||
|
if (dns_msg) {
|
||||||
|
asio::ip::address ip;
|
||||||
|
std::string answer_name;
|
||||||
|
if (dns_msg->rr_a) {
|
||||||
|
ip = dns_msg->rr_a->ip;
|
||||||
|
answer_name = dns_msg->rr_a->name;
|
||||||
|
}
|
||||||
|
else if (dns_msg->rr_aaaa) {
|
||||||
|
ip = dns_msg->rr_aaaa->ip;
|
||||||
|
answer_name = dns_msg->rr_aaaa->name;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return; // not matching query type with answer type
|
||||||
|
|
||||||
|
if (!answer_name.empty()) {
|
||||||
|
// transform both strings to lower. Should we really do it?
|
||||||
|
std::string name_tolower = answer_name;
|
||||||
|
std::transform(name_tolower.begin(), name_tolower.end(), name_tolower.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
std::string hostname_tolower = socket->get_hostname();
|
||||||
|
std::transform(hostname_tolower.begin(), hostname_tolower.end(), hostname_tolower.begin(),
|
||||||
|
[](unsigned char c) { return std::tolower(c); });
|
||||||
|
if (name_tolower == hostname_tolower) {
|
||||||
|
BonjourReply reply(ip, 0, std::string(), answer_name, BonjourReply::TxtData());
|
||||||
|
replyfn(std::move(reply));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// API - private part
|
||||||
|
|
||||||
|
struct Bonjour::priv
|
||||||
|
{
|
||||||
|
const std::string service;
|
||||||
|
std::string protocol;
|
||||||
|
std::string service_dn;
|
||||||
|
TxtKeys txt_keys;
|
||||||
|
unsigned timeout;
|
||||||
|
unsigned retries;
|
||||||
|
std::string hostname;
|
||||||
|
|
||||||
|
// std::vector<BonjourReply> replies;
|
||||||
|
|
||||||
|
std::vector<char> buffer;
|
||||||
|
std::thread io_thread;
|
||||||
|
Bonjour::ReplyFn replyfn;
|
||||||
|
Bonjour::CompleteFn completefn;
|
||||||
|
Bonjour::ResolveFn resolvefn;
|
||||||
|
|
||||||
|
priv(std::string&& service);
|
||||||
|
|
||||||
|
// void udp_receive_lookup(udp::endpoint from, size_t bytes);
|
||||||
|
void lookup_perform();
|
||||||
|
void resolve_perform();
|
||||||
|
};
|
||||||
|
|
||||||
|
Bonjour::priv::priv(std::string&& service)
|
||||||
|
: service(std::move(service))
|
||||||
|
, protocol("tcp")
|
||||||
|
, timeout(10)
|
||||||
|
, retries(1)
|
||||||
|
{
|
||||||
|
buffer.resize(DnsMessage::MAX_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
void Bonjour::priv::lookup_perform()
|
void Bonjour::priv::lookup_perform()
|
||||||
{
|
{
|
||||||
service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str();
|
service_dn = (boost::format("_%1%._%2%.local") % service % protocol).str();
|
||||||
|
|
||||||
const auto brq = BonjourRequest::make(service, protocol);
|
std::shared_ptr< boost::asio::io_service > io_service(new boost::asio::io_service);
|
||||||
if (!brq) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto self = this;
|
std::vector<LookupSocket*> sockets;
|
||||||
|
|
||||||
|
// resolve intefaces - from PR#6646
|
||||||
|
std::vector<boost::asio::ip::address> interfaces;
|
||||||
|
asio::ip::udp::resolver resolver(*io_service);
|
||||||
|
boost::system::error_code ec;
|
||||||
|
// ipv4 interfaces
|
||||||
|
auto results = resolver.resolve(udp::v4(), asio::ip::host_name(), "", ec);
|
||||||
|
if (!ec) {
|
||||||
|
for (const auto & r : results) {
|
||||||
|
const auto addr = r.endpoint().address();
|
||||||
|
if (addr.is_loopback()) continue;
|
||||||
|
interfaces.emplace_back(std::move(addr));
|
||||||
|
}
|
||||||
|
// create ipv4 socket for each interface
|
||||||
|
// each will send to querry to for both ipv4 and ipv6
|
||||||
|
for (const auto& intrfc : interfaces)
|
||||||
|
sockets.emplace_back(new LookupSocket(txt_keys, service, service_dn, protocol, replyfn, BonjourRequest::MCAST_IP4, intrfc, io_service));
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Failed to resolve ipv4 interfaces: " << ec.message();
|
||||||
|
}
|
||||||
|
if (sockets.empty())
|
||||||
|
sockets.emplace_back(new LookupSocket(txt_keys, service, service_dn, protocol, replyfn, BonjourRequest::MCAST_IP4, io_service));
|
||||||
|
// ipv6 interfaces
|
||||||
|
interfaces.clear();
|
||||||
|
//udp::resolver::query query(host, PORT, boost::asio::ip::resolver_query_base::numeric_service);
|
||||||
|
results = resolver.resolve(udp::v6(), asio::ip::host_name(), "", ec);
|
||||||
|
if (!ec)
|
||||||
|
{
|
||||||
|
for (const auto& r : results) {
|
||||||
|
const auto addr = r.endpoint().address();
|
||||||
|
if (addr.is_loopback()) continue;
|
||||||
|
interfaces.emplace_back(std::move(addr));
|
||||||
|
}
|
||||||
|
// create ipv6 socket for each interface
|
||||||
|
// each will send to querry to for both ipv4 and ipv6
|
||||||
|
for (const auto& intrfc : interfaces)
|
||||||
|
sockets.emplace_back(new LookupSocket(txt_keys, service, service_dn, protocol, replyfn, BonjourRequest::MCAST_IP6, intrfc, io_service));
|
||||||
|
if (interfaces.empty())
|
||||||
|
sockets.emplace_back(new LookupSocket(txt_keys, service, service_dn, protocol, replyfn, BonjourRequest::MCAST_IP6, io_service));
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(info)<< "Failed to resolve ipv6 interfaces: " << ec.message();
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
boost::asio::io_service io_service;
|
// send first queries
|
||||||
udp::socket socket(io_service);
|
for (auto * socket : sockets)
|
||||||
socket.open(udp::v4());
|
socket->send();
|
||||||
socket.set_option(udp::socket::reuse_address(true));
|
|
||||||
udp::endpoint mcast(BonjourRequest::MCAST_IP4, BonjourRequest::MCAST_PORT);
|
|
||||||
socket.send_to(asio::buffer(brq->data), mcast);
|
|
||||||
|
|
||||||
bool expired = false;
|
// timer settings
|
||||||
bool retry = false;
|
asio::deadline_timer timer(*io_service);
|
||||||
asio::deadline_timer timer(io_service);
|
|
||||||
retries--;
|
retries--;
|
||||||
std::function<void(const error_code &)> timer_handler = [&](const error_code &error) {
|
std::function<void(const error_code&)> timer_handler = [&](const error_code& error) {
|
||||||
|
// end
|
||||||
if (retries == 0 || error) {
|
if (retries == 0 || error) {
|
||||||
expired = true;
|
// is this correct ending?
|
||||||
if (self->completefn) {
|
io_service->stop();
|
||||||
self->completefn();
|
if (completefn) {
|
||||||
|
completefn();
|
||||||
}
|
}
|
||||||
|
// restart timer
|
||||||
} else {
|
} else {
|
||||||
retry = true;
|
|
||||||
retries--;
|
retries--;
|
||||||
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||||
timer.async_wait(timer_handler);
|
timer.async_wait(timer_handler);
|
||||||
|
// trigger another round of queries
|
||||||
|
for (auto * socket : sockets)
|
||||||
|
socket->send();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
// start timer
|
||||||
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||||
timer.async_wait(timer_handler);
|
timer.async_wait(timer_handler);
|
||||||
|
// start io_service, it will run until it has something to do - so in this case until stop is called in timer
|
||||||
|
io_service->run();
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
udp::endpoint recv_from;
|
void Bonjour::priv::resolve_perform()
|
||||||
const auto recv_handler = [&](const error_code &error, size_t bytes) {
|
{
|
||||||
if (!error) { self->udp_receive(recv_from, bytes); }
|
// reply callback is shared to every UDPSession which is called on same thread as io_service->run();
|
||||||
|
// thus no need to mutex replies in reply_callback, same should go with the timer
|
||||||
|
std::vector<BonjourReply> replies;
|
||||||
|
// examples would store [self] to the lambda (and the timer one), is it ok not to do it? (Should be c++03)
|
||||||
|
const auto reply_callback = [&rpls = replies](BonjourReply&& reply)
|
||||||
|
{
|
||||||
|
if (std::find(rpls.begin(), rpls.end(), reply) == rpls.end())
|
||||||
|
rpls.push_back(reply);
|
||||||
};
|
};
|
||||||
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
|
|
||||||
|
|
||||||
while (io_service.run_one()) {
|
std::shared_ptr< boost::asio::io_service > io_service(new boost::asio::io_service);
|
||||||
if (expired) {
|
std::vector<ResolveSocket*> sockets;
|
||||||
socket.cancel();
|
|
||||||
} else if (retry) {
|
// resolve interfaces - from PR#6646
|
||||||
retry = false;
|
std::vector<boost::asio::ip::address> interfaces;
|
||||||
socket.send_to(asio::buffer(brq->data), mcast);
|
asio::ip::udp::resolver resolver(*io_service);
|
||||||
|
boost::system::error_code ec;
|
||||||
|
// ipv4 interfaces
|
||||||
|
auto results = resolver.resolve(udp::v4(), asio::ip::host_name(), "", ec);
|
||||||
|
if (!ec) {
|
||||||
|
for (auto const& r : results) {
|
||||||
|
auto const addr = r.endpoint().address();
|
||||||
|
if (addr.is_loopback()) continue;
|
||||||
|
interfaces.emplace_back(addr);
|
||||||
|
}
|
||||||
|
// create ipv4 socket for each interface
|
||||||
|
// each will send to querry to for both ipv4 and ipv6
|
||||||
|
for (const auto& intrfc : interfaces)
|
||||||
|
sockets.emplace_back(new ResolveSocket(hostname, reply_callback, BonjourRequest::MCAST_IP4, intrfc, io_service));
|
||||||
} else {
|
} else {
|
||||||
buffer.resize(DnsMessage::MAX_SIZE);
|
BOOST_LOG_TRIVIAL(info) << "Failed to resolve ipv4 interfaces: " << ec.message();
|
||||||
socket.async_receive_from(asio::buffer(buffer, buffer.size()), recv_from, recv_handler);
|
|
||||||
}
|
}
|
||||||
|
if (sockets.empty())
|
||||||
|
sockets.emplace_back(new ResolveSocket(hostname, reply_callback, BonjourRequest::MCAST_IP4, io_service));
|
||||||
|
|
||||||
|
// ipv6 interfaces
|
||||||
|
interfaces.clear();
|
||||||
|
results = resolver.resolve(udp::v6(), asio::ip::host_name(), "", ec);
|
||||||
|
if (!ec) {
|
||||||
|
for (auto const& r : results) {
|
||||||
|
auto const addr = r.endpoint().address();
|
||||||
|
if (addr.is_loopback()) continue;
|
||||||
|
interfaces.emplace_back(addr);
|
||||||
}
|
}
|
||||||
} catch (std::exception& /* e */) {
|
// create ipv6 socket for each interface
|
||||||
|
// each will send to querry to for both ipv4 and ipv6
|
||||||
|
for (const auto& intrfc : interfaces)
|
||||||
|
sockets.emplace_back(new ResolveSocket(hostname, reply_callback, BonjourRequest::MCAST_IP6, intrfc, io_service));
|
||||||
|
if (interfaces.empty())
|
||||||
|
sockets.emplace_back(new ResolveSocket(hostname, reply_callback, BonjourRequest::MCAST_IP6, io_service));
|
||||||
|
} else {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Failed to resolve ipv6 interfaces: " << ec.message();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// send first queries
|
||||||
|
for (auto * socket : sockets)
|
||||||
|
socket->send();
|
||||||
|
|
||||||
|
// timer settings
|
||||||
|
asio::deadline_timer timer(*io_service);
|
||||||
|
retries--;
|
||||||
|
std::function<void(const error_code&)> timer_handler = [&](const error_code& error) {
|
||||||
|
int replies_count = replies.size();
|
||||||
|
// end
|
||||||
|
if (retries == 0 || error || replies_count > 0) {
|
||||||
|
// is this correct ending?
|
||||||
|
io_service->stop();
|
||||||
|
if (replies_count > 0 && resolvefn) {
|
||||||
|
resolvefn(replies);
|
||||||
|
}
|
||||||
|
// restart timer
|
||||||
|
} else {
|
||||||
|
retries--;
|
||||||
|
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||||
|
timer.async_wait(timer_handler);
|
||||||
|
// trigger another round of queries
|
||||||
|
for (auto * socket : sockets)
|
||||||
|
socket->send();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// start timer
|
||||||
|
timer.expires_from_now(boost::posix_time::seconds(timeout));
|
||||||
|
timer.async_wait(timer_handler);
|
||||||
|
// start io_service, it will run until it has something to do - so in this case until stop is called in timer
|
||||||
|
io_service->run();
|
||||||
|
}
|
||||||
|
catch (std::exception& e) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << e.what();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -770,6 +1136,12 @@ Bonjour& Bonjour::set_timeout(unsigned timeout)
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bonjour& Bonjour::set_hostname(const std::string& hostname)
|
||||||
|
{
|
||||||
|
if (p) { p->hostname = hostname; }
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
Bonjour& Bonjour::set_retries(unsigned retries)
|
Bonjour& Bonjour::set_retries(unsigned retries)
|
||||||
{
|
{
|
||||||
if (p && retries > 0) { p->retries = retries; }
|
if (p && retries > 0) { p->retries = retries; }
|
||||||
|
@ -788,6 +1160,12 @@ Bonjour& Bonjour::on_complete(CompleteFn fn)
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Bonjour& Bonjour::on_resolve(ResolveFn fn)
|
||||||
|
{
|
||||||
|
if (p) { p->resolvefn = std::move(fn); }
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
Bonjour::Ptr Bonjour::lookup()
|
Bonjour::Ptr Bonjour::lookup()
|
||||||
{
|
{
|
||||||
auto self = std::make_shared<Bonjour>(std::move(*this));
|
auto self = std::make_shared<Bonjour>(std::move(*this));
|
||||||
|
@ -803,4 +1181,26 @@ Bonjour::Ptr Bonjour::lookup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Bonjour::Ptr Bonjour::resolve()
|
||||||
|
{
|
||||||
|
auto self = std::make_shared<Bonjour>(std::move(*this));
|
||||||
|
|
||||||
|
if (self->p) {
|
||||||
|
auto io_thread = std::thread([self]() {
|
||||||
|
self->p->resolve_perform();
|
||||||
|
});
|
||||||
|
self->p->io_thread = std::move(io_thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Bonjour::resolve_sync()
|
||||||
|
{
|
||||||
|
if (p)
|
||||||
|
p->resolve_perform();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ wxString Duet::get_test_failed_msg (wxString &msg) const
|
||||||
% std::string(msg.ToUTF8())).str());
|
% std::string(msg.ToUTF8())).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
bool Duet::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
{
|
{
|
||||||
wxString connect_msg;
|
wxString connect_msg;
|
||||||
auto connectionType = connect(connect_msg);
|
auto connectionType = connect(connect_msg);
|
||||||
|
|
|
@ -22,7 +22,7 @@ public:
|
||||||
bool test(wxString &curl_msg) const override;
|
bool test(wxString &curl_msg) const override;
|
||||||
wxString get_test_ok_msg() const override;
|
wxString get_test_ok_msg() const override;
|
||||||
wxString get_test_failed_msg(wxString &msg) const override;
|
wxString get_test_failed_msg(wxString &msg) const override;
|
||||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
bool has_auto_discovery() const override { return false; }
|
bool has_auto_discovery() const override { return false; }
|
||||||
bool can_test() const override { return true; }
|
bool can_test() const override { return true; }
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::StartSimulation; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::StartSimulation; }
|
||||||
|
|
|
@ -76,7 +76,7 @@ wxString FlashAir::get_test_failed_msg (wxString &msg) const
|
||||||
% _utf8(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))).str());
|
% _utf8(L("Note: FlashAir with firmware 2.00.02 or newer and activated upload function is required."))).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
bool FlashAir::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
{
|
{
|
||||||
const char *name = get_name();
|
const char *name = get_name();
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ public:
|
||||||
bool test(wxString &curl_msg) const override;
|
bool test(wxString &curl_msg) const override;
|
||||||
wxString get_test_ok_msg() const override;
|
wxString get_test_ok_msg() const override;
|
||||||
wxString get_test_failed_msg(wxString &msg) const override;
|
wxString get_test_failed_msg(wxString &msg) const override;
|
||||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
bool has_auto_discovery() const override { return false; }
|
bool has_auto_discovery() const override { return false; }
|
||||||
bool can_test() const override { return true; }
|
bool can_test() const override { return true; }
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return {}; }
|
||||||
|
|
|
@ -62,7 +62,7 @@ wxString MKS::get_test_failed_msg(wxString& msg) const
|
||||||
% std::string(msg.ToUTF8())).str());
|
% std::string(msg.ToUTF8())).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
bool MKS::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
{
|
{
|
||||||
bool res = true;
|
bool res = true;
|
||||||
|
|
||||||
|
|
|
@ -22,7 +22,7 @@ public:
|
||||||
bool test(wxString& curl_msg) const override;
|
bool test(wxString& curl_msg) const override;
|
||||||
wxString get_test_ok_msg() const override;
|
wxString get_test_ok_msg() const override;
|
||||||
wxString get_test_failed_msg(wxString& msg) const override;
|
wxString get_test_failed_msg(wxString& msg) const override;
|
||||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
bool has_auto_discovery() const override { return false; }
|
bool has_auto_discovery() const override { return false; }
|
||||||
bool can_test() const override { return true; }
|
bool can_test() const override { return true; }
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
#include <boost/property_tree/ptree.hpp>
|
#include <boost/property_tree/ptree.hpp>
|
||||||
#include <boost/property_tree/json_parser.hpp>
|
#include <boost/property_tree/json_parser.hpp>
|
||||||
#include <boost/algorithm/string/predicate.hpp>
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
|
#include <boost/asio.hpp>
|
||||||
|
#include <boost/algorithm/string/split.hpp>
|
||||||
|
#include <boost/nowide/convert.hpp>
|
||||||
|
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
|
||||||
|
@ -15,10 +18,12 @@
|
||||||
|
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
#include "slic3r/GUI/I18N.hpp"
|
#include "slic3r/GUI/I18N.hpp"
|
||||||
#include "slic3r/GUI/GUI.hpp"
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/format.hpp"
|
||||||
#include "Http.hpp"
|
#include "Http.hpp"
|
||||||
#include "libslic3r/AppConfig.hpp"
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
#include "Bonjour.hpp"
|
||||||
|
#include "slic3r/GUI/BonjourDialog.hpp"
|
||||||
|
|
||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
namespace pt = boost::property_tree;
|
namespace pt = boost::property_tree;
|
||||||
|
@ -26,9 +31,9 @@ namespace pt = boost::property_tree;
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
#ifdef WIN32
|
|
||||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
|
||||||
namespace {
|
namespace {
|
||||||
|
#ifdef WIN32
|
||||||
|
// 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)
|
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||||
{
|
{
|
||||||
// put ipv6 into [] brackets
|
// put ipv6 into [] brackets
|
||||||
|
@ -91,8 +96,66 @@ std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
|
||||||
return out;
|
return out;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
} //namespace
|
|
||||||
|
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
|
#endif // WIN32
|
||||||
|
std::string escape_string(const std::string& unescaped)
|
||||||
|
{
|
||||||
|
std::string ret_val;
|
||||||
|
CURL* curl = curl_easy_init();
|
||||||
|
if (curl) {
|
||||||
|
char* decoded = curl_easy_escape(curl, unescaped.c_str(), unescaped.size());
|
||||||
|
if (decoded) {
|
||||||
|
ret_val = std::string(decoded);
|
||||||
|
curl_free(decoded);
|
||||||
|
}
|
||||||
|
curl_easy_cleanup(curl);
|
||||||
|
}
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
std::string escape_path_by_element(const boost::filesystem::path& path)
|
||||||
|
{
|
||||||
|
std::string ret_val = escape_string(path.filename().string());
|
||||||
|
boost::filesystem::path parent(path.parent_path());
|
||||||
|
while (!parent.empty() && parent.string() != "/") // "/" check is for case "/file.gcode" was inserted. Then boost takes "/" as parent_path.
|
||||||
|
{
|
||||||
|
ret_val = escape_string(parent.filename().string()) + "/" + ret_val;
|
||||||
|
parent = parent.parent_path();
|
||||||
|
}
|
||||||
|
return ret_val;
|
||||||
|
}
|
||||||
|
} //namespace
|
||||||
|
|
||||||
|
|
||||||
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||||
m_host(config->opt_string("print_host")),
|
m_host(config->opt_string("print_host")),
|
||||||
|
@ -103,11 +166,62 @@ OctoPrint::OctoPrint(DynamicPrintConfig *config) :
|
||||||
|
|
||||||
const char* OctoPrint::get_name() const { return "OctoPrint"; }
|
const char* OctoPrint::get_name() const { return "OctoPrint"; }
|
||||||
|
|
||||||
bool OctoPrint::test(wxString &msg) const
|
#ifdef WIN32
|
||||||
|
bool OctoPrint::test_with_resolved_ip(wxString &msg) const
|
||||||
{
|
{
|
||||||
// Since the request is performed synchronously here,
|
// 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.
|
||||||
|
auto url = substitute_host(make_url("api/version"), GUI::into_u8(msg));
|
||||||
|
msg.Clear();
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(url);//std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version at %2% : %3%, HTTP %4%, body: `%5%`") % name % url % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
if (!ptree.get_optional<std::string>("api")) {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception&) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response.";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
bool OctoPrint::test(wxString& msg) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
const char *name = get_name();
|
const char *name = get_name();
|
||||||
|
|
||||||
bool res = true;
|
bool res = true;
|
||||||
|
@ -159,9 +273,10 @@ bool OctoPrint::test(wxString &msg) const
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
wxString OctoPrint::get_test_ok_msg () const
|
wxString OctoPrint::get_test_ok_msg () const
|
||||||
{
|
{
|
||||||
return _(L("Connection to OctoPrint/Klipper works correctly."));
|
return _(L("Connection to OctoPrint works correctly."));
|
||||||
}
|
}
|
||||||
|
|
||||||
wxString OctoPrint::get_test_failed_msg (wxString &msg) const
|
wxString OctoPrint::get_test_failed_msg (wxString &msg) const
|
||||||
|
@ -172,9 +287,130 @@ wxString OctoPrint::get_test_failed_msg (wxString &msg) const
|
||||||
% _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str());
|
% _utf8(L("Note: OctoPrint version at least 1.1.0 is required."))).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
{
|
{
|
||||||
const char *name = get_name();
|
#ifndef WIN32
|
||||||
|
return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||||
|
#else
|
||||||
|
std::string host = get_host_from_url(m_host);
|
||||||
|
|
||||||
|
// decide what to do based on m_host - resolve hostname or upload to ip
|
||||||
|
std::vector<boost::asio::ip::address> resolved_addr;
|
||||||
|
boost::system::error_code ec;
|
||||||
|
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")){
|
||||||
|
Bonjour("octoprint")
|
||||||
|
.set_hostname(host)
|
||||||
|
.set_retries(5) // number of rounds of queries send
|
||||||
|
.set_timeout(1) // after each timeout, if there is any answer, the resolving will stop
|
||||||
|
.on_resolve([&ra = resolved_addr](const std::vector<BonjourReply>& replies) {
|
||||||
|
for (const auto & rpl : replies) {
|
||||||
|
boost::asio::ip::address ip(rpl.ip);
|
||||||
|
ra.emplace_back(ip);
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Resolved IP address: " << rpl.ip;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.resolve_sync();
|
||||||
|
}
|
||||||
|
if (resolved_addr.empty()) {
|
||||||
|
// no resolved addresses - try system resolving
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "PrusaSlicer failed to resolve hostname " << m_host << " into the IP address. Starting upload with system resolving.";
|
||||||
|
return upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||||
|
} else if (resolved_addr.size() == 1) {
|
||||||
|
// one address resolved - upload there
|
||||||
|
return upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr.front());
|
||||||
|
} else if (resolved_addr.size() == 2 && resolved_addr[0].is_v4() != resolved_addr[1].is_v4()) {
|
||||||
|
// there are just 2 addresses and 1 is ip_v4 and other is ip_v6
|
||||||
|
// try sending to both. (Then if both fail, show both error msg after second try)
|
||||||
|
wxString error_message;
|
||||||
|
if (!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
|
||||||
|
, [&msg = error_message, resolved_addr](wxString error) { msg = GUI::format_wxstr("%1%: %2%", resolved_addr.front(), error); }
|
||||||
|
, info_fn, resolved_addr.front())
|
||||||
|
&&
|
||||||
|
!upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn
|
||||||
|
, [&msg = error_message, resolved_addr](wxString error) { msg += GUI::format_wxstr("\n%1%: %2%", resolved_addr.back(), error); }
|
||||||
|
, info_fn, resolved_addr.back())
|
||||||
|
) {
|
||||||
|
|
||||||
|
error_fn(error_message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// There are multiple addresses - user needs to choose which to use.
|
||||||
|
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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
#endif // WIN32
|
||||||
|
}
|
||||||
|
#ifdef WIN32
|
||||||
|
bool OctoPrint::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
|
||||||
|
{
|
||||||
|
info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
|
||||||
|
|
||||||
|
// If test fails, test_msg_or_host_ip contains the error message.
|
||||||
|
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||||
|
// Test_msg already contains resolved ip and will be cleared on start of test().
|
||||||
|
wxString test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
|
||||||
|
if (!test_with_resolved_ip(test_msg_or_host_ip)) {
|
||||||
|
error_fn(std::move(test_msg_or_host_ip));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* name = get_name();
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
std::string url = substitute_host(make_url("api/files/local"), resolved_addr.to_string());
|
||||||
|
bool result = true;
|
||||||
|
|
||||||
|
info_fn(L"resolve", boost::nowide::widen(url));
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||||
|
|
||||||
|
auto http = Http::post(url);//std::move(url));
|
||||||
|
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 ???
|
||||||
|
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % body;
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file to %2%: %3%, HTTP %4%, body: `%5%`") % name % url % error % status % body;
|
||||||
|
error_fn(format_error(body, error, status));
|
||||||
|
result = false;
|
||||||
|
})
|
||||||
|
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||||
|
prorgess_fn(std::move(progress), cancel);
|
||||||
|
if (cancel) {
|
||||||
|
// Upload was canceled
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
|
||||||
|
result = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
|
{
|
||||||
|
const char* name = get_name();
|
||||||
|
|
||||||
const auto upload_filename = upload_data.upload_path.filename();
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
@ -182,7 +418,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
||||||
// If test fails, test_msg_or_host_ip contains the error message.
|
// If test fails, test_msg_or_host_ip contains the error message.
|
||||||
// Otherwise on Windows it contains the resolved IP address of the host.
|
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||||
wxString test_msg_or_host_ip;
|
wxString test_msg_or_host_ip;
|
||||||
if (! test(test_msg_or_host_ip)) {
|
if (!test(test_msg_or_host_ip)) {
|
||||||
error_fn(std::move(test_msg_or_host_ip));
|
error_fn(std::move(test_msg_or_host_ip));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -192,7 +428,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
||||||
|
|
||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
// 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())
|
if (m_host.find("https://") == 0 || test_msg_or_host_ip.empty() || GUI::get_app_config()->get("allow_ip_resolve") != "1")
|
||||||
#endif // _WIN32
|
#endif // _WIN32
|
||||||
{
|
{
|
||||||
// If https is entered we assume signed ceritificate is being used
|
// If https is entered we assume signed ceritificate is being used
|
||||||
|
@ -207,6 +443,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
||||||
// This new address returns in "test_msg_or_host_ip" variable.
|
// This new address returns in "test_msg_or_host_ip" variable.
|
||||||
// Solves troubles of uploades failing with name address.
|
// Solves troubles of uploades failing with name address.
|
||||||
// in original address (m_host) replace host for resolved ip
|
// in original address (m_host) replace host for resolved ip
|
||||||
|
info_fn(L"resolve", test_msg_or_host_ip);
|
||||||
url = substitute_host(make_url("api/files/local"), GUI::into_u8(test_msg_or_host_ip));
|
url = substitute_host(make_url("api/files/local"), GUI::into_u8(test_msg_or_host_ip));
|
||||||
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||||
}
|
}
|
||||||
|
@ -233,7 +470,7 @@ bool OctoPrint::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Erro
|
||||||
error_fn(format_error(body, error, status));
|
error_fn(format_error(body, error, status));
|
||||||
res = false;
|
res = false;
|
||||||
})
|
})
|
||||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||||
prorgess_fn(std::move(progress), cancel);
|
prorgess_fn(std::move(progress), cancel);
|
||||||
if (cancel) {
|
if (cancel) {
|
||||||
// Upload was canceled
|
// Upload was canceled
|
||||||
|
@ -321,11 +558,12 @@ void SL1Host::set_auth(Http &http) const
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrusaLink
|
// PrusaLink
|
||||||
PrusaLink::PrusaLink(DynamicPrintConfig* config) :
|
PrusaLink::PrusaLink(DynamicPrintConfig* config, bool show_after_message) :
|
||||||
OctoPrint(config),
|
OctoPrint(config),
|
||||||
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
m_authorization_type(dynamic_cast<const ConfigOptionEnum<AuthorizationType>*>(config->option("printhost_authorization_type"))->value),
|
||||||
m_username(config->opt_string("printhost_user")),
|
m_username(config->opt_string("printhost_user")),
|
||||||
m_password(config->opt_string("printhost_password"))
|
m_password(config->opt_string("printhost_password")),
|
||||||
|
m_show_after_message(show_after_message)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -364,4 +602,528 @@ void PrusaLink::set_auth(Http& http) const
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
bool PrusaLink::version_check(const boost::optional<std::string>& version_text) const
|
||||||
|
{
|
||||||
|
// version_text is in format OctoPrint 1.2.3
|
||||||
|
// true (= use PUT) should return:
|
||||||
|
// PrusaLink 0.7+
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!version_text)
|
||||||
|
throw Slic3r::RuntimeError("no version_text was given");
|
||||||
|
|
||||||
|
std::vector<std::string> name_and_version;
|
||||||
|
boost::algorithm::split(name_and_version, *version_text, boost::is_any_of(" "));
|
||||||
|
|
||||||
|
if (name_and_version.size() != 2)
|
||||||
|
throw Slic3r::RuntimeError("invalid version_text");
|
||||||
|
|
||||||
|
Semver semver(name_and_version[1]); // throws Slic3r::RuntimeError when unable to parse
|
||||||
|
if (name_and_version.front() == "PrusaLink" && semver >= Semver(0, 7, 0))
|
||||||
|
return true;
|
||||||
|
} catch (const Slic3r::RuntimeError& ex) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << std::string("Print host version check failed: ") + ex.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool PrusaLink::test(wxString& msg) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
const char* name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("api/version");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
if (!ptree.get_optional<std::string>("api")) {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception&) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.on_ip_resolve([&](std::string address) {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Remember resolved address to be reused at successive REST API call.
|
||||||
|
msg = GUI::from_u8(address);
|
||||||
|
})
|
||||||
|
#endif // WIN32
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrusaLink::get_storage(wxArrayString& output) const
|
||||||
|
{
|
||||||
|
const char* name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("api/v1/storage");
|
||||||
|
wxString error_msg;
|
||||||
|
|
||||||
|
struct StorageInfo{
|
||||||
|
wxString name;
|
||||||
|
bool read_only;
|
||||||
|
long long free_space;
|
||||||
|
};
|
||||||
|
std::vector<StorageInfo> storage;
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get storage at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
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);
|
||||||
|
res = false;
|
||||||
|
// If status is 0, the communication with the printer has failed completely (most likely a timeout), if the status is <= 400, it is an error returned by the pritner.
|
||||||
|
// If 0, we can show error to the user now, as we know the communication has failed. (res = true will do the trick.)
|
||||||
|
// if not 0, we must not show error, as not all printers support api/v1/storage endpoint.
|
||||||
|
// So we must be extra careful here, or we might be showing errors on perfectly fine communication.
|
||||||
|
if (status == 0)
|
||||||
|
res = true;
|
||||||
|
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got storage: %2%") % name % body;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
// what if there is more structure added in the future? Enumerate all elements?
|
||||||
|
if (ptree.front().first != "storage_list") {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// each storage has own subtree of storage_list
|
||||||
|
for (const auto& section : ptree.front().second) {
|
||||||
|
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 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.free_space = space ? std::stoll(*space) : 1; // If free_space is missing, assume there is free space.
|
||||||
|
storage.emplace_back(std::move(si));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception&)
|
||||||
|
{
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
|
||||||
|
#endif // WIN32
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
for (const auto& si : storage) {
|
||||||
|
if (!si.read_only && si.free_space > 0)
|
||||||
|
output.push_back(si.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res && output.empty())
|
||||||
|
{
|
||||||
|
if (!storage.empty()) { // otherwise error_msg is already filled
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
std::string message = GUI::format(_L("Upload has failed. There is no suitable storage found at %1%.%2%"), m_host, error_msg);
|
||||||
|
BOOST_LOG_TRIVIAL(error) << message;
|
||||||
|
throw Slic3r::IOError(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
|
||||||
|
const char* name = get_name();
|
||||||
|
|
||||||
|
bool res = true;
|
||||||
|
auto url = make_url("api/version");
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
if (!ptree.get_optional<std::string>("api")) {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
use_put = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find capabilities subtree and read upload-by-put
|
||||||
|
for (const auto& section : ptree) {
|
||||||
|
if (section.first == "capabilities") {
|
||||||
|
const auto put_upload = section.second.get_optional<bool>("upload-by-put");
|
||||||
|
if (put_upload)
|
||||||
|
use_put = *put_upload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (const std::exception&) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.on_ip_resolve([&](std::string address) {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Remember resolved address to be reused at successive REST API call.
|
||||||
|
msg = GUI::from_u8(address);
|
||||||
|
})
|
||||||
|
#endif // WIN32
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_put) const
|
||||||
|
{
|
||||||
|
// Since the request is performed synchronously here,
|
||||||
|
// it is ok to refer to `msg` from within the closure
|
||||||
|
const char* name = get_name();
|
||||||
|
bool res = true;
|
||||||
|
// Msg contains ip string.
|
||||||
|
auto url = substitute_host(make_url("api/version"), GUI::into_u8(msg));
|
||||||
|
msg.Clear();
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
|
||||||
|
|
||||||
|
auto http = Http::get(url);//std::move(url));
|
||||||
|
set_auth(http);
|
||||||
|
http
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting version at %2% : %3%, HTTP %4%, body: `%5%`") % name % url % error % status % body;
|
||||||
|
res = false;
|
||||||
|
msg = format_error(body, error, status);
|
||||||
|
})
|
||||||
|
.on_complete([&, this](std::string body, unsigned) {
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Got version: %2%") % name % body;
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::stringstream ss(body);
|
||||||
|
pt::ptree ptree;
|
||||||
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
|
if (!ptree.get_optional<std::string>("api")) {
|
||||||
|
res = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
use_put = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find capabilities subtree and read upload-by-put
|
||||||
|
for (const auto& section : ptree) {
|
||||||
|
if (section.first == "capabilities") {
|
||||||
|
const auto put_upload = section.second.get_optional<bool>("upload-by-put");
|
||||||
|
if (put_upload)
|
||||||
|
use_put = *put_upload;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
catch (const std::exception&) {
|
||||||
|
res = false;
|
||||||
|
msg = "Could not parse server response";
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrusaLink::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
|
||||||
|
{
|
||||||
|
info_fn(L"resolve", boost::nowide::widen(resolved_addr.to_string()));
|
||||||
|
|
||||||
|
// If test fails, test_msg_or_host_ip contains the error message.
|
||||||
|
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||||
|
// Test_msg already contains resolved ip and will be cleared on start of test().
|
||||||
|
wxString test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
|
||||||
|
bool use_put = false;
|
||||||
|
if (!test_with_resolved_ip_and_method_check(test_msg_or_host_ip, use_put)) {
|
||||||
|
error_fn(std::move(test_msg_or_host_ip));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* name = get_name();
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
std::string storage_path = (use_put ? "api/v1/files" : "api/files");
|
||||||
|
storage_path += (upload_data.storage.empty() ? "/local" : upload_data.storage);
|
||||||
|
std::string url = substitute_host(make_url(storage_path), resolved_addr.to_string());
|
||||||
|
bool result = true;
|
||||||
|
info_fn(L"resolve", boost::nowide::widen(url));
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, method: %7%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||||
|
% (use_put ? "PUT" : "POST");
|
||||||
|
|
||||||
|
if (use_put)
|
||||||
|
return put_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
|
||||||
|
return post_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //WIN32
|
||||||
|
|
||||||
|
bool PrusaLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
|
{
|
||||||
|
const char* name = get_name();
|
||||||
|
|
||||||
|
const auto upload_filename = upload_data.upload_path.filename();
|
||||||
|
const auto upload_parent_path = upload_data.upload_path.parent_path();
|
||||||
|
|
||||||
|
// If test fails, test_msg_or_host_ip contains the error message.
|
||||||
|
// Otherwise on Windows it contains the resolved IP address of the host.
|
||||||
|
wxString test_msg_or_host_ip;
|
||||||
|
bool use_put = false;
|
||||||
|
if (!test_with_method_check(test_msg_or_host_ip, use_put)) {
|
||||||
|
error_fn(std::move(test_msg_or_host_ip));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
#endif // _WIN32
|
||||||
|
{
|
||||||
|
// If https is entered we assume signed ceritificate is being used
|
||||||
|
// IP resolving will not happen - it could resolve into address not being specified in cert
|
||||||
|
url = make_url(storage_path);
|
||||||
|
}
|
||||||
|
#ifdef WIN32
|
||||||
|
else {
|
||||||
|
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
|
||||||
|
// Curl uses easy_getinfo to get ip address of last successful transaction.
|
||||||
|
// If it got the address use it instead of the stored in "host" variable.
|
||||||
|
// This new address returns in "test_msg_or_host_ip" variable.
|
||||||
|
// Solves troubles of uploades failing with name address.
|
||||||
|
// in original address (m_host) replace host for resolved ip
|
||||||
|
info_fn(L"resolve", test_msg_or_host_ip);
|
||||||
|
url = substitute_host(make_url(storage_path), GUI::into_u8(test_msg_or_host_ip));
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||||
|
}
|
||||||
|
#endif // _WIN32
|
||||||
|
|
||||||
|
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% at %3%, filename: %4%, path: %5%, print: %6%, method: %7%")
|
||||||
|
% name
|
||||||
|
% upload_data.source_path
|
||||||
|
% url
|
||||||
|
% upload_filename.string()
|
||||||
|
% upload_parent_path.string()
|
||||||
|
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
|
||||||
|
% (use_put ? "PUT" : "POST");
|
||||||
|
|
||||||
|
if (use_put)
|
||||||
|
return put_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
|
||||||
|
return post_inner(std::move(upload_data), std::move(url), name, prorgess_fn, error_fn, info_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PrusaLink::put_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
|
{
|
||||||
|
info_fn(L"set_complete_off", wxString());
|
||||||
|
|
||||||
|
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));
|
||||||
|
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.set_put_body(upload_data.source_path)
|
||||||
|
.header("Content-Type", "text/x.gcode")
|
||||||
|
.header("Overwrite", "?1")
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
wxString widebody = wxString::FromUTF8(body);
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % widebody;
|
||||||
|
std::string message = m_show_after_message ? (boost::format("%1%") % widebody).str() : std::string();
|
||||||
|
info_fn(L"complete", message);
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
error_fn(format_error(body, error, status));
|
||||||
|
res = false;
|
||||||
|
})
|
||||||
|
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||||
|
prorgess_fn(std::move(progress), cancel);
|
||||||
|
if (cancel) {
|
||||||
|
// Upload was canceled
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
#endif
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
bool PrusaLink::post_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
|
{
|
||||||
|
info_fn(L"set_complete_off", wxString());
|
||||||
|
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));
|
||||||
|
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 ???
|
||||||
|
.form_add_file("file", upload_data.source_path.string(), upload_filename.string())
|
||||||
|
.on_complete([&](std::string body, unsigned status) {
|
||||||
|
if (m_show_after_message) {
|
||||||
|
// PrusaConnect message
|
||||||
|
wxString widebody = wxString::FromUTF8(body);
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%: %3%") % name % status % widebody;
|
||||||
|
std::string message = m_show_after_message ? (boost::format("%1%") % widebody).str() : std::string();
|
||||||
|
if (status == 202)
|
||||||
|
info_fn(L"complete_with_warning", message);
|
||||||
|
else
|
||||||
|
info_fn(L"complete", message);
|
||||||
|
} else {
|
||||||
|
// PrusaLink
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: File uploaded: HTTP %2%") % name % status;
|
||||||
|
info_fn(L"complete", wxString());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading file: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||||
|
error_fn(format_error(body, error, status));
|
||||||
|
res = false;
|
||||||
|
})
|
||||||
|
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||||
|
prorgess_fn(std::move(progress), cancel);
|
||||||
|
if (cancel) {
|
||||||
|
// Upload was canceled
|
||||||
|
BOOST_LOG_TRIVIAL(info) << "Octoprint: Upload canceled";
|
||||||
|
res = false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
#ifdef WIN32
|
||||||
|
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||||
|
#endif
|
||||||
|
.perform_sync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrusaLink::set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const
|
||||||
|
{
|
||||||
|
http.form_add("print", post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||||
|
}
|
||||||
|
|
||||||
|
PrusaConnect::PrusaConnect(DynamicPrintConfig* config)
|
||||||
|
: PrusaLink(config, true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrusaConnect::set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const
|
||||||
|
{
|
||||||
|
// Language for accept message
|
||||||
|
wxString wlang = GUI::wxGetApp().current_language_code();
|
||||||
|
std::string lang = GUI::format(wlang.SubString(0, 1));
|
||||||
|
http.header("Accept-Language", lang);
|
||||||
|
// Post action
|
||||||
|
if (post_action == PrintHostPostUploadAction::StartPrint) {
|
||||||
|
http.form_add("to_print", "True");
|
||||||
|
} else if (post_action == PrintHostPostUploadAction::QueuePrint) {
|
||||||
|
http.form_add("to_queue", "True");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <wx/string.h>
|
#include <wx/string.h>
|
||||||
#include <boost/optional.hpp>
|
#include <boost/optional.hpp>
|
||||||
|
#include <boost/asio/ip/address.hpp>
|
||||||
|
|
||||||
#include "PrintHost.hpp"
|
#include "PrintHost.hpp"
|
||||||
#include "libslic3r/PrintConfig.hpp"
|
#include "libslic3r/PrintConfig.hpp"
|
||||||
|
@ -22,10 +23,10 @@ public:
|
||||||
|
|
||||||
const char* get_name() const override;
|
const char* get_name() const override;
|
||||||
|
|
||||||
bool test(wxString &curl_msg) const override;
|
virtual bool test(wxString &curl_msg) const override;
|
||||||
wxString get_test_ok_msg () const override;
|
wxString get_test_ok_msg () const override;
|
||||||
wxString get_test_failed_msg (wxString &msg) const override;
|
wxString get_test_failed_msg (wxString &msg) const override;
|
||||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
bool has_auto_discovery() const override { return true; }
|
bool has_auto_discovery() const override { return true; }
|
||||||
bool can_test() const override { return true; }
|
bool can_test() const override { return true; }
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
@ -34,9 +35,12 @@ public:
|
||||||
const std::string& get_cafile() const { return m_cafile; }
|
const std::string& get_cafile() const { return m_cafile; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
#ifdef WIN32
|
||||||
|
virtual 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;
|
||||||
|
#endif
|
||||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||||
|
virtual bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||||
|
|
||||||
private:
|
|
||||||
std::string m_host;
|
std::string m_host;
|
||||||
std::string m_apikey;
|
std::string m_apikey;
|
||||||
std::string m_cafile;
|
std::string m_cafile;
|
||||||
|
@ -44,6 +48,11 @@ private:
|
||||||
|
|
||||||
virtual void set_auth(Http &http) const;
|
virtual void set_auth(Http &http) const;
|
||||||
std::string make_url(const std::string &path) const;
|
std::string make_url(const std::string &path) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
#ifdef WIN32
|
||||||
|
bool test_with_resolved_ip(wxString& curl_msg) const;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class SL1Host: public OctoPrint
|
class SL1Host: public OctoPrint
|
||||||
|
@ -74,51 +83,57 @@ private:
|
||||||
class PrusaLink : public OctoPrint
|
class PrusaLink : public OctoPrint
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PrusaLink(DynamicPrintConfig* config);
|
PrusaLink(DynamicPrintConfig* config) : PrusaLink(config, false) {}
|
||||||
|
PrusaLink(DynamicPrintConfig* config, bool show_after_message);
|
||||||
~PrusaLink() override = default;
|
~PrusaLink() override = default;
|
||||||
|
|
||||||
const char* get_name() const override;
|
const char* get_name() const override;
|
||||||
|
|
||||||
wxString get_test_ok_msg() const override;
|
wxString get_test_ok_msg() const override;
|
||||||
wxString get_test_failed_msg(wxString& msg) const override;
|
wxString get_test_failed_msg(wxString& msg) const override;
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
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;
|
||||||
protected:
|
protected:
|
||||||
|
bool test(wxString& curl_msg) const override;
|
||||||
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
||||||
|
bool upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
|
|
||||||
|
void set_auth(Http& http) const override;
|
||||||
|
virtual void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const;
|
||||||
|
#ifdef WIN32
|
||||||
|
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
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void set_auth(Http& http) const override;
|
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;
|
||||||
|
bool post_inner(PrintHostUpload upload_data, std::string url, const std::string& name, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const;
|
||||||
|
#ifdef WIN32
|
||||||
|
bool test_with_resolved_ip_and_method_check(wxString& curl_msg, bool& use_put) const;
|
||||||
|
#endif
|
||||||
// Host authorization type.
|
// Host authorization type.
|
||||||
AuthorizationType m_authorization_type;
|
AuthorizationType m_authorization_type;
|
||||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
||||||
std::string m_username;
|
std::string m_username;
|
||||||
std::string m_password;
|
std::string m_password;
|
||||||
|
bool m_show_after_message;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
bool version_check(const boost::optional<std::string>& version_text) const;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class PrusaLink : public OctoPrint
|
class PrusaConnect : public PrusaLink
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PrusaLink(DynamicPrintConfig* config);
|
PrusaConnect(DynamicPrintConfig* config);
|
||||||
~PrusaLink() override = default;
|
~PrusaConnect() override = default;
|
||||||
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; }
|
||||||
const char* get_name() const override;
|
const char* get_name() const override { return "PrusaConnect"; }
|
||||||
|
|
||||||
wxString get_test_ok_msg() const override;
|
|
||||||
wxString get_test_failed_msg(wxString& msg) const override;
|
|
||||||
bool can_start_print() const override { return true; }
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
bool validate_version_text(const boost::optional<std::string>& version_text) const override;
|
void set_http_post_header_args(Http& http, PrintHostPostUploadAction post_action) const override;
|
||||||
|
|
||||||
private:
|
|
||||||
void set_auth(Http& http) const override;
|
|
||||||
|
|
||||||
// Host authorization type.
|
|
||||||
AuthorizationType authorization_type;
|
|
||||||
// username and password for HTTP Digest Authentization (RFC RFC2617)
|
|
||||||
std::string username;
|
|
||||||
std::string password;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,6 +52,7 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||||
case htAstroBox: return new AstroBox(config);
|
case htAstroBox: return new AstroBox(config);
|
||||||
case htRepetier: return new Repetier(config);
|
case htRepetier: return new Repetier(config);
|
||||||
case htPrusaLink: return new PrusaLink(config);
|
case htPrusaLink: return new PrusaLink(config);
|
||||||
|
case htPrusaConnect: return new PrusaConnect(config);
|
||||||
case htMKS: return new MKS(config);
|
case htMKS: return new MKS(config);
|
||||||
default: return nullptr;
|
default: return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -93,10 +94,13 @@ struct PrintHostJobQueue::priv
|
||||||
void emit_progress(int progress);
|
void emit_progress(int progress);
|
||||||
void emit_error(wxString error);
|
void emit_error(wxString error);
|
||||||
void emit_cancel(size_t id);
|
void emit_cancel(size_t id);
|
||||||
|
void emit_info(wxString tag, wxString status);
|
||||||
void start_bg_thread();
|
void start_bg_thread();
|
||||||
void stop_bg_thread();
|
void stop_bg_thread();
|
||||||
void bg_thread_main();
|
void bg_thread_main();
|
||||||
void progress_fn(Http::Progress progress, bool &cancel);
|
void progress_fn(Http::Progress progress, bool &cancel);
|
||||||
|
void error_fn(wxString error);
|
||||||
|
void info_fn(wxString tag, wxString status);
|
||||||
void remove_source(const fs::path &path);
|
void remove_source(const fs::path &path);
|
||||||
void remove_source();
|
void remove_source();
|
||||||
void perform_job(PrintHostJob the_job);
|
void perform_job(PrintHostJob the_job);
|
||||||
|
@ -125,6 +129,12 @@ void PrintHostJobQueue::priv::emit_error(wxString error)
|
||||||
wxQueueEvent(queue_dialog, evt);
|
wxQueueEvent(queue_dialog, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::emit_info(wxString tag, wxString status)
|
||||||
|
{
|
||||||
|
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_INFO, queue_dialog->GetId(), job_id, std::move(tag), std::move(status));
|
||||||
|
wxQueueEvent(queue_dialog, evt);
|
||||||
|
}
|
||||||
|
|
||||||
void PrintHostJobQueue::priv::emit_cancel(size_t id)
|
void PrintHostJobQueue::priv::emit_cancel(size_t id)
|
||||||
{
|
{
|
||||||
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id);
|
auto evt = new PrintHostQueueDialog::Event(GUI::EVT_PRINTHOST_CANCEL, queue_dialog->GetId(), id);
|
||||||
|
@ -233,6 +243,40 @@ void PrintHostJobQueue::priv::progress_fn(Http::Progress progress, bool &cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::error_fn(wxString error)
|
||||||
|
{
|
||||||
|
// check if transfer was not canceled before error occured - than do not show the error
|
||||||
|
bool do_emit_err = true;
|
||||||
|
if (channel_cancels.size_hint() > 0) {
|
||||||
|
// Lock both queues
|
||||||
|
auto cancels = channel_cancels.lock_rw();
|
||||||
|
auto jobs = channel_jobs.lock_rw();
|
||||||
|
|
||||||
|
for (size_t cancel_id : *cancels) {
|
||||||
|
if (cancel_id == job_id) {
|
||||||
|
do_emit_err = false;
|
||||||
|
emit_cancel(job_id);
|
||||||
|
}
|
||||||
|
else if (cancel_id > job_id) {
|
||||||
|
const size_t idx = cancel_id - job_id - 1;
|
||||||
|
if (idx < jobs->size()) {
|
||||||
|
jobs->at(idx).cancelled = true;
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << boost::format("PrintHostJobQueue: Job id %1% cancelled") % cancel_id;
|
||||||
|
emit_cancel(cancel_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cancels->clear();
|
||||||
|
}
|
||||||
|
if (do_emit_err)
|
||||||
|
emit_error(std::move(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
void PrintHostJobQueue::priv::info_fn(wxString tag, wxString status)
|
||||||
|
{
|
||||||
|
emit_info(tag, status);
|
||||||
|
}
|
||||||
|
|
||||||
void PrintHostJobQueue::priv::remove_source(const fs::path &path)
|
void PrintHostJobQueue::priv::remove_source(const fs::path &path)
|
||||||
{
|
{
|
||||||
if (! path.empty()) {
|
if (! path.empty()) {
|
||||||
|
@ -256,9 +300,8 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
||||||
|
|
||||||
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
bool success = the_job.printhost->upload(std::move(the_job.upload_data),
|
||||||
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
[this](Http::Progress progress, bool &cancel) { this->progress_fn(std::move(progress), cancel); },
|
||||||
[this](wxString error) {
|
[this](wxString error) { this->error_fn(std::move(error)); },
|
||||||
emit_error(std::move(error));
|
[this](wxString tag, wxString host) { this->info_fn(std::move(tag), std::move(host)); }
|
||||||
}
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
|
@ -21,7 +21,8 @@ class DynamicPrintConfig;
|
||||||
enum class PrintHostPostUploadAction {
|
enum class PrintHostPostUploadAction {
|
||||||
None,
|
None,
|
||||||
StartPrint,
|
StartPrint,
|
||||||
StartSimulation
|
StartSimulation,
|
||||||
|
QueuePrint
|
||||||
};
|
};
|
||||||
using PrintHostPostUploadActions = enum_bitmask<PrintHostPostUploadAction>;
|
using PrintHostPostUploadActions = enum_bitmask<PrintHostPostUploadAction>;
|
||||||
ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
|
ENABLE_ENUM_BITMASK_OPERATORS(PrintHostPostUploadAction);
|
||||||
|
@ -32,6 +33,7 @@ struct PrintHostUpload
|
||||||
boost::filesystem::path upload_path;
|
boost::filesystem::path upload_path;
|
||||||
|
|
||||||
std::string group;
|
std::string group;
|
||||||
|
std::string storage;
|
||||||
|
|
||||||
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
|
PrintHostPostUploadAction post_action { PrintHostPostUploadAction::None };
|
||||||
};
|
};
|
||||||
|
@ -43,13 +45,14 @@ public:
|
||||||
|
|
||||||
typedef Http::ProgressFn ProgressFn;
|
typedef Http::ProgressFn ProgressFn;
|
||||||
typedef std::function<void(wxString /* error */)> ErrorFn;
|
typedef std::function<void(wxString /* error */)> ErrorFn;
|
||||||
|
typedef std::function<void(wxString /* tag */, wxString /* status */)> InfoFn;
|
||||||
|
|
||||||
virtual const char* get_name() const = 0;
|
virtual const char* get_name() const = 0;
|
||||||
|
|
||||||
virtual bool test(wxString &curl_msg) const = 0;
|
virtual bool test(wxString &curl_msg) const = 0;
|
||||||
virtual wxString get_test_ok_msg () const = 0;
|
virtual wxString get_test_ok_msg () const = 0;
|
||||||
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
|
virtual wxString get_test_failed_msg (wxString &msg) const = 0;
|
||||||
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const = 0;
|
virtual bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const = 0;
|
||||||
virtual bool has_auto_discovery() const = 0;
|
virtual bool has_auto_discovery() const = 0;
|
||||||
virtual bool can_test() const = 0;
|
virtual bool can_test() const = 0;
|
||||||
virtual PrintHostPostUploadActions get_post_upload_actions() const = 0;
|
virtual PrintHostPostUploadActions get_post_upload_actions() const = 0;
|
||||||
|
@ -61,6 +64,9 @@ public:
|
||||||
// Returns false if not supported. May throw HostNetworkError.
|
// Returns false if not supported. May throw HostNetworkError.
|
||||||
virtual bool get_groups(wxArrayString & /* groups */) const { return false; }
|
virtual bool get_groups(wxArrayString & /* groups */) const { return false; }
|
||||||
virtual bool get_printers(wxArrayString & /* printers */) const { return false; }
|
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; }
|
||||||
|
|
||||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,24 @@ Repetier::Repetier(DynamicPrintConfig *config) :
|
||||||
|
|
||||||
const char* Repetier::get_name() const { return "Repetier"; }
|
const char* Repetier::get_name() const { return "Repetier"; }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static bool validate_repetier(const boost::optional<std::string>& name,
|
||||||
|
const boost::optional<std::string>& soft)
|
||||||
|
{
|
||||||
|
if (soft) {
|
||||||
|
// See https://github.com/prusa3d/PrusaSlicer/issues/7807:
|
||||||
|
// Repetier allows "rebranding", so the "name" value is not reliable when detecting
|
||||||
|
// server type. Newer Repetier versions send "software", which should be invariant.
|
||||||
|
return ((*soft) == "Repetier-Server");
|
||||||
|
} else {
|
||||||
|
// If there is no "software" value, validate as we did before:
|
||||||
|
return name ? boost::starts_with(*name, "Repetier") : true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
bool Repetier::test(wxString &msg) const
|
bool Repetier::test(wxString &msg) const
|
||||||
{
|
{
|
||||||
// Since the request is performed synchronously here,
|
// Since the request is performed synchronously here,
|
||||||
|
@ -64,9 +82,10 @@ bool Repetier::test(wxString &msg) const
|
||||||
pt::read_json(ss, ptree);
|
pt::read_json(ss, ptree);
|
||||||
|
|
||||||
const auto text = ptree.get_optional<std::string>("name");
|
const auto text = ptree.get_optional<std::string>("name");
|
||||||
res = validate_version_text(text);
|
const auto soft = ptree.get_optional<std::string>("software");
|
||||||
|
res = validate_repetier(text, soft);
|
||||||
if (! res) {
|
if (! res) {
|
||||||
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (text ? *text : "Repetier")).str());
|
msg = GUI::from_u8((boost::format(_utf8(L("Mismatched type of print host: %s"))) % (soft ? *soft : (text ? *text : "Repetier"))).str());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const std::exception &) {
|
catch (const std::exception &) {
|
||||||
|
@ -92,7 +111,7 @@ wxString Repetier::get_test_failed_msg (wxString &msg) const
|
||||||
% _utf8(L("Note: Repetier version at least 0.90.0 is required."))).str());
|
% _utf8(L("Note: Repetier version at least 0.90.0 is required."))).str());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const
|
bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||||
{
|
{
|
||||||
const char *name = get_name();
|
const char *name = get_name();
|
||||||
|
|
||||||
|
@ -129,6 +148,7 @@ bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error
|
||||||
|
|
||||||
if(upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
if(upload_data.post_action == PrintHostPostUploadAction::StartPrint) {
|
||||||
http.form_add("name", upload_filename.string());
|
http.form_add("name", upload_filename.string());
|
||||||
|
http.form_add("autostart", "true"); // See https://github.com/prusa3d/PrusaSlicer/issues/7807#issuecomment-1235519371
|
||||||
}
|
}
|
||||||
|
|
||||||
http.form_add("a", "upload")
|
http.form_add("a", "upload")
|
||||||
|
@ -154,10 +174,7 @@ bool Repetier::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, Error
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Repetier::validate_version_text(const boost::optional<std::string> &version_text) const
|
|
||||||
{
|
|
||||||
return version_text ? (!version_text->empty()) : true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Repetier::set_auth(Http &http) const
|
void Repetier::set_auth(Http &http) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -23,7 +23,7 @@ public:
|
||||||
bool test(wxString &curl_msg) const override;
|
bool test(wxString &curl_msg) const override;
|
||||||
wxString get_test_ok_msg () const override;
|
wxString get_test_ok_msg () const override;
|
||||||
wxString get_test_failed_msg (wxString &msg) const override;
|
wxString get_test_failed_msg (wxString &msg) const override;
|
||||||
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn) const override;
|
bool upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||||
bool has_auto_discovery() const override { return false; }
|
bool has_auto_discovery() const override { return false; }
|
||||||
bool can_test() const override { return true; }
|
bool can_test() const override { return true; }
|
||||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||||
|
@ -33,9 +33,6 @@ public:
|
||||||
bool get_groups(wxArrayString &groups) const override;
|
bool get_groups(wxArrayString &groups) const override;
|
||||||
bool get_printers(wxArrayString &printers) const override;
|
bool get_printers(wxArrayString &printers) const override;
|
||||||
|
|
||||||
protected:
|
|
||||||
virtual bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string host;
|
std::string host;
|
||||||
std::string apikey;
|
std::string apikey;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue