mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-05-16 18:12:10 +00:00
Support file uploads and the device details page for Elegoo Centauri Carbon 2 (#13212)
* Support file uploads and the device details page for CC2 printers. * Resolved build issues for Linux and macOS. * 1. Added `ElegooPrinterWebViewHandler` to handle WebUI messages for Elegoo printers. Other printers will keep the current behavior. 2. Added a static `get_print_host_webui` method in `PrintHost` to retrieve the printer WebUI URL. * Improved timeout handling for CC2 file upload and SN info APIs. --------- Co-authored-by: SoftFever <softfeverever@gmail.com>
This commit is contained in:
BIN
resources/plugins/elegoolink/web/lan_service_web/favicon.ico
Normal file
BIN
resources/plugins/elegoolink/web/lan_service_web/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 B |
265
resources/plugins/elegoolink/web/lan_service_web/index.html
Normal file
265
resources/plugins/elegoolink/web/lan_service_web/index.html
Normal file
File diff suppressed because one or more lines are too long
@@ -390,6 +390,8 @@ set(SLIC3R_GUI_SOURCES
|
||||
GUI/Printer/PrinterFileSystem.cpp
|
||||
GUI/Printer/PrinterFileSystem.h
|
||||
GUI/PrinterWebView.cpp
|
||||
GUI/PrinterWebViewHandler.cpp
|
||||
GUI/PrinterWebViewHandler.hpp
|
||||
GUI/PrinterWebView.hpp
|
||||
GUI/PrintHostDialogs.cpp
|
||||
GUI/PrintHostDialogs.hpp
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include "Widgets/ProgressDialog.hpp"
|
||||
#include "BindDialog.hpp"
|
||||
#include "../Utils/MacDarkMode.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
|
||||
#include <fstream>
|
||||
#include <string_view>
|
||||
@@ -4162,15 +4163,12 @@ void MainFrame::load_printer_url()
|
||||
return;
|
||||
|
||||
auto cfg = preset_bundle.printers.get_edited_preset().config;
|
||||
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
wxString apikey;
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type == htPrusaLink || host_type == htPrusaConnect))
|
||||
apikey = cfg.opt_string("printhost_apikey");
|
||||
if (!url.empty()) {
|
||||
if (!url.Lower().starts_with("http"))
|
||||
url = wxString::Format("http://%s", url);
|
||||
|
||||
load_printer_url(url, apikey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,7 @@
|
||||
#include "GUI_Utils.hpp"
|
||||
#include "GUI_Factories.hpp"
|
||||
#include "wxExtensions.hpp"
|
||||
#include "../Utils/PrintHost.hpp"
|
||||
#include "MainFrame.hpp"
|
||||
#include "format.hpp"
|
||||
#include "3DScene.hpp"
|
||||
@@ -2483,13 +2484,11 @@ void Sidebar::update_all_preset_comboboxes()
|
||||
p->m_bpButton_ams_filament->Hide();
|
||||
|
||||
auto print_btn_type = MainFrame::PrintSelectType::eExportGcode;
|
||||
wxString url = cfg.opt_string("print_host_webui").empty() ? cfg.opt_string("print_host") : cfg.opt_string("print_host_webui");
|
||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||
wxString apikey;
|
||||
if(url.empty())
|
||||
url = wxString::Format("file://%s/web/orca/missing_connection.html", from_u8(resources_dir()));
|
||||
else {
|
||||
if (!url.Lower().starts_with("http"))
|
||||
url = wxString::Format("http://%s", url);
|
||||
const auto host_type = cfg.option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
if (cfg.has("printhost_apikey") && (host_type != htSimplyPrint))
|
||||
apikey = cfg.opt_string("printhost_apikey");
|
||||
|
||||
@@ -643,7 +643,7 @@ void ElegooPrintHostSendDialog::init() {
|
||||
auto preset_bundle = wxGetApp().preset_bundle;
|
||||
auto model_id = preset_bundle->printers.get_edited_preset().get_printer_type(preset_bundle);
|
||||
|
||||
if (!boost::starts_with(model_id, "Elegoo-C")) {
|
||||
if (model_id != "Elegoo-CC" && model_id != "Elegoo-C") {
|
||||
PrintHostSendDialog::init();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#include "PrinterWebView.hpp"
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "PrinterWebViewHandler.hpp"
|
||||
#include "slic3r/GUI/PrinterWebView.hpp"
|
||||
#include "slic3r/GUI/wxExtensions.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/MainFrame.hpp"
|
||||
#include "libslic3r_version.h"
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <wx/sizer.h>
|
||||
#include <wx/string.h>
|
||||
#include <wx/toolbar.h>
|
||||
#include <wx/textdlg.h>
|
||||
|
||||
#include <slic3r/GUI/Widgets/WebView.hpp>
|
||||
#include <wx/webview.h>
|
||||
@@ -19,13 +20,17 @@
|
||||
#include <webkit2/webkit2.h>
|
||||
#endif
|
||||
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
PrinterWebView::PrinterWebView(wxWindow *parent)
|
||||
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||
, m_browser(nullptr)
|
||||
, m_zoomFactor(100)
|
||||
, m_apikey()
|
||||
, m_apikey_sent(false)
|
||||
, m_url_deferred()
|
||||
, m_handler(std::make_unique<PrinterWebViewHandler>(*this))
|
||||
{
|
||||
|
||||
wxBoxSizer* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||
@@ -47,6 +52,8 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
|
||||
|
||||
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PrinterWebView::OnError, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PrinterWebView::OnLoaded, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PrinterWebView::OnNewWindow, this);
|
||||
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PrinterWebView::OnScriptMessage, this);
|
||||
|
||||
SetSizer(topsizer);
|
||||
|
||||
@@ -64,9 +71,6 @@ PrinterWebView::PrinterWebView(wxWindow *parent)
|
||||
}
|
||||
*/
|
||||
|
||||
//Zoom
|
||||
m_zoomFactor = 100;
|
||||
|
||||
//Connect the idle events
|
||||
Bind(wxEVT_CLOSE_WINDOW, &PrinterWebView::OnClose, this);
|
||||
|
||||
@@ -76,11 +80,18 @@ PrinterWebView::~PrinterWebView()
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " Start";
|
||||
SetEvtHandlerEnabled(false);
|
||||
m_handler.reset();
|
||||
|
||||
// Destroy the webview
|
||||
if(m_browser){
|
||||
m_browser->Destroy();
|
||||
m_browser = nullptr;
|
||||
}
|
||||
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << " End";
|
||||
}
|
||||
|
||||
|
||||
void PrinterWebView::load_url(wxString& url, wxString apikey)
|
||||
{
|
||||
// this->Show();
|
||||
@@ -89,6 +100,7 @@ void PrinterWebView::load_url(wxString& url, wxString apikey)
|
||||
return;
|
||||
m_apikey = apikey;
|
||||
m_apikey_sent = false;
|
||||
m_handler = create_printer_webview_handler(*this);
|
||||
|
||||
if (this->IsShown()) {
|
||||
//ORCA: m_url_deferred will be cleared on load success
|
||||
@@ -190,14 +202,34 @@ void PrinterWebView::OnError(wxWebViewEvent &evt)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__<< boost::format(": error loading page %1% %2% %3% %4%") %evt.GetURL() %evt.GetTarget() %e %evt.GetString();
|
||||
}
|
||||
|
||||
void PrinterWebView::OnLoaded(wxWebViewEvent &evt)
|
||||
void PrinterWebView::OnLoaded(wxWebViewEvent& evt)
|
||||
{
|
||||
if (evt.GetURL().IsEmpty())
|
||||
return;
|
||||
//ORCA: url loaded successfully, safe to clear
|
||||
m_url_deferred.clear();
|
||||
SendAPIKey();
|
||||
|
||||
if (m_handler != nullptr) {
|
||||
m_handler->on_loaded(evt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void PrinterWebView::OnNewWindow(wxWebViewEvent& evt)
|
||||
{
|
||||
const wxString url = evt.GetURL();
|
||||
if (!url.empty())
|
||||
wxLaunchDefaultBrowser(url);
|
||||
evt.Veto();
|
||||
}
|
||||
|
||||
void PrinterWebView::OnScriptMessage(wxWebViewEvent& evt)
|
||||
{
|
||||
if (m_handler != nullptr)
|
||||
m_handler->on_script_message(evt);
|
||||
}
|
||||
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
@@ -25,11 +25,14 @@
|
||||
#include <wx/tbarbase.h>
|
||||
#include "wx/textctrl.h"
|
||||
#include <wx/timer.h>
|
||||
#include <memory>
|
||||
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class PrinterWebViewHandler;
|
||||
|
||||
|
||||
class PrinterWebView : public wxPanel {
|
||||
public:
|
||||
@@ -41,20 +44,24 @@ public:
|
||||
void OnClose(wxCloseEvent& evt);
|
||||
void OnError(wxWebViewEvent& evt);
|
||||
void OnLoaded(wxWebViewEvent& evt);
|
||||
void OnNewWindow(wxWebViewEvent& evt);
|
||||
void OnScriptMessage(wxWebViewEvent& evt);
|
||||
void reload();
|
||||
void update_mode();
|
||||
|
||||
bool Show(bool show = true) override;
|
||||
|
||||
private:
|
||||
friend class PrinterWebViewHandler;
|
||||
|
||||
void SendAPIKey();
|
||||
|
||||
wxWebView* m_browser;
|
||||
long m_zoomFactor;
|
||||
wxString m_apikey;
|
||||
bool m_apikey_sent;
|
||||
|
||||
wxString m_url_deferred;
|
||||
std::unique_ptr<PrinterWebViewHandler> m_handler;
|
||||
|
||||
// DECLARE_EVENT_TABLE()
|
||||
};
|
||||
|
||||
339
src/slic3r/GUI/PrinterWebViewHandler.cpp
Normal file
339
src/slic3r/GUI/PrinterWebViewHandler.cpp
Normal file
@@ -0,0 +1,339 @@
|
||||
#include "PrinterWebViewHandler.hpp"
|
||||
|
||||
#include "I18N.hpp"
|
||||
#include "PrinterWebView.hpp"
|
||||
#include "slic3r/GUI/GUI_App.hpp"
|
||||
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||
#include "slic3r/Utils/PrintHost.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <atomic>
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <thread>
|
||||
#include <wx/filedlg.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
using json = nlohmann::json;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
PrinterWebViewHandler::PrinterWebViewHandler(PrinterWebView& owner)
|
||||
: m_owner(owner)
|
||||
{
|
||||
}
|
||||
|
||||
PrinterWebViewHandler::~PrinterWebViewHandler() = default;
|
||||
|
||||
void PrinterWebViewHandler::on_loaded(wxWebViewEvent &evt)
|
||||
{
|
||||
}
|
||||
|
||||
void PrinterWebViewHandler::on_script_message(wxWebViewEvent &evt)
|
||||
{
|
||||
}
|
||||
|
||||
PrinterWebView& PrinterWebViewHandler::owner() const
|
||||
{
|
||||
return m_owner;
|
||||
}
|
||||
|
||||
wxWebView* PrinterWebViewHandler::browser() const
|
||||
{
|
||||
return m_owner.m_browser;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
DynamicPrintConfig* get_active_printer_config()
|
||||
{
|
||||
if (wxGetApp().preset_bundle == nullptr)
|
||||
return nullptr;
|
||||
|
||||
return &wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||
}
|
||||
|
||||
std::string json_string(const json& node, const char* key)
|
||||
{
|
||||
auto it = node.find(key);
|
||||
return (it != node.end() && it->is_string()) ? it->get<std::string>() : std::string();
|
||||
}
|
||||
|
||||
std::string dump_json(const json& node)
|
||||
{
|
||||
return node.dump(-1, ' ', false, json::error_handler_t::replace);
|
||||
}
|
||||
|
||||
boost::filesystem::path path_from_utf8(const std::string& utf8_path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const wxString wide_path = wxString::FromUTF8(utf8_path.c_str());
|
||||
return boost::filesystem::path(wide_path.ToStdWstring());
|
||||
#else
|
||||
return boost::filesystem::path(utf8_path);
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string filename_to_utf8(const boost::filesystem::path& path)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
const wxString wx_filename(path.filename().c_str());
|
||||
const wxScopedCharBuffer utf8 = wx_filename.ToUTF8();
|
||||
return utf8.data() != nullptr ? std::string(utf8.data()) : std::string();
|
||||
#else
|
||||
return path.filename().string();
|
||||
#endif
|
||||
}
|
||||
|
||||
class ElegooPrinterWebViewHandler final : public PrinterWebViewHandler {
|
||||
public:
|
||||
explicit ElegooPrinterWebViewHandler(PrinterWebView& owner)
|
||||
: PrinterWebViewHandler(owner)
|
||||
{
|
||||
}
|
||||
|
||||
~ElegooPrinterWebViewHandler() override
|
||||
{
|
||||
stop_upload = true;
|
||||
if (upload_thread.joinable())
|
||||
upload_thread.join();
|
||||
if (sn_thread.joinable())
|
||||
sn_thread.join();
|
||||
}
|
||||
|
||||
void on_script_message(wxWebViewEvent &evt) override
|
||||
{
|
||||
const wxString message = evt.GetString();
|
||||
if (message.empty())
|
||||
return;
|
||||
|
||||
json root = json::parse(message.ToUTF8().data(), nullptr, false);
|
||||
if (root.is_discarded() || !root.is_object())
|
||||
return;
|
||||
|
||||
std::string request_id = json_string(root, "id");
|
||||
std::string method = json_string(root, "method");
|
||||
json params = root.contains("params") && root["params"].is_object() ? root["params"] : json::object();
|
||||
|
||||
if (method.empty()) {
|
||||
method = json_string(root, "command");
|
||||
if (params.empty() && root.contains("data") && root["data"].is_object())
|
||||
params = root["data"];
|
||||
}
|
||||
|
||||
if (method == "open" || method == "common_openurl") {
|
||||
const std::string url = json_string(params, "url").empty() ? json_string(root, "url") : json_string(params, "url");
|
||||
if (!url.empty())
|
||||
wxLaunchDefaultBrowser(url);
|
||||
if (!request_id.empty())
|
||||
send_ipc_message("response", request_id, method, 0, "success");
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "upload_file") {
|
||||
handle_upload_request(request_id, method, dump_json(params));
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "open_file_dialog") {
|
||||
handle_open_file_dialog_request(request_id, method, dump_json(params));
|
||||
return;
|
||||
}
|
||||
|
||||
if (method == "get_sn") {
|
||||
handle_get_sn_request(request_id, method);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void send_ipc_message(const char* type, const std::string& request_id, const std::string& method, int code,
|
||||
const std::string& message, const std::string& data_json = "{}")
|
||||
{
|
||||
if (browser() == nullptr)
|
||||
return;
|
||||
|
||||
json body = json::object();
|
||||
body["type"] = type;
|
||||
if (!request_id.empty())
|
||||
body["id"] = request_id;
|
||||
if (!method.empty())
|
||||
body["method"] = method;
|
||||
|
||||
json data = json::parse(data_json, nullptr, false);
|
||||
if (data.is_discarded())
|
||||
data = json::object();
|
||||
body["data"] = std::move(data);
|
||||
|
||||
if (std::string(type) == "response") {
|
||||
body["code"] = code;
|
||||
body["message"] = message;
|
||||
}
|
||||
|
||||
const wxString payload = wxString::FromUTF8(dump_json(body));
|
||||
const wxString script = "if (typeof HandleStudio === 'function') { HandleStudio(" + payload + "); } else { window.postMessage(" + payload + ", '*'); }";
|
||||
wxGetApp().CallAfter([this, script]() {
|
||||
if (browser() != nullptr)
|
||||
WebView::RunScript(browser(), script);
|
||||
});
|
||||
}
|
||||
|
||||
void handle_upload_request(const std::string& request_id, const std::string& method, const std::string& params_json)
|
||||
{
|
||||
if (upload_in_progress.exchange(true)) {
|
||||
send_ipc_message("response", request_id, method, 1, "Upload already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
if (upload_thread.joinable())
|
||||
upload_thread.join();
|
||||
|
||||
json params = json::parse(params_json, nullptr, false);
|
||||
if (params.is_discarded())
|
||||
params = json::object();
|
||||
|
||||
std::string file_path = json_string(params, "filePath");
|
||||
std::string file_name = json_string(params, "fileName");
|
||||
|
||||
if (file_path.empty()) {
|
||||
upload_in_progress = false;
|
||||
send_ipc_message("response", request_id, method, 1, "Missing filePath");
|
||||
return;
|
||||
}
|
||||
|
||||
// HTML IPC passes UTF-8 strings; decode explicitly to avoid Windows codepage issues.
|
||||
boost::filesystem::path source_path = path_from_utf8(file_path);
|
||||
if (file_name.empty())
|
||||
file_name = filename_to_utf8(source_path);
|
||||
|
||||
DynamicPrintConfig* config = get_active_printer_config();
|
||||
std::unique_ptr<PrintHost> print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config));
|
||||
if (print_host == nullptr) {
|
||||
upload_in_progress = false;
|
||||
send_ipc_message("response", request_id, method, 1, "Could not get a valid Printer Host reference");
|
||||
return;
|
||||
}
|
||||
|
||||
stop_upload = false;
|
||||
upload_thread = std::thread([this, request_id, method, file_path, file_name, source_path, print_host = std::move(print_host)]() mutable {
|
||||
std::string error_message;
|
||||
|
||||
PrintHostUpload upload_data;
|
||||
upload_data.use_3mf = false;
|
||||
upload_data.post_action = PrintHostPostUploadAction::None;
|
||||
upload_data.source_path = source_path;
|
||||
upload_data.upload_path = path_from_utf8(file_name);
|
||||
|
||||
const bool success = print_host->upload(
|
||||
std::move(upload_data),
|
||||
[this, request_id](Http::Progress progress, bool& cancel) {
|
||||
cancel = stop_upload.load();
|
||||
json data = {
|
||||
{"uploadedBytes", static_cast<uint64_t>(progress.ulnow)},
|
||||
{"totalBytes", static_cast<uint64_t>(progress.ultotal)}
|
||||
};
|
||||
send_ipc_message("event", request_id, "upload_progress", 0, "", dump_json(data));
|
||||
},
|
||||
[&error_message](wxString error) {
|
||||
error_message = error.ToUTF8().data();
|
||||
},
|
||||
[this, request_id](wxString tag, wxString status) {
|
||||
json data = {
|
||||
{"tag", tag.ToUTF8().data()},
|
||||
{"status", status.ToUTF8().data()}
|
||||
};
|
||||
send_ipc_message("event", request_id, "upload_info", 0, "", dump_json(data));
|
||||
});
|
||||
|
||||
upload_in_progress = false;
|
||||
|
||||
if (success) {
|
||||
json data = {
|
||||
{"success", true},
|
||||
{"filePath", file_path},
|
||||
{"fileName", file_name}
|
||||
};
|
||||
send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
|
||||
} else {
|
||||
if (error_message.empty())
|
||||
error_message = "Upload failed";
|
||||
send_ipc_message("response", request_id, method, 1, error_message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void handle_open_file_dialog_request(const std::string& request_id, const std::string& method, const std::string& params_json)
|
||||
{
|
||||
json params = json::parse(params_json, nullptr, false);
|
||||
if (params.is_discarded())
|
||||
params = json::object();
|
||||
|
||||
const std::string filter = json_string(params, "filter").empty() ? "All files (*.*)|*.*" : json_string(params, "filter");
|
||||
|
||||
wxWindow* parent = owner().GetParent();
|
||||
if (parent == nullptr)
|
||||
parent = wxGetApp().GetTopWindow();
|
||||
|
||||
wxFileDialog open_file_dialog(parent, _L("Open File"), "", "", wxString::FromUTF8(filter), wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
json data = json::object();
|
||||
data["files"] = json::array();
|
||||
if (open_file_dialog.ShowModal() != wxID_CANCEL)
|
||||
data["files"].push_back(open_file_dialog.GetPath().ToUTF8().data());
|
||||
|
||||
send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
|
||||
}
|
||||
|
||||
void handle_get_sn_request(const std::string& request_id, const std::string& method)
|
||||
{
|
||||
if (sn_request_in_progress.exchange(true)) {
|
||||
send_ipc_message("response", request_id, method, 1, "SN request already in progress");
|
||||
return;
|
||||
}
|
||||
|
||||
if (sn_thread.joinable())
|
||||
sn_thread.join();
|
||||
|
||||
sn_thread = std::thread([this, request_id, method]() {
|
||||
std::string sn;
|
||||
|
||||
DynamicPrintConfig* config = get_active_printer_config();
|
||||
std::unique_ptr<PrintHost> print_host(config == nullptr ? nullptr : PrintHost::get_print_host(config));
|
||||
if (print_host != nullptr)
|
||||
sn = print_host->get_sn();
|
||||
|
||||
sn_request_in_progress = false;
|
||||
json data = {
|
||||
{"sn", sn}
|
||||
};
|
||||
send_ipc_message("response", request_id, method, 0, "success", dump_json(data));
|
||||
});
|
||||
}
|
||||
|
||||
std::atomic<bool> upload_in_progress { false };
|
||||
std::atomic<bool> sn_request_in_progress { false };
|
||||
std::atomic<bool> stop_upload { false };
|
||||
std::thread upload_thread;
|
||||
std::thread sn_thread;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<PrinterWebViewHandler> create_printer_webview_handler(PrinterWebView& owner)
|
||||
{
|
||||
auto cfg = get_active_printer_config();
|
||||
if(cfg == nullptr) return nullptr;
|
||||
|
||||
const auto host_type = cfg->option<ConfigOptionEnum<PrintHostType>>("host_type")->value;
|
||||
switch (host_type)
|
||||
{
|
||||
case PrintHostType::htElegooLink:
|
||||
return std::make_unique<ElegooPrinterWebViewHandler>(owner);
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
36
src/slic3r/GUI/PrinterWebViewHandler.hpp
Normal file
36
src/slic3r/GUI/PrinterWebViewHandler.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef slic3r_PrinterWebViewHandler_hpp_
|
||||
#define slic3r_PrinterWebViewHandler_hpp_
|
||||
|
||||
#include <memory>
|
||||
#include <wx/webview.h>
|
||||
#include <wx/string.h>
|
||||
|
||||
class wxWebView;
|
||||
|
||||
namespace Slic3r {
|
||||
namespace GUI {
|
||||
|
||||
class PrinterWebView;
|
||||
|
||||
class PrinterWebViewHandler {
|
||||
public:
|
||||
explicit PrinterWebViewHandler(PrinterWebView& owner);
|
||||
virtual ~PrinterWebViewHandler();
|
||||
|
||||
virtual void on_loaded(wxWebViewEvent &evt);
|
||||
virtual void on_script_message(wxWebViewEvent &evt);
|
||||
|
||||
protected:
|
||||
PrinterWebView& owner() const;
|
||||
wxWebView* browser() const;
|
||||
|
||||
private:
|
||||
PrinterWebView& m_owner;
|
||||
};
|
||||
|
||||
std::unique_ptr<PrinterWebViewHandler> create_printer_webview_handler(PrinterWebView& owner);
|
||||
|
||||
} // GUI
|
||||
} // Slic3r
|
||||
|
||||
#endif /* slic3r_PrinterWebViewHandler_hpp_ */
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/nowide/convert.hpp>
|
||||
#include <boost/nowide/fstream.hpp>
|
||||
#include <boost/uuid/uuid.hpp>
|
||||
#include <boost/uuid/uuid_generators.hpp>
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
@@ -58,6 +59,69 @@ namespace Slic3r {
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr const char* ELEGOO_CC2_DEFAULT_TOKEN = "123456";
|
||||
|
||||
enum class ElegooPrinterType {
|
||||
Other,
|
||||
CC,
|
||||
CC2,
|
||||
};
|
||||
|
||||
ElegooPrinterType classify_printer_model(const std::string& printer_model)
|
||||
{
|
||||
if (!boost::algorithm::starts_with(printer_model, "Elegoo Centauri"))
|
||||
return ElegooPrinterType::Other;
|
||||
|
||||
const auto last_char = printer_model.find_last_not_of(" \t\r\n");
|
||||
if (last_char != std::string::npos && printer_model[last_char] == '2')
|
||||
return ElegooPrinterType::CC2;
|
||||
|
||||
return ElegooPrinterType::CC;
|
||||
}
|
||||
|
||||
std::string get_cc2_token(const std::string& apikey)
|
||||
{
|
||||
return apikey.empty() ? ELEGOO_CC2_DEFAULT_TOKEN : apikey;
|
||||
}
|
||||
|
||||
bool parse_cc2_response(const std::string& body, std::string& error_message, std::string* serial_number = nullptr)
|
||||
{
|
||||
try {
|
||||
pt::ptree root;
|
||||
std::istringstream is(body);
|
||||
pt::read_json(is, root);
|
||||
|
||||
const int error_code = root.get<int>("error_code", -1);
|
||||
if (error_code != 0) {
|
||||
error_message = root.get<std::string>("message", "Printer returned an error");
|
||||
if (error_message.empty())
|
||||
error_message = "Printer returned an error";
|
||||
error_message += " (" + std::to_string(error_code) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (serial_number != nullptr) {
|
||||
const auto system_info = root.get_child_optional("system_info");
|
||||
if (!system_info) {
|
||||
error_message = "Missing system_info in response";
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto sn = system_info->get_optional<std::string>("sn");
|
||||
if (!sn || sn->empty()) {
|
||||
error_message = "Missing printer serial number in response";
|
||||
return false;
|
||||
}
|
||||
*serial_number = *sn;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (const std::exception&) {
|
||||
error_message = "Error parsing response";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_host_from_url(const std::string& url_in)
|
||||
{
|
||||
std::string url = url_in;
|
||||
@@ -220,15 +284,126 @@ namespace Slic3r {
|
||||
return ret_val;
|
||||
}
|
||||
|
||||
std::string path_to_utf8(const boost::filesystem::path& path)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return boost::nowide::narrow(path.wstring());
|
||||
#else
|
||||
return path.string();
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string filename_to_utf8(const boost::filesystem::path& path)
|
||||
{
|
||||
#ifdef WIN32
|
||||
return boost::nowide::narrow(path.filename().wstring());
|
||||
#else
|
||||
return path.filename().string();
|
||||
#endif
|
||||
}
|
||||
|
||||
} //namespace
|
||||
|
||||
|
||||
ElegooLink::ElegooLink(DynamicPrintConfig *config):
|
||||
OctoPrint(config) {
|
||||
OctoPrint(config), m_printerModel(config->opt_string("printer_model")) {
|
||||
|
||||
}
|
||||
|
||||
std::string ElegooLink::get_print_host_webui(DynamicPrintConfig* config)
|
||||
{
|
||||
if (config == nullptr)
|
||||
return {};
|
||||
|
||||
std::string fallback_webui = config->opt_string("print_host_webui");
|
||||
if (fallback_webui.empty())
|
||||
fallback_webui = config->opt_string("print_host");
|
||||
if (!fallback_webui.empty()) {
|
||||
const bool has_http_scheme = boost::algorithm::istarts_with(fallback_webui, "http");
|
||||
const bool has_file_scheme = boost::algorithm::istarts_with(fallback_webui, "file:");
|
||||
|
||||
if (!has_http_scheme && !has_file_scheme)
|
||||
fallback_webui = "http://" + fallback_webui;
|
||||
}
|
||||
|
||||
const std::string host = config->opt_string("print_host");
|
||||
if (host.empty())
|
||||
return fallback_webui;
|
||||
|
||||
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::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=" + get_host_from_url(host) + "&id=elegoo_123456";
|
||||
|
||||
const std::string lang = GUI::wxGetApp().current_language_code_safe().utf8_string();
|
||||
if (!lang.empty())
|
||||
web_path += "&lang=" + lang;
|
||||
|
||||
if (GUI::get_app_config()->get_bool("developer_mode"))
|
||||
web_path += "&dev=true";
|
||||
|
||||
return web_path;
|
||||
}
|
||||
|
||||
std::string ElegooLink::cc2_token() const
|
||||
{
|
||||
return get_cc2_token(m_apikey);
|
||||
}
|
||||
|
||||
std::string ElegooLink::make_cc2_info_url() const
|
||||
{
|
||||
return make_url("system/info?X-Token=" + escape_string(cc2_token()));
|
||||
}
|
||||
|
||||
std::string ElegooLink::make_cc2_upload_url() const
|
||||
{
|
||||
return make_url("upload");
|
||||
}
|
||||
|
||||
const char* ElegooLink::get_name() const { return "ElegooLink"; }
|
||||
PrintHostPostUploadActions ElegooLink::get_post_upload_actions() const
|
||||
{
|
||||
if (classify_printer_model(m_printerModel) == ElegooPrinterType::CC2) {
|
||||
return PrintHostPostUploadAction::None;
|
||||
} else {
|
||||
return PrintHostPostUploadAction::StartPrint;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool ElegooLink::elegoo_test(wxString& msg) const{
|
||||
|
||||
@@ -268,11 +443,54 @@ namespace Slic3r {
|
||||
return res;
|
||||
}
|
||||
bool ElegooLink::test(wxString &curl_msg) const{
|
||||
if(OctoPrint::test(curl_msg)){
|
||||
return true;
|
||||
switch (classify_printer_model(m_printerModel)) {
|
||||
case ElegooPrinterType::Other:
|
||||
return OctoPrint::test(curl_msg);
|
||||
case ElegooPrinterType::CC2:
|
||||
return elegoo_cc2_test(curl_msg);
|
||||
case ElegooPrinterType::CC:
|
||||
return elegoo_test(curl_msg);
|
||||
}
|
||||
curl_msg="";
|
||||
return elegoo_test(curl_msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ElegooLink::elegoo_cc2_test(wxString& msg) const
|
||||
{
|
||||
const char* name = get_name();
|
||||
bool res = true;
|
||||
const auto token = cc2_token();
|
||||
auto url = make_cc2_info_url();
|
||||
auto http = Http::get(std::move(url));
|
||||
|
||||
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: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
res = false;
|
||||
if (status == 401 || status == 403)
|
||||
msg = format_error(body, "Invalid access code", status);
|
||||
else
|
||||
msg = format_error(body, error.empty() ? "CC2 device not detected" : error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: Got CC2 device info: %2%") % name % body;
|
||||
std::string error_message;
|
||||
std::string serial_number;
|
||||
if (!parse_cc2_response(body, error_message, &serial_number)) {
|
||||
res = false;
|
||||
msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
|
||||
return;
|
||||
}
|
||||
res = true;
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.on_ip_resolve([&](std::string address) {
|
||||
msg = GUI::from_u8(address);
|
||||
})
|
||||
#endif // WIN32
|
||||
.perform_sync();
|
||||
return res;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
@@ -320,12 +538,53 @@ namespace Slic3r {
|
||||
}
|
||||
bool ElegooLink::test_with_resolved_ip(wxString& msg) const
|
||||
{
|
||||
// Elegoo supports both otcoprint and Elegoo link
|
||||
if (OctoPrint::test_with_resolved_ip(msg)) {
|
||||
return true;
|
||||
switch (classify_printer_model(m_printerModel)) {
|
||||
case ElegooPrinterType::Other:
|
||||
return OctoPrint::test_with_resolved_ip(msg);
|
||||
case ElegooPrinterType::CC2:
|
||||
return elegoo_cc2_test_with_resolved_ip(msg);
|
||||
case ElegooPrinterType::CC:
|
||||
return elegoo_test_with_resolved_ip(msg);
|
||||
}
|
||||
msg = "";
|
||||
return elegoo_test_with_resolved_ip(msg);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ElegooLink::elegoo_cc2_test_with_resolved_ip(wxString& msg) const
|
||||
{
|
||||
const char* name = get_name();
|
||||
bool res = true;
|
||||
const auto token = cc2_token();
|
||||
auto url = substitute_host(make_cc2_info_url(), GUI::into_u8(msg));
|
||||
std::string host_header = get_host_from_url(m_host);
|
||||
auto http = Http::get(url);
|
||||
msg.Clear();
|
||||
|
||||
http.header("Host", host_header);
|
||||
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 at %2% : %3%, HTTP %4%, body: `%5%`") % name % url %
|
||||
error % status % body;
|
||||
res = false;
|
||||
if (status == 401 || status == 403)
|
||||
msg = format_error(body, "Invalid access code", status);
|
||||
else
|
||||
msg = format_error(body, error.empty() ? "CC2 device not detected" : error, status);
|
||||
})
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
std::string error_message;
|
||||
std::string serial_number;
|
||||
if (!parse_cc2_response(body, error_message, &serial_number)) {
|
||||
res = false;
|
||||
msg = format_error(body, error_message.empty() ? "CC2 device not detected" : error_message, status);
|
||||
return;
|
||||
}
|
||||
res = true;
|
||||
})
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
.perform_sync();
|
||||
|
||||
return res;
|
||||
}
|
||||
#endif // WIN32
|
||||
|
||||
@@ -343,25 +602,31 @@ namespace Slic3r {
|
||||
#ifdef WIN32
|
||||
bool ElegooLink::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
|
||||
{
|
||||
wxString test_msg_or_host_ip = "";
|
||||
const auto printer_type = classify_printer_model(m_printerModel);
|
||||
|
||||
if (printer_type == ElegooPrinterType::Other)
|
||||
return OctoPrint::upload_inner_with_resolved_ip(std::move(upload_data), prorgess_fn, error_fn, info_fn, resolved_addr);
|
||||
|
||||
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().
|
||||
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
|
||||
//Elegoo supports both otcoprint and Elegoo link
|
||||
if(OctoPrint::test_with_resolved_ip(test_msg_or_host_ip)){
|
||||
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
|
||||
|
||||
if (printer_type == ElegooPrinterType::CC2) {
|
||||
wxString cc2_msg = GUI::from_u8(resolved_addr.to_string());
|
||||
if (!elegoo_cc2_test_with_resolved_ip(cc2_msg)) {
|
||||
error_fn(std::move(cc2_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url = substitute_host(make_cc2_upload_url(), resolved_addr.to_string());
|
||||
info_fn(L"resolve", boost::nowide::widen(url));
|
||||
return loopUploadCC2(url, get_host_from_url(m_host), std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||
}
|
||||
|
||||
test_msg_or_host_ip = GUI::from_u8(resolved_addr.to_string());
|
||||
if(!elegoo_test_with_resolved_ip(test_msg_or_host_ip)){
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
wxString legacy_msg = GUI::from_u8(resolved_addr.to_string());
|
||||
if (!elegoo_test_with_resolved_ip(legacy_msg)) {
|
||||
error_fn(std::move(legacy_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
std::string url = substitute_host(make_url("uploadFile/upload"), resolved_addr.to_string());
|
||||
info_fn(L"resolve", boost::nowide::widen(url));
|
||||
|
||||
@@ -372,23 +637,46 @@ namespace Slic3r {
|
||||
|
||||
bool ElegooLink::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
// 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;
|
||||
//Elegoo supports both otcoprint and Elegoo link
|
||||
if(OctoPrint::test(test_msg_or_host_ip)){
|
||||
return OctoPrint::upload_inner_with_host(upload_data, prorgess_fn, error_fn, info_fn);
|
||||
const auto printer_type = classify_printer_model(m_printerModel);
|
||||
|
||||
if (printer_type == ElegooPrinterType::Other)
|
||||
return OctoPrint::upload_inner_with_host(std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||
|
||||
if (printer_type == ElegooPrinterType::CC2) {
|
||||
wxString cc2_msg;
|
||||
if (!elegoo_cc2_test(cc2_msg)) {
|
||||
error_fn(std::move(cc2_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
#ifdef WIN32
|
||||
if (m_host.find("https://") == 0 || cc2_msg.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
|
||||
#endif // _WIN32
|
||||
{
|
||||
url = make_cc2_upload_url();
|
||||
}
|
||||
#ifdef WIN32
|
||||
else {
|
||||
info_fn(L"resolve", cc2_msg);
|
||||
url = substitute_host(make_cc2_upload_url(), GUI::into_u8(cc2_msg));
|
||||
BOOST_LOG_TRIVIAL(info) << "CC2 upload address after ip resolve: " << url;
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
return loopUploadCC2(url, get_host_from_url(m_host), std::move(upload_data), prorgess_fn, error_fn, info_fn);
|
||||
}
|
||||
test_msg_or_host_ip="";
|
||||
if(!elegoo_test(test_msg_or_host_ip)){
|
||||
error_fn(std::move(test_msg_or_host_ip));
|
||||
|
||||
wxString legacy_msg;
|
||||
if(!elegoo_test(legacy_msg)){
|
||||
error_fn(std::move(legacy_msg));
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string url;
|
||||
#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_bool("allow_ip_resolve"))
|
||||
if (m_host.find("https://") == 0 || legacy_msg.empty() || !GUI::get_app_config()->get_bool("allow_ip_resolve"))
|
||||
#endif // _WIN32
|
||||
{
|
||||
// If https is entered we assume signed ceritificate is being used
|
||||
@@ -403,8 +691,8 @@ namespace Slic3r {
|
||||
// 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("uploadFile/upload"), GUI::into_u8(test_msg_or_host_ip));
|
||||
info_fn(L"resolve", legacy_msg);
|
||||
url = substitute_host(make_url("uploadFile/upload"), GUI::into_u8(legacy_msg));
|
||||
BOOST_LOG_TRIVIAL(info) << "Upload address after ip resolve: " << url;
|
||||
}
|
||||
#endif // _WIN32
|
||||
@@ -490,11 +778,11 @@ namespace Slic3r {
|
||||
bool ElegooLink::loopUpload(std::string url, PrintHostUpload upload_data, ProgressFn progress_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
const char* name = get_name();
|
||||
const auto upload_filename = upload_data.upload_path.filename().string();
|
||||
std::string source_path = upload_data.source_path.string();
|
||||
const auto upload_filename = filename_to_utf8(upload_data.upload_path);
|
||||
std::string source_path = path_to_utf8(upload_data.source_path);
|
||||
|
||||
// calc file size
|
||||
std::ifstream file(source_path, std::ios::binary | std::ios::ate);
|
||||
boost::nowide::ifstream file(source_path, std::ios::binary | std::ios::ate);
|
||||
std::streamsize size = file.tellg();
|
||||
file.close();
|
||||
const std::string fileSize = std::to_string(size);
|
||||
@@ -583,6 +871,146 @@ namespace Slic3r {
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ElegooLink::uploadPartCC2(Http& http,
|
||||
const std::string& host_header,
|
||||
const std::string& token,
|
||||
const std::string& md5,
|
||||
const boost::filesystem::path& path,
|
||||
const std::string& filename,
|
||||
size_t filesize,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn) const
|
||||
{
|
||||
const char* name = get_name();
|
||||
|
||||
boost::nowide::ifstream file(path, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
error_fn(_L("Failed to open file for upload."));
|
||||
return false;
|
||||
}
|
||||
|
||||
file.seekg(static_cast<std::streamoff>(offset), std::ios::beg);
|
||||
std::string chunk(length, '\0');
|
||||
file.read(chunk.data(), static_cast<std::streamsize>(length));
|
||||
if (!file && static_cast<size_t>(file.gcount()) != length) {
|
||||
error_fn(_L("Failed to read file chunk for upload."));
|
||||
return false;
|
||||
}
|
||||
chunk.resize(static_cast<size_t>(file.gcount()));
|
||||
|
||||
const size_t end_offset = offset + chunk.size() - 1;
|
||||
const auto range = std::string("bytes ") + std::to_string(offset) + "-" + std::to_string(end_offset) + "/" + std::to_string(filesize);
|
||||
|
||||
bool result = false;
|
||||
http.headers_reset();
|
||||
if (!host_header.empty())
|
||||
http.header("Host", host_header);
|
||||
http.header("Accept", "application/json")
|
||||
.header("Content-Type", "application/octet-stream")
|
||||
.header("Content-Length", std::to_string(chunk.size()))
|
||||
.header("Content-Range", range)
|
||||
.header("X-File-Name", filename)
|
||||
.header("X-File-MD5", md5)
|
||||
.header("X-Token", token)
|
||||
.set_post_body(chunk)
|
||||
.on_complete([&](std::string body, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(debug) << boost::format("%1%: CC2 chunk uploaded: HTTP %2%: %3%") % name % status % body;
|
||||
std::string error_message;
|
||||
if (!parse_cc2_response(body, error_message)) {
|
||||
error_fn(format_error(body, error_message.empty() ? "CC2 upload failed" : error_message, status));
|
||||
return;
|
||||
}
|
||||
result = true;
|
||||
})
|
||||
.on_error([&](std::string body, std::string error, unsigned status) {
|
||||
BOOST_LOG_TRIVIAL(error) << boost::format("%1%: Error uploading CC2 chunk: %2%, HTTP %3%, body: `%4%`") % name % error % status % body;
|
||||
if (status == 401 || status == 403)
|
||||
error_fn(format_error(body, "Invalid access code", status));
|
||||
else
|
||||
error_fn(format_error(body, error.empty() ? "CC2 upload failed" : error, status));
|
||||
})
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
if (progress.ultotal == progress.ulnow)
|
||||
return;
|
||||
prorgess_fn(std::move(progress), cancel);
|
||||
if (cancel)
|
||||
BOOST_LOG_TRIVIAL(info) << name << ": CC2 upload canceled";
|
||||
})
|
||||
#ifdef WIN32
|
||||
.ssl_revoke_best_effort(m_ssl_revoke_best_effort)
|
||||
#endif
|
||||
.perform_sync();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool ElegooLink::loopUploadCC2(std::string url,
|
||||
const std::string& host_header,
|
||||
PrintHostUpload upload_data,
|
||||
ProgressFn progress_fn,
|
||||
ErrorFn error_fn,
|
||||
InfoFn info_fn) const
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(info) << get_name() << ": Uploading file to Elegoo CC2";
|
||||
const auto upload_filename = filename_to_utf8(upload_data.upload_path);
|
||||
std::string source_path = path_to_utf8(upload_data.source_path);
|
||||
std::string md5;
|
||||
bbl_calc_md5(source_path, md5);
|
||||
std::transform(md5.begin(), md5.end(), md5.begin(), [](unsigned char ch) { return static_cast<char>(std::tolower(ch)); });
|
||||
|
||||
boost::nowide::ifstream file(source_path, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
error_fn(_L("Failed to open file for upload."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = file.tellg();
|
||||
file.close();
|
||||
if (size <= 0) {
|
||||
error_fn(_L("The file is empty or could not be read."));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (md5.empty()) {
|
||||
error_fn(_L("Failed to calculate file checksum."));
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string token = cc2_token();
|
||||
const int packageCount = static_cast<int>((size + MAX_UPLOAD_PACKAGE_LENGTH - 1) / MAX_UPLOAD_PACKAGE_LENGTH);
|
||||
auto http = Http::put2(url);
|
||||
http.timeout_connect(30)
|
||||
.timeout_max(180);
|
||||
|
||||
bool res = false;
|
||||
for (int i = 0; i < packageCount; ++i) {
|
||||
const size_t offset = MAX_UPLOAD_PACKAGE_LENGTH * static_cast<size_t>(i);
|
||||
size_t length = MAX_UPLOAD_PACKAGE_LENGTH;
|
||||
if (i == packageCount - 1 && size % MAX_UPLOAD_PACKAGE_LENGTH > 0)
|
||||
length = static_cast<size_t>(size % MAX_UPLOAD_PACKAGE_LENGTH);
|
||||
|
||||
res = uploadPartCC2(
|
||||
http, host_header, token, md5, source_path, upload_filename, static_cast<size_t>(size), offset, length,
|
||||
[size, i, progress_fn](Http::Progress progress, bool& cancel) {
|
||||
const size_t uploaded = static_cast<size_t>(i) * MAX_UPLOAD_PACKAGE_LENGTH + progress.ulnow;
|
||||
Http::Progress merged(0, 0, static_cast<size_t>(size), std::min(static_cast<size_t>(size), uploaded), progress.buffer,
|
||||
progress.upload_spd);
|
||||
progress_fn(merged, cancel);
|
||||
},
|
||||
error_fn);
|
||||
if (!res)
|
||||
break;
|
||||
}
|
||||
|
||||
if (res && upload_data.post_action == PrintHostPostUploadAction::StartPrint)
|
||||
BOOST_LOG_TRIVIAL(info) << get_name() << ": CC2 upload completed; start print is not supported.";
|
||||
|
||||
(void) info_fn;
|
||||
return res;
|
||||
}
|
||||
|
||||
bool ElegooLink::upload(PrintHostUpload upload_data, ProgressFn prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const
|
||||
{
|
||||
#ifndef WIN32
|
||||
|
||||
@@ -20,14 +20,16 @@ class ElegooLink : public OctoPrint
|
||||
public:
|
||||
ElegooLink(DynamicPrintConfig *config);
|
||||
~ElegooLink() override = default;
|
||||
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
||||
const char* get_name() const override;
|
||||
virtual 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 prorgess_fn, ErrorFn error_fn, InfoFn info_fn) const override;
|
||||
std::string get_sn() 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; }
|
||||
PrintHostPostUploadActions get_post_upload_actions() const override;
|
||||
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;
|
||||
@@ -39,9 +41,9 @@ protected:
|
||||
virtual bool test_with_resolved_ip(wxString& curl_msg) const override;
|
||||
bool elegoo_test_with_resolved_ip(wxString& curl_msg) const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
bool elegoo_test(wxString& curl_msg) const;
|
||||
bool elegoo_cc2_test(wxString& curl_msg) const;
|
||||
bool print(WebSocketClient& client,
|
||||
std::string timeLapse,
|
||||
std::string heatedBedLeveling,
|
||||
@@ -54,6 +56,12 @@ private:
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn,
|
||||
InfoFn info_fn) const;
|
||||
bool loopUploadCC2(std::string url,
|
||||
const std::string& host_header,
|
||||
PrintHostUpload upload_data,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn,
|
||||
InfoFn info_fn) const;
|
||||
|
||||
bool uploadPart(Http &http,
|
||||
std::string md5,
|
||||
@@ -66,6 +74,26 @@ private:
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn,
|
||||
InfoFn info_fn) const;
|
||||
bool uploadPartCC2(Http& http,
|
||||
const std::string& host_header,
|
||||
const std::string& token,
|
||||
const std::string& md5,
|
||||
const boost::filesystem::path& path,
|
||||
const std::string& filename,
|
||||
size_t filesize,
|
||||
size_t offset,
|
||||
size_t length,
|
||||
ProgressFn prorgess_fn,
|
||||
ErrorFn error_fn) const;
|
||||
|
||||
std::string cc2_token() const;
|
||||
std::string make_cc2_info_url() const;
|
||||
std::string make_cc2_upload_url() const;
|
||||
#ifdef WIN32
|
||||
bool elegoo_cc2_test_with_resolved_ip(wxString& curl_msg) const;
|
||||
#endif
|
||||
|
||||
std::string m_printerModel;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -570,6 +570,21 @@ Http& Http::header(std::string name, const std::string &value)
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::headers_reset()
|
||||
{
|
||||
if (!p) { return *this; }
|
||||
|
||||
::curl_slist_free_all(p->headerlist);
|
||||
p->headerlist = nullptr;
|
||||
p->headerlist = curl_slist_append(p->headerlist, "Expect:");
|
||||
|
||||
std::lock_guard<std::mutex> l(g_mutex);
|
||||
for (auto it = extra_headers.begin(); it != extra_headers.end(); ++it)
|
||||
this->header(it->first, it->second);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
Http& Http::remove_header(std::string name)
|
||||
{
|
||||
if (p) {
|
||||
|
||||
@@ -109,6 +109,8 @@ public:
|
||||
Http& set_range(const std::string& range);
|
||||
// Sets a HTTP header field.
|
||||
Http& header(std::string name, const std::string &value);
|
||||
// Clears all custom headers and restores default implicit headers.
|
||||
Http& headers_reset();
|
||||
// Removes a header field.
|
||||
Http& remove_header(std::string name);
|
||||
// Authorization by HTTP digest, based on RFC2617.
|
||||
|
||||
@@ -74,6 +74,40 @@ PrintHost* PrintHost::get_print_host(DynamicPrintConfig *config)
|
||||
}
|
||||
}
|
||||
|
||||
std::string PrintHost::get_print_host_webui(DynamicPrintConfig* config)
|
||||
{
|
||||
if (config == nullptr)
|
||||
return {};
|
||||
|
||||
std::string webui_url;
|
||||
const auto* host_type_opt = config->option<ConfigOptionEnum<PrintHostType>>("host_type");
|
||||
const auto host_type = host_type_opt != nullptr ? host_type_opt->value : htOctoPrint;
|
||||
|
||||
switch (host_type) {
|
||||
case htElegooLink: {
|
||||
webui_url = ElegooLink::get_print_host_webui(config);
|
||||
break;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
if (webui_url.empty()) {
|
||||
webui_url = config->opt_string("print_host_webui");
|
||||
if (webui_url.empty())
|
||||
webui_url = config->opt_string("print_host");
|
||||
if (webui_url.empty())
|
||||
return webui_url;
|
||||
}
|
||||
|
||||
const bool has_http_scheme = boost::algorithm::istarts_with(webui_url, "http");
|
||||
const bool has_file_scheme = boost::algorithm::istarts_with(webui_url, "file:");
|
||||
|
||||
if (!has_http_scheme && !has_file_scheme)
|
||||
webui_url = "http://" + webui_url;
|
||||
|
||||
return webui_url;
|
||||
}
|
||||
|
||||
wxString PrintHost::format_error(const std::string &body, const std::string &error, unsigned status) const
|
||||
{
|
||||
if (status != 0) {
|
||||
|
||||
@@ -63,6 +63,12 @@ public:
|
||||
// A print host usually does not support multiple printers, with the exception of Repetier server.
|
||||
virtual bool supports_multiple_printers() const { return false; }
|
||||
virtual std::string get_host() const = 0;
|
||||
/**
|
||||
* Get the serial number for connecting to the printer.
|
||||
* For Elegoo CC2, the device details connection to the printer requires the serial number.
|
||||
* Other print hosts do not need to implement this interface, and it returns an empty string by default.
|
||||
*/
|
||||
virtual std::string get_sn() const { return ""; }
|
||||
|
||||
// Support for Repetier server multiple groups & printers. Not supported by other print hosts.
|
||||
// Returns false if not supported. May throw HostNetworkError.
|
||||
@@ -73,6 +79,7 @@ public:
|
||||
virtual bool get_storage(wxArrayString& /*storage_path*/, wxArrayString& /*storage_name*/) const { return false; }
|
||||
|
||||
static PrintHost* get_print_host(DynamicPrintConfig *config);
|
||||
static std::string get_print_host_webui(DynamicPrintConfig *config);
|
||||
|
||||
//Support for cloud webui login
|
||||
virtual bool is_cloud() const { return false; }
|
||||
|
||||
Reference in New Issue
Block a user