mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-11 03:57:39 +00:00
Merge branch 'main' into pr/grant0013/13752
This commit is contained in:
667
src/slic3r/Utils/3DPrinterOS.cpp
Executable file
667
src/slic3r/Utils/3DPrinterOS.cpp
Executable file
@@ -0,0 +1,667 @@
|
||||
#include "3DPrinterOS.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
|
||||
#include <wx/progdlg.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/event.h>
|
||||
#include <wx/dialog.h>
|
||||
#include <wx/radiobut.h>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "slic3r/GUI/GUI_Utils.hpp"
|
||||
#include "slic3r/GUI/MsgDialog.hpp"
|
||||
#include "slic3r/GUI/Widgets/ComboBox.hpp"
|
||||
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
|
||||
#include "Http.hpp"
|
||||
#include <wx/busyinfo.h>
|
||||
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace {
|
||||
|
||||
class UploadOptionsDialog : public Slic3r::GUI::DPIDialog
|
||||
{
|
||||
public:
|
||||
UploadOptionsDialog(wxWindow* parent,
|
||||
const wxArrayString& cloud_projects,
|
||||
const wxArrayString& cloud_printer_types,
|
||||
const wxString preset_name)
|
||||
: Slic3r::GUI::DPIDialog(parent,
|
||||
wxID_ANY,
|
||||
_L("3DPrinterOS Cloud upload options"),
|
||||
wxDefaultPosition,
|
||||
wxSize(100 * Slic3r::GUI::wxGetApp().em_unit(), -1),
|
||||
wxDEFAULT_DIALOG_STYLE),
|
||||
okButton(nullptr)
|
||||
{
|
||||
SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetForegroundColour(*wxBLACK);
|
||||
|
||||
singleRadio = new wxRadioButton(this, wxID_ANY, _L("Single file"), wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
|
||||
projectRadio = new wxRadioButton(this, wxID_ANY, _L("Project File"));
|
||||
projectsLabel = new wxStaticText(this, wxID_ANY, _L("Project:"));
|
||||
wxStaticText* printerLabel = new wxStaticText(this, wxID_ANY, _L("Printer type:"));
|
||||
|
||||
projectsComboBox = new wxComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, DD_NO_CHECK_ICON);
|
||||
printerTypeComboBox = new wxComboBox(this, wxID_ANY, wxString(""), wxDefaultPosition, wxDefaultSize, 0, nullptr, DD_NO_CHECK_ICON | wxTE_READONLY);
|
||||
|
||||
printerWarningLabel = new wxStaticText(this, wxID_ANY, _L("Printer type not found, please select manually."));
|
||||
printerWarningLabel->SetForegroundColour(*wxRED);
|
||||
printerWarningLabel->Hide();
|
||||
|
||||
for (int i = 0; i < cloud_projects.size(); i++) {
|
||||
projectsComboBox->Append(cloud_projects[i]);
|
||||
}
|
||||
if (cloud_printer_types.size() > 0) {
|
||||
for (int i = 0; i < cloud_printer_types.size(); i++) {
|
||||
printerTypeComboBox->Append(cloud_printer_types[i]);
|
||||
if (cloud_printer_types[i].Find(preset_name) != wxNOT_FOUND && printerTypeComboBox->GetSelection() == -1) {
|
||||
printerTypeComboBox->SetSelection(i);
|
||||
}
|
||||
}
|
||||
if (printerTypeComboBox->GetCount() > 1) {
|
||||
printerWarningLabel->Show();
|
||||
} else {
|
||||
printerTypeComboBox->SetSelection(0);
|
||||
}
|
||||
}
|
||||
|
||||
okButton = new wxButton(this, wxID_OK, _L("OK"));
|
||||
wxButton* cancelButton = new wxButton(this, wxID_CANCEL, _L("Cancel"));
|
||||
|
||||
wxBoxSizer* radioSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
wxBoxSizer* btnSizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
radioSizer->Add(singleRadio, 0, wxALL, 5);
|
||||
radioSizer->Add(projectRadio, 0, wxALL, 5);
|
||||
btnSizer->Add(okButton, 0, wxALL | wxALIGN_CENTER, 5);
|
||||
btnSizer->Add(cancelButton, 0, wxALL | wxALIGN_CENTER, 5);
|
||||
|
||||
wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(radioSizer, 0, wxALL, 5);
|
||||
sizer->Add(projectsLabel, 0, wxALL, 5);
|
||||
sizer->Add(projectsComboBox, 0, wxALL | wxEXPAND, 5);
|
||||
sizer->Add(printerLabel, 0, wxALL, 5);
|
||||
sizer->Add(printerTypeComboBox, 0, wxALL | wxEXPAND, 5);
|
||||
sizer->Add(printerWarningLabel, 0, wxLEFT | wxRIGHT | wxBOTTOM, 5);
|
||||
sizer->Add(btnSizer, 0, wxALL | wxALIGN_CENTER, 5);
|
||||
SetSizer(sizer);
|
||||
sizer->Fit(this);
|
||||
projectsComboBox->Hide();
|
||||
projectsLabel->Hide();
|
||||
projectRadio->Bind(wxEVT_RADIOBUTTON, &UploadOptionsDialog::OnRadioButtonSelected, this);
|
||||
singleRadio->Bind(wxEVT_RADIOBUTTON, &UploadOptionsDialog::OnRadioButtonSelected, this);
|
||||
// Bind combo box selection change to validation
|
||||
printerTypeComboBox->Bind(wxEVT_COMBOBOX, &UploadOptionsDialog::OnPrinterTypeChanged, this);
|
||||
ValidateOkButton(); // Initial validation
|
||||
Slic3r::GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
CenterOnParent();
|
||||
}
|
||||
|
||||
void OnRadioButtonSelected(wxCommandEvent& event)
|
||||
{
|
||||
wxRadioButton* selectedRadio = dynamic_cast<wxRadioButton*>(event.GetEventObject());
|
||||
if (selectedRadio) {
|
||||
wxString label = selectedRadio->GetLabel();
|
||||
if (label == _L("Project File")) {
|
||||
projectsComboBox->Show();
|
||||
projectsLabel->Show();
|
||||
} else {
|
||||
projectsComboBox->Hide();
|
||||
projectsLabel->Hide();
|
||||
}
|
||||
Layout();
|
||||
}
|
||||
}
|
||||
|
||||
void on_dpi_changed(const wxRect& suggested_rect) {}
|
||||
|
||||
void OnPrinterTypeChanged(wxCommandEvent& event)
|
||||
{
|
||||
ValidateOkButton();
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void ValidateOkButton()
|
||||
{
|
||||
bool hasSelection = (printerTypeComboBox->GetSelection() != wxNOT_FOUND);
|
||||
okButton->Enable(hasSelection);
|
||||
}
|
||||
|
||||
void GetValues(std::string& project, std::string& printer_type)
|
||||
{
|
||||
project = projectRadio->GetValue() ? std::string(projectsComboBox->GetValue().c_str()) : "";
|
||||
printer_type = std::string(printerTypeComboBox->GetValue().c_str());
|
||||
}
|
||||
|
||||
private:
|
||||
wxComboBox* projectsComboBox;
|
||||
wxComboBox* printerTypeComboBox;
|
||||
wxStaticText* projectsLabel;
|
||||
wxStaticText* printerWarningLabel;
|
||||
wxRadioButton* singleRadio;
|
||||
wxRadioButton* projectRadio;
|
||||
wxButton* okButton;
|
||||
};
|
||||
|
||||
|
||||
class TokenAuthDialog : public Slic3r::GUI::DPIDialog
|
||||
{
|
||||
public:
|
||||
TokenAuthDialog(wxWindow* parent, const std::string &url, const std::string& token, const std::string &cafile, pt::ptree& resp)
|
||||
: Slic3r::GUI::DPIDialog(parent,
|
||||
wxID_ANY,
|
||||
"3DPrinterOS",
|
||||
wxDefaultPosition,
|
||||
wxSize(45 * Slic3r::GUI::wxGetApp().em_unit(), -1),
|
||||
wxDEFAULT_DIALOG_STYLE)
|
||||
, m_url(url)
|
||||
, m_token(token)
|
||||
, m_cafile(cafile)
|
||||
, m_resp(resp)
|
||||
{
|
||||
SetFont(Slic3r::GUI::wxGetApp().normal_font());
|
||||
SetBackgroundColour(*wxWHITE);
|
||||
SetForegroundColour(*wxBLACK);
|
||||
|
||||
auto* sizer = new wxBoxSizer(wxVERTICAL);
|
||||
sizer->Add(new wxStaticText(this, wxID_ANY, _L("Authorizing...")), 1, wxALL | wxCENTER, 10);
|
||||
auto* cancelBtn = new wxButton(this, wxID_CANCEL, _L("Cancel"));
|
||||
sizer->Add(cancelBtn, 0, wxALL | wxALIGN_CENTER, 10);
|
||||
SetSizerAndFit(sizer);
|
||||
|
||||
Bind(wxEVT_THREAD, [this](wxThreadEvent& e) { EndModal(e.GetId()); });
|
||||
Bind(wxEVT_TIMER, &TokenAuthDialog::OnRetry, this);
|
||||
Bind(wxEVT_SHOW, &TokenAuthDialog::OnShow, this);
|
||||
Bind(wxEVT_BUTTON, &TokenAuthDialog::OnCancel, this, wxID_CANCEL);
|
||||
|
||||
m_timer.SetOwner(this);
|
||||
Slic3r::GUI::wxGetApp().UpdateDlgDarkUI(this);
|
||||
CenterOnParent();
|
||||
}
|
||||
|
||||
void on_dpi_changed(const wxRect& suggested_rect) {}
|
||||
|
||||
private:
|
||||
|
||||
void OnShow(wxShowEvent& event)
|
||||
{
|
||||
if (event.IsShown() && !m_started) {
|
||||
m_started = true;
|
||||
SendRequest();
|
||||
}
|
||||
event.Skip();
|
||||
}
|
||||
|
||||
void OnCancel(wxCommandEvent&)
|
||||
{
|
||||
m_cancelled = true;
|
||||
if (m_http_ptr) {
|
||||
m_http_ptr->cancel(); // abort the background request
|
||||
}
|
||||
EndModal(wxID_CANCEL);
|
||||
}
|
||||
|
||||
void OnRetry(wxTimerEvent&) { SendRequest(); }
|
||||
|
||||
void SendRequest()
|
||||
{
|
||||
if (m_cancelled || m_attempt >= m_max_retries) {
|
||||
if (m_attempt >= m_max_retries) {
|
||||
m_resp.put("result", false);
|
||||
m_resp.put("message", "Maximum login retries exceeded");
|
||||
}
|
||||
wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD, wxID_ABORT));
|
||||
return;
|
||||
}
|
||||
|
||||
m_attempt++;
|
||||
std::string postBody = "token=" + m_token;
|
||||
auto http = Slic3r::Http::post(m_url);
|
||||
http.timeout_max(60);
|
||||
if (!m_cafile.empty()) {
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
http.header("Content-Length", std::to_string(postBody.size()));
|
||||
http.set_post_body(postBody);
|
||||
http.on_error([this](std::string, std::string error, unsigned status) {
|
||||
if (!m_cancelled) {
|
||||
m_resp.put("result", false);
|
||||
m_resp.put("message", (status != 200) ? "HTTP error: " + std::to_string(status) : error);
|
||||
wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD, wxID_ABORT));
|
||||
}
|
||||
})
|
||||
.on_complete([this](std::string body, unsigned status) {
|
||||
if (!m_cancelled) {
|
||||
if (status != 200) {
|
||||
m_resp.put("result", false);
|
||||
m_resp.put("message", "HTTP error: " + std::to_string(status));
|
||||
wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD, wxID_ABORT));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::read_json(ss, m_resp);
|
||||
} catch (...) {
|
||||
m_resp.put("result", false);
|
||||
m_resp.put("message", "Could not parse server response");
|
||||
}
|
||||
|
||||
if (m_resp.get<bool>("result", false) && m_resp.get_optional<std::string>("message.session").has_value()) {
|
||||
wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD, wxID_OK));
|
||||
} else if (m_resp.get<bool>("result", false)) {
|
||||
if (m_attempt < m_max_retries)
|
||||
m_timer.StartOnce(m_retry_delay_ms);
|
||||
else
|
||||
wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD, wxID_ABORT));
|
||||
} else {
|
||||
wxQueueEvent(this, new wxThreadEvent(wxEVT_THREAD, wxID_ABORT));
|
||||
}
|
||||
}
|
||||
});
|
||||
m_http_ptr = http.perform();
|
||||
}
|
||||
|
||||
private:
|
||||
std::string m_token;
|
||||
std::string m_url;
|
||||
std::string m_cafile;
|
||||
pt::ptree& m_resp;
|
||||
wxTimer m_timer;
|
||||
std::shared_ptr<Slic3r::Http> m_http_ptr;
|
||||
bool m_cancelled{false};
|
||||
bool m_started{false};
|
||||
int m_attempt{0};
|
||||
const int m_max_retries{10};
|
||||
const int m_retry_delay_ms{500};
|
||||
};
|
||||
} // namespace
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
static const std::string API_CREDENTIALS_PATH = "3dprinteros_api_cred.json";
|
||||
|
||||
C3DPrinterOS::C3DPrinterOS(DynamicPrintConfig *config)
|
||||
: m_host(config->opt_string("print_host"))
|
||||
, m_apikey(config->opt_string("printhost_apikey"))
|
||||
, m_preset_name(config->opt_string("printer_model"))
|
||||
{
|
||||
m_api_session_file_path = (boost::filesystem::path(Slic3r::data_dir()) / API_CREDENTIALS_PATH)
|
||||
.make_preferred()
|
||||
.string();
|
||||
load_api_session();
|
||||
}
|
||||
|
||||
const char *C3DPrinterOS::get_name() const { return "3DPrinterOS"; }
|
||||
|
||||
bool C3DPrinterOS::test(wxString &msg) const
|
||||
{
|
||||
return check_session(msg);
|
||||
}
|
||||
|
||||
bool C3DPrinterOS::login(wxString& msg) const
|
||||
{
|
||||
// Get token for auth
|
||||
msg.clear();
|
||||
std::string token = get_api_auth_token(msg);
|
||||
if (token.empty()) {
|
||||
msg = "Error. Can't get api token for authorization";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto login_url = make_url("noauth/apiglobal_login_with_token/" + token);
|
||||
wxLaunchDefaultBrowser(login_url);
|
||||
pt::ptree login_resp;
|
||||
login_with_token(login_resp, token);
|
||||
std::string session, email;
|
||||
try {
|
||||
if (login_resp.get<bool>("result")) {
|
||||
session = login_resp.get<std::string>("message.session");
|
||||
email = login_resp.get<std::string>("message.email");
|
||||
} else {
|
||||
msg = wxString(login_resp.get<std::string>("message").c_str());
|
||||
return false;
|
||||
}
|
||||
} catch (const std::exception&) {
|
||||
msg = "Could not parse server response";
|
||||
return false;
|
||||
}
|
||||
bool res = save_api_session(session, email);
|
||||
if (!res) {
|
||||
msg = "Error saving session to file";
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
wxString C3DPrinterOS::get_test_ok_msg() const
|
||||
{
|
||||
return _("Connection to 3DPrinterOS cloud works correctly.") + (!m_username.empty() ? "" + _(" Logined as user: ") + m_username : "");
|
||||
}
|
||||
|
||||
wxString C3DPrinterOS::get_test_failed_msg(wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s\n\n", _L("Error session check"), msg);
|
||||
}
|
||||
|
||||
bool C3DPrinterOS::upload(
|
||||
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();
|
||||
wxString test_msg;
|
||||
if (!check_session(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
pt::ptree cloud_project_resp;
|
||||
pt::ptree cloud_printer_types_resp;
|
||||
|
||||
get_cloud_projects_list(cloud_project_resp);
|
||||
get_cloud_printer_types(cloud_printer_types_resp, m_preset_name);
|
||||
wxArrayString cloud_projects_list;
|
||||
wxArrayString cloud_printer_types_list;
|
||||
|
||||
try {
|
||||
if (cloud_project_resp.get<bool>("result")) {
|
||||
for (const auto &messageItem : cloud_project_resp.get_child("message")) {
|
||||
cloud_projects_list.Add(messageItem.second.get<std::string>("name"));
|
||||
}
|
||||
}
|
||||
|
||||
if (cloud_printer_types_resp.get<bool>("result")) {
|
||||
for (const auto &messageItem : cloud_printer_types_resp.get_child("message")) {
|
||||
cloud_printer_types_list.Add(messageItem.second.get<std::string>("description"));
|
||||
}
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
error_fn("Could not parse server response");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Show "Confirm cloud printer type and project for 3DPrinterOS upload
|
||||
|
||||
UploadOptionsDialog dlg(GUI::wxGetApp().GetTopWindow(), cloud_projects_list, cloud_printer_types_list, m_preset_name);
|
||||
|
||||
if (dlg.ShowModal() != wxID_OK) {
|
||||
error_fn("Canceled");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string selected_project;
|
||||
std::string selected_printer_type;
|
||||
dlg.GetValues(selected_project, selected_printer_type);
|
||||
std::string project_id;
|
||||
std::string printer_type_id;
|
||||
|
||||
// search for cloud project_id by name
|
||||
if (!selected_project.empty()) {
|
||||
for (const auto& messageItem : cloud_project_resp.get_child("message")) {
|
||||
if (messageItem.second.get<std::string>("name", "") == selected_project) {
|
||||
project_id = messageItem.second.get<std::string>("id", "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// search for cloud printer_type_id by name
|
||||
for (const auto& messageItem : cloud_printer_types_resp.get_child("message")) {
|
||||
if (messageItem.second.get<std::string>("description", "") == selected_printer_type) {
|
||||
printer_type_id = messageItem.second.get<std::string>("id", "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool res = true;
|
||||
auto url = make_url("apiglobal/upload");
|
||||
std::string file_id;
|
||||
pt::ptree uploadResponse;
|
||||
auto http = Http::post(std::move(url));
|
||||
if (!m_cafile.empty()) {
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
http.form_add("session", m_apikey)
|
||||
.form_add("upload_type_id", "7")
|
||||
.form_add("upload_soft_name", "OrcaSlicer")
|
||||
.form_add("zip", "false")
|
||||
.form_add_file("file", upload_data.source_path.string(), upload_filename.string());
|
||||
|
||||
if (!project_id.empty()) {
|
||||
http.form_add("project_id", project_id);
|
||||
} else if (!selected_project.empty()) {
|
||||
http.form_add("project_name", selected_project);
|
||||
http.form_add("project_color", "grey");
|
||||
}
|
||||
|
||||
http.on_complete([&](std::string body, unsigned status) {
|
||||
std::stringstream ss(body);
|
||||
try {
|
||||
pt::read_json(ss, uploadResponse);
|
||||
} catch (const std::exception &) {
|
||||
uploadResponse.put("result", false);
|
||||
uploadResponse.put("message", "Could not parse server response");
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
error_fn(format_error(body, error, status));
|
||||
res = false;
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool &cancel) {
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
res = false;
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
try {
|
||||
if (uploadResponse.get<bool>("result")) {
|
||||
file_id = uploadResponse.get<std::string>("message.file_id");
|
||||
} else {
|
||||
res = false;
|
||||
error_fn(uploadResponse.get<std::string>("message"));
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
res = false;
|
||||
error_fn("Error during file upload");
|
||||
}
|
||||
// set printer type for uploaded gcode
|
||||
if (res) {
|
||||
pt::ptree update_file_response;
|
||||
update_file(update_file_response, file_id, printer_type_id, "OrcaSlicer");
|
||||
try {
|
||||
if (!update_file_response.get<bool>("result")) {
|
||||
const std::string msg = update_file_response.get<std::string>("message", "Unknown update error");
|
||||
BOOST_LOG_TRIVIAL(warning) << "Failed to update uploaded file: " << msg;
|
||||
}
|
||||
} catch (const std::exception& ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "Could not parse update response: " << ex.what();
|
||||
}
|
||||
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint && !upload_data.use_3mf) {
|
||||
auto quick_print_url = make_url("quickprint?file_id=" + file_id);
|
||||
wxLaunchDefaultBrowser(quick_print_url);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void C3DPrinterOS::log_out() const
|
||||
{
|
||||
boost::filesystem::remove(m_api_session_file_path.c_str());
|
||||
}
|
||||
|
||||
bool C3DPrinterOS::validate_version_text(const boost::optional<std::string> &version_text) const
|
||||
{
|
||||
return version_text ? boost::starts_with(*version_text, "3DPrinterOS") : true;
|
||||
}
|
||||
|
||||
std::string C3DPrinterOS::make_url(const std::string &path) const
|
||||
{
|
||||
if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
|
||||
if (m_host.back() == '/') {
|
||||
return (boost::format("%1%%2%") % m_host % path).str();
|
||||
} else {
|
||||
return (boost::format("%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
} else {
|
||||
return (boost::format("https://%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
}
|
||||
|
||||
std::string C3DPrinterOS::get_api_auth_token(wxString &err) const
|
||||
{
|
||||
std::string result;
|
||||
pt::ptree resp;
|
||||
std::string postBody = "app_type=plugin&app_name=" + Http::url_encode("OrcaSlicer");
|
||||
send_form("apiglobal/generate_login_token", postBody, resp);
|
||||
try {
|
||||
if (resp.get<bool>("result")) {
|
||||
result = resp.get<std::string>("message");
|
||||
} else {
|
||||
err = wxString(resp.get<std::string>("message").c_str());
|
||||
}
|
||||
} catch (const std::exception &) {
|
||||
err = "Could not parse server response";
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void C3DPrinterOS::login_with_token(pt::ptree &resp, const std::string &token) const {
|
||||
auto url = make_url("apiglobal/login_with_token");
|
||||
TokenAuthDialog dlg(GUI::wxGetApp().GetTopWindow(), url, token, m_cafile, resp);
|
||||
dlg.ShowModal();
|
||||
}
|
||||
|
||||
bool C3DPrinterOS::check_session(wxString &msg) const {
|
||||
std::string postBody = "session=" + m_apikey;
|
||||
pt::ptree resp;
|
||||
send_form("apiglobal/check_session", postBody, resp);
|
||||
try {
|
||||
if (resp.get<bool>("result")) {
|
||||
return true;
|
||||
} else {
|
||||
msg = wxString(resp.get<std::string>("message").c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
} catch (const std::exception &) {
|
||||
msg = wxString("Could not parse server response");
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool C3DPrinterOS::save_api_session(const std::string &session, const std::string &email) const {
|
||||
pt::ptree j;
|
||||
j.put("session", session);
|
||||
j.put("email", email);
|
||||
try {
|
||||
auto temp_path = m_api_session_file_path + ".tmp";
|
||||
pt::write_json(temp_path, j);
|
||||
boost::filesystem::rename(temp_path, m_api_session_file_path);
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": failed to write json to file. Path = "
|
||||
<< m_api_session_file_path
|
||||
<< " Reason = " << err.what();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void C3DPrinterOS::load_api_session()
|
||||
{
|
||||
m_apikey.clear();
|
||||
if (boost::filesystem::exists(m_api_session_file_path)) {
|
||||
pt::ptree j;
|
||||
try {
|
||||
pt::read_json(m_api_session_file_path, j);
|
||||
m_apikey = j.get<std::string>("session");
|
||||
m_username = j.get<std::string>("email");
|
||||
} catch (const std::exception &err) {
|
||||
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": load_api_session failed, reason = " << err.what();
|
||||
// remove corrupted file to avoid repeated failures
|
||||
try {
|
||||
boost::filesystem::remove(m_api_session_file_path);
|
||||
} catch (...) {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void C3DPrinterOS::send_form(
|
||||
const std::string &endpoint,
|
||||
const std::string &postBody,
|
||||
boost::property_tree::ptree &responseTree
|
||||
) const
|
||||
{
|
||||
responseTree.clear();
|
||||
auto url = make_url(endpoint);
|
||||
auto http = Http::post(std::move(url));
|
||||
if (!m_cafile.empty()) {
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
http.header("Content-length", std::to_string(postBody.size()));
|
||||
http.set_post_body(postBody);
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("Error sending form: %1%") % error;
|
||||
responseTree.put("result", false);
|
||||
responseTree.put("message", error);
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
std::stringstream ss(body);
|
||||
try {
|
||||
pt::read_json(ss, responseTree);
|
||||
} catch (const std::exception &) {
|
||||
responseTree.put("result", false);
|
||||
responseTree.put("message", "Could not parse server response");
|
||||
}
|
||||
})
|
||||
.perform_sync();
|
||||
}
|
||||
|
||||
void C3DPrinterOS::get_cloud_projects_list(boost::property_tree::ptree &response) const
|
||||
{
|
||||
std::string postBody = std::string("session=" + m_apikey);
|
||||
send_form("apiglobal/get_projects", postBody, response);
|
||||
}
|
||||
|
||||
void C3DPrinterOS::get_cloud_printer_types(boost::property_tree::ptree &response, const std::string &query) const
|
||||
{
|
||||
std::string postBody = std::string("session=" + m_apikey);
|
||||
if (!query.empty()) {
|
||||
postBody += "&description=" + Http::url_encode(query) + "&software_version=" + Http::url_encode("OrcaSlicer");
|
||||
}
|
||||
send_form("apiglobal/get_printer_types", postBody, response);
|
||||
}
|
||||
|
||||
void C3DPrinterOS::update_file(boost::property_tree::ptree &response, const std::string &file_id, const std::string &ptype, const std::string >ype) const
|
||||
{
|
||||
std::string postBody = "session=" + m_apikey
|
||||
+ "&updates[" + file_id + "][ptype]=" + ptype
|
||||
+ "&updates[" + file_id + "][gtype]=" + Http::url_encode(gtype)
|
||||
+ "&updates[" + file_id + "][zip]=false";
|
||||
send_form("apiglobal/file_update", postBody, response);
|
||||
}
|
||||
|
||||
};
|
||||
// namespace Slic3r
|
||||
80
src/slic3r/Utils/3DPrinterOS.hpp
Executable file
80
src/slic3r/Utils/3DPrinterOS.hpp
Executable file
@@ -0,0 +1,80 @@
|
||||
#ifndef slic3r_3DPrinterOS_hpp_
|
||||
#define slic3r_3DPrinterOS_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <boost/optional.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
|
||||
class C3DPrinterOS : public PrintHost
|
||||
{
|
||||
public:
|
||||
C3DPrinterOS(DynamicPrintConfig *config);
|
||||
~C3DPrinterOS() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
bool test(wxString &curl_msg) const override;
|
||||
bool login(wxString &msg) const;
|
||||
wxString get_test_ok_msg () const override;
|
||||
wxString get_test_failed_msg (wxString &msg) 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 can_test() const override { return true; }
|
||||
bool is_cloud() const override { return true; }
|
||||
void log_out() const override;
|
||||
bool is_logged_in() const override { return !m_apikey.empty(); }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint | PrintHostPostUploadAction::QueuePrint; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
static std::string default_host() { return "https://cloud.3dprinteros.com"; }
|
||||
|
||||
protected:
|
||||
bool validate_version_text(const boost::optional<std::string> &version_text) const;
|
||||
|
||||
private:
|
||||
std::string m_host;
|
||||
std::string m_apikey;
|
||||
std::string m_cafile;
|
||||
std::string m_username;
|
||||
std::string m_host_type;
|
||||
std::string m_preset_name;
|
||||
std::string m_api_session_file_path;
|
||||
|
||||
void load_api_session();
|
||||
bool save_api_session(const std::string &session, const std::string &email) const;
|
||||
std::string parse_printer_model(const std::string& input) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
std::string get_api_auth_token(wxString &err) const;
|
||||
void login_with_token(boost::property_tree::ptree &resp, const std::string &token) const;
|
||||
bool check_session(wxString &msg) const;
|
||||
void send_form(
|
||||
const std::string &endpoint,
|
||||
const std::string &postBody,
|
||||
boost::property_tree::ptree &responseTree
|
||||
) const;
|
||||
|
||||
|
||||
void get_cloud_projects_list(boost::property_tree::ptree &response) const;
|
||||
void get_cloud_printer_types(boost::property_tree::ptree &response, const std::string &querry) const;
|
||||
void update_file(
|
||||
boost::property_tree::ptree &response,
|
||||
const std::string &file_id,
|
||||
const std::string &ptype,
|
||||
const std::string >ype
|
||||
) const;
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -433,7 +433,7 @@ std::string BBLCloudServiceAgent::request_setting_id(std::string name, std::map<
|
||||
return "";
|
||||
}
|
||||
|
||||
int BBLCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
|
||||
int BBLCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, bool force)
|
||||
{
|
||||
auto& plugin = BBLNetworkPlugin::instance();
|
||||
auto agent = plugin.get_agent();
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
// Settings Synchronization
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets) override;
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, bool force = false) override;
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int delete_setting(std::string setting_id) override;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "ElegooLink.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <sstream>
|
||||
#include <exception>
|
||||
#include <boost/format.hpp>
|
||||
@@ -60,6 +62,52 @@ namespace Slic3r {
|
||||
namespace {
|
||||
|
||||
constexpr const char* ELEGOO_CC2_DEFAULT_TOKEN = "123456";
|
||||
// AppConfig section for CC2 serial numbers, keyed by normalized print_host (host/IP).
|
||||
constexpr const char* ELEGOO_DEV_SN_SECTION = "dev_sn";
|
||||
|
||||
static std::mutex s_sn_cache_mutex;
|
||||
static std::map<std::string, std::string> s_sn_cache;
|
||||
|
||||
std::string sn_cache_key(const std::string& host_ip, const std::string& token)
|
||||
{
|
||||
return host_ip + ":" + token;
|
||||
}
|
||||
|
||||
void cache_sn(const std::string& host_ip, const std::string& token, const std::string& sn)
|
||||
{
|
||||
if (host_ip.empty() || token.empty() || sn.empty())
|
||||
return;
|
||||
std::lock_guard<std::mutex> lock(s_sn_cache_mutex);
|
||||
s_sn_cache[sn_cache_key(host_ip, token)] = sn;
|
||||
}
|
||||
|
||||
std::string lookup_sn(const std::string& host_ip, const std::string& token)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(s_sn_cache_mutex);
|
||||
auto it = s_sn_cache.find(sn_cache_key(host_ip, token));
|
||||
return it != s_sn_cache.end() ? it->second : std::string{};
|
||||
}
|
||||
|
||||
std::string load_sn_from_config(const std::string& host_ip)
|
||||
{
|
||||
if (host_ip.empty())
|
||||
return {};
|
||||
AppConfig* app_cfg = GUI::get_app_config();
|
||||
if (app_cfg == nullptr)
|
||||
return {};
|
||||
return app_cfg->get(ELEGOO_DEV_SN_SECTION, host_ip);
|
||||
}
|
||||
|
||||
void persist_sn(const std::string& host_ip, const std::string& token, const std::string& sn)
|
||||
{
|
||||
if (host_ip.empty() || sn.empty())
|
||||
return;
|
||||
cache_sn(host_ip, token, sn);
|
||||
AppConfig* app_cfg = GUI::get_app_config();
|
||||
if (app_cfg == nullptr)
|
||||
return;
|
||||
app_cfg->set_str(ELEGOO_DEV_SN_SECTION, host_ip, sn);
|
||||
}
|
||||
|
||||
enum class ElegooPrinterType {
|
||||
Other,
|
||||
@@ -122,6 +170,36 @@ namespace Slic3r {
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE (merge): host parsing was moved into Http::get_host_from_url /
|
||||
// Http::get_host_header_value by the K2 discovery refactor on this branch, so the
|
||||
// former ElegooLink-local get_host_from_url/get_host_from_url_no_port helpers are gone.
|
||||
// main only added the CC2 serial-number lookup below; it is kept here and routed through
|
||||
// Http::get_host_header_value, which has the same host:port semantics the SN cache key
|
||||
// relies on.
|
||||
std::string lookup_cc2_serial_impl(const std::string& printer_model,
|
||||
const std::string& print_host,
|
||||
const std::string& apikey)
|
||||
{
|
||||
if (classify_printer_model(printer_model) != ElegooPrinterType::CC2)
|
||||
return {};
|
||||
|
||||
const std::string host_ip = Http::get_host_header_value(print_host);
|
||||
const std::string token = get_cc2_token(apikey);
|
||||
std::string sn = lookup_sn(host_ip, token);
|
||||
if (sn.empty())
|
||||
sn = load_sn_from_config(host_ip);
|
||||
return sn;
|
||||
}
|
||||
|
||||
std::string lookup_cc2_serial(DynamicPrintConfig* config)
|
||||
{
|
||||
if (config == nullptr)
|
||||
return {};
|
||||
return lookup_cc2_serial_impl(config->opt_string("printer_model"),
|
||||
config->opt_string("print_host"),
|
||||
config->opt_string("printhost_apikey"));
|
||||
}
|
||||
|
||||
#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)
|
||||
@@ -262,11 +340,32 @@ namespace Slic3r {
|
||||
if (classify_printer_model(config->opt_string("printer_model")) != ElegooPrinterType::CC2)
|
||||
return fallback_webui;
|
||||
|
||||
std::string web_path = resources_dir() + "/plugins/elegoolink/web/lan_service_web/index.html";
|
||||
std::string web_path = resources_dir() + "/web/elegoolink/lan_service_web/index.html";
|
||||
std::replace(web_path.begin(), web_path.end(), '\\', '/');
|
||||
web_path = "file://" + web_path;
|
||||
web_path += "?access_code=" + get_cc2_token(config->opt_string("printhost_apikey"));
|
||||
web_path += "&ip=" + Http::get_host_header_value(host) + "&id=elegoo_123456";
|
||||
const std::string token = get_cc2_token(config->opt_string("printhost_apikey"));
|
||||
const std::string host_ip = Http::get_host_header_value(host);
|
||||
|
||||
// Pass sn= so the panel can subscribe to the correct MQTT topics.
|
||||
std::string sn = lookup_cc2_serial(config);
|
||||
if (sn.empty()) {
|
||||
std::string error_msg;
|
||||
auto http = Http::get("http://" + host_ip + "/system/info?X-Token=" + escape_string(token));
|
||||
http.timeout_connect(3).timeout_max(5);
|
||||
http.header("X-Token", token);
|
||||
http.header("Accept", "application/json");
|
||||
http.on_complete([&](std::string body, unsigned /*status*/) {
|
||||
parse_cc2_response(body, error_msg, &sn);
|
||||
}).perform_sync();
|
||||
if (!sn.empty())
|
||||
persist_sn(host_ip, token, sn);
|
||||
}
|
||||
|
||||
web_path += "?access_code=" + token;
|
||||
web_path += "&ip=" + host_ip;
|
||||
if (!sn.empty())
|
||||
web_path += "&sn=" + sn;
|
||||
web_path += "&id=elegoo_123456";
|
||||
|
||||
const std::string lang = GUI::wxGetApp().current_language_code_safe().utf8_string();
|
||||
if (!lang.empty())
|
||||
@@ -305,33 +404,9 @@ namespace Slic3r {
|
||||
|
||||
std::string ElegooLink::get_sn() const
|
||||
{
|
||||
if (classify_printer_model(m_printerModel) != ElegooPrinterType::CC2)
|
||||
return "";
|
||||
|
||||
const char* name = get_name();
|
||||
std::string sn;
|
||||
const auto token = cc2_token();
|
||||
auto http = Http::get(make_cc2_info_url());
|
||||
http.timeout_connect(10)
|
||||
.timeout_max(15);
|
||||
http.header("X-Token", token);
|
||||
http.header("Accept", "application/json");
|
||||
http.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error getting CC2 device info for SN: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
std::string error_message;
|
||||
if (!parse_cc2_response(body, error_message, &sn)) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: Failed to parse CC2 SN response, HTTP %2%, reason: %3%") % name % status % error_message;
|
||||
sn.clear();
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
|
||||
return sn;
|
||||
// Panel IPC calls this on every load with a 10s timeout. Never block on HTTP
|
||||
// here — URL sn= and dev_sn must be enough; HTTP is only for get_print_host_webui.
|
||||
return lookup_cc2_serial_impl(m_printerModel, m_host, m_apikey);
|
||||
}
|
||||
|
||||
bool ElegooLink::elegoo_test(wxString& msg) const{
|
||||
@@ -410,6 +485,7 @@ namespace Slic3r {
|
||||
msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
|
||||
return;
|
||||
}
|
||||
persist_sn(Http::get_host_header_value(m_host), token, serial_number);
|
||||
res = true;
|
||||
})
|
||||
#ifdef WIN32
|
||||
|
||||
@@ -247,7 +247,7 @@ public:
|
||||
/**
|
||||
* Update or create a preset with a known setting_id.
|
||||
*/
|
||||
virtual int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) = 0;
|
||||
virtual int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, bool force = false) = 0;
|
||||
|
||||
/**
|
||||
* Trigger bulk download of user presets.
|
||||
|
||||
311
src/slic3r/Utils/Moonraker.cpp
Normal file
311
src/slic3r/Utils/Moonraker.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
#include "Moonraker.hpp"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
#include "slic3r/GUI/I18N.hpp"
|
||||
#include "slic3r/GUI/GUI.hpp"
|
||||
#include "slic3r/GUI/format.hpp"
|
||||
#include "Http.hpp"
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
Moonraker::Moonraker(DynamicPrintConfig *config)
|
||||
: m_host(config->opt_string("print_host"))
|
||||
, m_apikey(config->opt_string("printhost_apikey"))
|
||||
, m_cafile(config->opt_string("printhost_cafile"))
|
||||
, m_ssl_revoke_best_effort(config->opt_bool("printhost_ssl_ignore_revoke"))
|
||||
{}
|
||||
|
||||
const char* Moonraker::get_name() const { return "Moonraker"; }
|
||||
|
||||
wxString Moonraker::get_test_ok_msg() const
|
||||
{
|
||||
return _(L("Connection to Moonraker is working correctly."));
|
||||
}
|
||||
|
||||
wxString Moonraker::get_test_failed_msg(wxString &msg) const
|
||||
{
|
||||
return GUI::format_wxstr("%s: %s", _L("Could not connect to Moonraker"), msg);
|
||||
}
|
||||
|
||||
std::string Moonraker::make_url(const std::string &path) const
|
||||
{
|
||||
if (m_host.find("http://") == 0 || m_host.find("https://") == 0) {
|
||||
if (m_host.back() == '/')
|
||||
return (boost::format("%1%%2%") % m_host % path).str();
|
||||
return (boost::format("%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
return (boost::format("http://%1%/%2%") % m_host % path).str();
|
||||
}
|
||||
|
||||
void Moonraker::set_auth(Http &http) const
|
||||
{
|
||||
//ORCA: Moonraker accepts unauthenticated requests by default; X-Api-Key is the only auth header
|
||||
// defined by the Moonraker spec. HTTP Basic / Digest do NOT belong here even if the user
|
||||
// filled the user/password fields — those are PrusaLink/OctoPrint conventions.
|
||||
if (!m_apikey.empty())
|
||||
http.header("X-Api-Key", m_apikey);
|
||||
if (!m_cafile.empty())
|
||||
http.ca_file(m_cafile);
|
||||
}
|
||||
|
||||
bool Moonraker::test(wxString &msg) const
|
||||
{
|
||||
//ORCA: Moonraker's /server/info returns
|
||||
// { "result": { "klippy_state": "ready|startup|shutdown|error|disconnected", ... } }
|
||||
// We treat the connection as healthy as long as the envelope is valid and `klippy_state`
|
||||
// is present — matching the OctoPrint/PrusaLink convention of "can I reach this host?".
|
||||
// Klipper state (idle, error, etc.) is surfaced to the log but does not gate the test:
|
||||
// buddy-fork firmwares legitimately report non-`ready` states at idle, and any real upload
|
||||
// problem will surface a contextual error at upload() time anyway.
|
||||
const char *name = get_name();
|
||||
bool res = true;
|
||||
auto url = make_url("server/info");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get server info 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 server info: %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%: /server/info body: %2%") % name % body;
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
|
||||
const auto klippy_state = ptree.get_optional<std::string>("result.klippy_state");
|
||||
if (!klippy_state) {
|
||||
//ORCA: response wasn't shaped like a Moonraker /server/info reply — likely an OctoPrint
|
||||
// or PrusaLink host the user mis-selected as Moonraker, or a totally different
|
||||
// service. Treat as a connection failure with a clear hint.
|
||||
res = false;
|
||||
msg = _L("The host responded but it doesn't look like Moonraker (missing result.klippy_state).");
|
||||
return;
|
||||
}
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: klippy_state = %2%") % name % (*klippy_state);
|
||||
} catch (const std::exception &ex) {
|
||||
res = false;
|
||||
msg = GUI::format_wxstr(_L("Could not parse Moonraker server response: %s"), ex.what());
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Moonraker::get_storage(wxArrayString &storage_path, wxArrayString &storage_name) const
|
||||
{
|
||||
//ORCA: GET /server/files/roots enumerates Moonraker's storage roots (default "gcodes" plus any
|
||||
// configured extras like "config", "logs", "timelapse"). Only roots with permissions
|
||||
// including "rw" or "rwd" can receive uploads; we filter to those so the UI dropdown only
|
||||
// offers usable destinations. The base class returns false (no per-host storage); returning
|
||||
// true here populates the storage picker in PrintHostDialogs's send-to-print dialog.
|
||||
// Failures (404 — older Moonraker, or a buddy-fork that doesn't implement the endpoint)
|
||||
// gracefully degrade to false so upload() falls back to the hardcoded "gcodes" default.
|
||||
const char *name = get_name();
|
||||
bool got_any = false;
|
||||
auto url = make_url("server/files/roots");
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Enumerating storage roots 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) {
|
||||
//ORCA: /server/files/roots is optional in the Moonraker spec and absent on older versions
|
||||
// and slimmer shims (e.g. Prusa-Firmware-Buddy 0.8.x prusalink-shim returns 501). A
|
||||
// missing endpoint here is benign — upload() silently falls back to the hardcoded
|
||||
// "gcodes" root — so don't pollute the log at warning level for it. Other HTTP
|
||||
// errors still warn.
|
||||
if (status == 404 || status == 501) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: /server/files/roots not implemented (HTTP %2%); upload() will fall back to the \"gcodes\" root.")
|
||||
% name % status;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: Could not enumerate roots: %2%, HTTP %3%, body: `%4%`")
|
||||
% name % error % status % body;
|
||||
}
|
||||
})
|
||||
.on_complete([&, this](std::string body, unsigned) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: /server/files/roots body: %2%") % name % body;
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
const auto result_node = ptree.get_child_optional("result");
|
||||
if (!result_node)
|
||||
return;
|
||||
for (const auto &child : *result_node) {
|
||||
const std::string &root = child.second.get<std::string>("name", "");
|
||||
const std::string &perms = child.second.get<std::string>("permissions", "");
|
||||
if (root.empty() || perms.find('w') == std::string::npos)
|
||||
continue;
|
||||
storage_path.Add(wxString::FromUTF8(root));
|
||||
storage_name.Add(wxString::FromUTF8(root));
|
||||
got_any = true;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format("%1%: Could not parse roots: %2%") % name % ex.what();
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return got_any;
|
||||
}
|
||||
|
||||
bool Moonraker::start_print(wxString &error_msg, const std::string &filename) const
|
||||
{
|
||||
//ORCA: POST /printer/print/start with JSON body { "filename": "<name>.gcode" }.
|
||||
// `filename` is what /server/files/upload returned as result.item.path (the storage-relative
|
||||
// path inside `root`, no leading slash, with extension). Build the body via property_tree
|
||||
// so that special characters in the filename (server-side collision-suffix could produce
|
||||
// paths with quotes / backslashes on exotic file systems) are properly escaped.
|
||||
const char *name = get_name();
|
||||
bool res = true;
|
||||
auto url = make_url("printer/print/start");
|
||||
pt::ptree body_tree;
|
||||
body_tree.put("filename", filename);
|
||||
std::ostringstream body_ss;
|
||||
pt::write_json(body_ss, body_tree, /*pretty=*/false);
|
||||
std::string body = body_ss.str();
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Starting print of %2% at %3%") % name % filename % url;
|
||||
|
||||
auto http = Http::post(std::move(url));
|
||||
set_auth(http);
|
||||
http.header("Content-Type", "application/json")
|
||||
.set_post_body(body)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: print/start HTTP %2%: %3%") % name % status % body;
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error starting print at %2%: %3%, HTTP %4%, body: `%5%`")
|
||||
% name % url % error % status % body;
|
||||
res = false;
|
||||
error_msg = format_error(body, error, status);
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool Moonraker::upload(PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
//ORCA: POST /server/files/upload as multipart/form-data with:
|
||||
// file = <gcode file>
|
||||
// root = <storage root> (Moonraker default: "gcodes")
|
||||
// Successful response shape:
|
||||
// { "result": { "item": { "path": "<name>.gcode", "root": "<root>" }, "print_started": <bool> } }
|
||||
// We always start the print explicitly via /printer/print/start regardless of `print_started`
|
||||
// so the user can rely on a single call site for state.
|
||||
wxString test_msg;
|
||||
if (!test(test_msg)) {
|
||||
error_fn(std::move(test_msg));
|
||||
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();
|
||||
//ORCA: upload_data.storage is plumbed from the (future) per-printer storage dropdown. When unset,
|
||||
// fall back to the Moonraker-standard "gcodes" root. Reading it through here means a UI
|
||||
// addition later (storage picker) needs no change to this method.
|
||||
const std::string root = upload_data.storage.empty() ? std::string("gcodes") : upload_data.storage;
|
||||
|
||||
std::string url = make_url("server/files/upload");
|
||||
bool result = true;
|
||||
std::string uploaded_path;
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Uploading file %2% to %3% (root=%4%, filename=%5%, start_print=%6%)")
|
||||
% name
|
||||
% upload_data.source_path
|
||||
% url
|
||||
% root
|
||||
% upload_filename.string()
|
||||
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
|
||||
|
||||
auto http = Http::post(std::move(url));
|
||||
set_auth(http);
|
||||
http.form_add("root", root)
|
||||
.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%: upload HTTP %2%: %3%") % name % status % body;
|
||||
try {
|
||||
std::stringstream ss(body);
|
||||
pt::ptree ptree;
|
||||
pt::read_json(ss, ptree);
|
||||
|
||||
//ORCA: Moonraker confirms the storage-relative path in result.item.path. We pass exactly
|
||||
// that string to /printer/print/start so any server-side renaming (collision suffix,
|
||||
// etc.) is respected.
|
||||
const auto stored_path = ptree.get_optional<std::string>("result.item.path");
|
||||
if (stored_path) {
|
||||
uploaded_path = *stored_path;
|
||||
} else {
|
||||
//ORCA: fallback if the server response omits result.item.path (older Moonraker, or
|
||||
// a buddy-fork that returns a slimmer envelope). Use the original filename.
|
||||
uploaded_path = upload_filename.string();
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format(
|
||||
"%1%: upload response missing result.item.path, falling back to original filename `%2%`")
|
||||
% name % uploaded_path;
|
||||
}
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(warning) << boost::format(
|
||||
"%1%: could not parse upload response (%2%); falling back to original filename")
|
||||
% name % ex.what();
|
||||
uploaded_path = upload_filename.string();
|
||||
}
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading 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) {
|
||||
progress_fn(std::move(progress), cancel);
|
||||
if (cancel) {
|
||||
BOOST_LOG_TRIVIAL(info) << name << ": Upload canceled";
|
||||
result = false;
|
||||
}
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
if (!result)
|
||||
return false;
|
||||
|
||||
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint && !uploaded_path.empty()) {
|
||||
wxString start_msg;
|
||||
if (!start_print(start_msg, uploaded_path)) {
|
||||
error_fn(std::move(start_msg));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
63
src/slic3r/Utils/Moonraker.hpp
Normal file
63
src/slic3r/Utils/Moonraker.hpp
Normal file
@@ -0,0 +1,63 @@
|
||||
#ifndef slic3r_Moonraker_hpp_
|
||||
#define slic3r_Moonraker_hpp_
|
||||
|
||||
#include <string>
|
||||
#include <wx/string.h>
|
||||
#include <wx/arrstr.h>
|
||||
|
||||
#include "PrintHost.hpp"
|
||||
#include "libslic3r/PrintConfig.hpp"
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
|
||||
class DynamicPrintConfig;
|
||||
class Http;
|
||||
|
||||
// Moonraker is the JSON / WebSocket gateway that ships in front of Klipper
|
||||
// (and on Klipper-API-compatible firmwares like the Prusa-Firmware-Buddy
|
||||
// Buddy-Klipper fork). REST shape differs from OctoPrint: distinct paths,
|
||||
// JSON body for print/start, {"result":...}/{"error":...} envelope.
|
||||
//
|
||||
// Endpoints used:
|
||||
// GET /server/info -- connection test, reads klippy_state
|
||||
// POST /server/files/upload (multipart) -- upload gcode (form fields: file, root)
|
||||
// POST /printer/print/start (json) -- {"filename":"<name>.gcode"} starts print
|
||||
//
|
||||
// Auth: X-Api-Key header if `printhost_apikey` is non-empty; Moonraker accepts
|
||||
// unauthenticated LAN access by default, so the key is optional. HTTP Basic /
|
||||
// Digest are not part of the Moonraker spec and are not sent.
|
||||
class Moonraker : public PrintHost
|
||||
{
|
||||
public:
|
||||
Moonraker(DynamicPrintConfig *config);
|
||||
~Moonraker() override = default;
|
||||
|
||||
const char* get_name() const override;
|
||||
|
||||
bool test(wxString &curl_msg) const override;
|
||||
wxString get_test_ok_msg() const override;
|
||||
wxString get_test_failed_msg(wxString &msg) const override;
|
||||
bool upload(PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
bool has_auto_discovery() const override { return false; }
|
||||
bool can_test() const override { return true; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override { return PrintHostPostUploadAction::StartPrint; }
|
||||
std::string get_host() const override { return m_host; }
|
||||
bool get_storage(wxArrayString &storage_path, wxArrayString &storage_name) const override;
|
||||
const std::string& get_apikey() const { return m_apikey; }
|
||||
const std::string& get_cafile() const { return m_cafile; }
|
||||
|
||||
protected:
|
||||
std::string m_host;
|
||||
std::string m_apikey;
|
||||
std::string m_cafile;
|
||||
bool m_ssl_revoke_best_effort;
|
||||
|
||||
void set_auth(Http &http) const;
|
||||
std::string make_url(const std::string &path) const;
|
||||
bool start_print(wxString &error_msg, const std::string &filename) const;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -383,11 +383,12 @@ int NetworkAgent::put_setting(std::string setting_id,
|
||||
std::string name,
|
||||
std::map<std::string, std::string>* values_map,
|
||||
unsigned int* http_code,
|
||||
const std::string& provider)
|
||||
const std::string& provider,
|
||||
bool force)
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->put_setting(std::move(setting_id), std::move(name), values_map, http_code);
|
||||
return cloud_agent->put_setting(std::move(setting_id), std::move(name), values_map, http_code, force);
|
||||
return -1;
|
||||
}
|
||||
|
||||
@@ -582,21 +583,22 @@ int NetworkAgent::get_my_token(std::string ticket, unsigned int* http_code, std:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_enable(bool enable, const std::string& provider)
|
||||
int NetworkAgent::track_enable(bool enable)
|
||||
{
|
||||
this->enable_track = enable;
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
// Orca cloud has no telemetry; the only cloud agent that tracks events is BBL.
|
||||
this->enable_track = enable;
|
||||
const auto cloud_agent = get_cloud_agent(BBL_CLOUD_PROVIDER);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_enable(enable);
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_remove_files(const std::string& provider)
|
||||
int NetworkAgent::track_remove_files()
|
||||
{
|
||||
const auto cloud_agent = get_cloud_agent(provider);
|
||||
const auto cloud_agent = get_cloud_agent(BBL_CLOUD_PROVIDER);
|
||||
if (cloud_agent)
|
||||
return cloud_agent->track_remove_files();
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int NetworkAgent::track_event(std::string evt_key, std::string content, const std::string& provider)
|
||||
|
||||
@@ -93,7 +93,7 @@ public:
|
||||
// NOTE: this should always call only OrcaCloud
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER, bool force = false);
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int delete_setting(std::string setting_id, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
@@ -118,8 +118,9 @@ public:
|
||||
int get_model_mall_detail_url(std::string* url, std::string id, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_my_profile(std::string token, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int get_my_token(std::string ticket, unsigned int* http_code, std::string* http_body, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_enable(bool enable, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_remove_files(const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
// Orca: telemetry only exists on the BBL cloud agent (Orca cloud has no track events).
|
||||
int track_enable(bool enable);
|
||||
int track_remove_files();
|
||||
int track_event(std::string evt_key, std::string content, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_header(std::string header, const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
int track_update_property(std::string name, std::string value, std::string type = "string", const std::string& provider = ORCA_CLOUD_PROVIDER);
|
||||
|
||||
@@ -56,6 +56,7 @@ constexpr const char* ORCA_DEFAULT_PUB_KEY = "sb_publishable_lvVe_whOi80SU9BPSxM
|
||||
constexpr const char* ORCA_HEALTH_PATH = "/api/v1/health";
|
||||
constexpr const char* ORCA_SYNC_PULL_PATH = "/api/v1/sync/pull";
|
||||
constexpr const char* ORCA_SYNC_PUSH_PATH = "/api/v1/sync/push";
|
||||
constexpr const char* ORCA_SYNC_FORCE_PUSH_PATH = "/api/v1/sync/force-push";
|
||||
constexpr const char* ORCA_SYNC_DELETE_PATH = "/api/v1/sync/delete";
|
||||
constexpr const char* ORCA_PROFILES_PATH = "/api/v1/sync/profiles";
|
||||
constexpr const char* ORCA_SUBSCRIPTIONS_PATH = "/api/v1/subscriptions";
|
||||
@@ -965,7 +966,7 @@ std::string OrcaCloudServiceAgent::request_setting_id(std::string name, std::map
|
||||
return "";
|
||||
}
|
||||
|
||||
int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
|
||||
int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, bool force)
|
||||
{
|
||||
// Extract original_updated_time for Optimistic Concurrency Control
|
||||
// If present, server will verify version before update. If absent, treated as insert.
|
||||
@@ -989,7 +990,7 @@ int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name,
|
||||
}
|
||||
}
|
||||
|
||||
auto result = sync_push(setting_id, name, content, original_updated_time);
|
||||
auto result = sync_push(setting_id, name, content, original_updated_time, force);
|
||||
if (http_code) *http_code = result.http_code;
|
||||
|
||||
if (result.success) {
|
||||
@@ -1208,11 +1209,11 @@ int OrcaCloudServiceAgent::sync_pull(
|
||||
}
|
||||
}
|
||||
|
||||
SyncPushResult OrcaCloudServiceAgent::sync_push(
|
||||
const std::string& profile_id,
|
||||
const std::string& name,
|
||||
const nlohmann::json& content,
|
||||
const std::string& original_updated_time)
|
||||
SyncPushResult OrcaCloudServiceAgent::sync_push(const std::string& profile_id,
|
||||
const std::string& name,
|
||||
const nlohmann::json& content,
|
||||
const std::string& original_updated_time,
|
||||
bool force)
|
||||
{
|
||||
SyncPushResult result;
|
||||
result.success = false;
|
||||
@@ -1243,7 +1244,7 @@ SyncPushResult OrcaCloudServiceAgent::sync_push(
|
||||
|
||||
std::string response;
|
||||
unsigned int http_code = 0;
|
||||
int http_result = http_post(ORCA_SYNC_PUSH_PATH, body_str, &response, &http_code);
|
||||
int http_result = http_post(force ? ORCA_SYNC_FORCE_PUSH_PATH : ORCA_SYNC_PUSH_PATH, body_str, &response, &http_code);
|
||||
|
||||
result.http_code = http_code;
|
||||
|
||||
@@ -1888,7 +1889,7 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string&
|
||||
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
|
||||
result.success = false;
|
||||
result.status = resp_status == 0 ? 404 : resp_status;
|
||||
result.body = body;
|
||||
result.body = resp_body;
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
|
||||
})
|
||||
.timeout_max(30)
|
||||
|
||||
@@ -176,7 +176,12 @@ public:
|
||||
// ========================================================================
|
||||
int get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets) override;
|
||||
std::string request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code) override;
|
||||
int put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code, bool force = false) override;
|
||||
SyncPushResult sync_push(const std::string& profile_id,
|
||||
const std::string& name,
|
||||
const nlohmann::json& content,
|
||||
const std::string& original_updated_time = "",
|
||||
bool force = false);
|
||||
int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr) override;
|
||||
int delete_setting(std::string setting_id) override;
|
||||
@@ -294,13 +299,6 @@ private:
|
||||
std::function<void(int http_code, const std::string& error)> on_error
|
||||
);
|
||||
|
||||
SyncPushResult sync_push(
|
||||
const std::string& profile_id,
|
||||
const std::string& name,
|
||||
const nlohmann::json& content,
|
||||
const std::string& original_updated_time = ""
|
||||
);
|
||||
|
||||
// HTTP request helpers
|
||||
int http_get(const std::string& path, std::string* response_body, unsigned int* http_code);
|
||||
int http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code);
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#include "Flashforge.hpp"
|
||||
#include "SimplyPrint.hpp"
|
||||
#include "ElegooLink.hpp"
|
||||
#include "3DPrinterOS.hpp"
|
||||
#include "Moonraker.hpp"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
using boost::optional;
|
||||
@@ -67,6 +69,8 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||
case htFlashforge: return new Flashforge(config);
|
||||
case htSimplyPrint: return new SimplyPrint(config);
|
||||
case htElegooLink: return new ElegooLink(config);
|
||||
case ht3DPrinterOS: return new C3DPrinterOS(config);
|
||||
case htMoonraker: return new Moonraker(config);
|
||||
default: return nullptr;
|
||||
}
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user