diff --git a/src/slic3r/GUI/DeviceCore/DevManager.cpp b/src/slic3r/GUI/DeviceCore/DevManager.cpp index 5c32f42c2b..6f4885f4cc 100644 --- a/src/slic3r/GUI/DeviceCore/DevManager.cpp +++ b/src/slic3r/GUI/DeviceCore/DevManager.cpp @@ -518,7 +518,7 @@ namespace Slic3r #if !BBL_RELEASE_TO_PUBLIC it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); #else - it->second->connect(it->second->local_use_ssl_for_mqtt); + it->second->connect(it->second->local_use_ssl); #endif it->second->set_lan_mode_connection_state(true); } @@ -542,7 +542,7 @@ namespace Slic3r #if !BBL_RELEASE_TO_PUBLIC it->second->connect(Slic3r::GUI::wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); #else - it->second->connect(it->second->local_use_ssl_for_mqtt); + it->second->connect(it->second->local_use_ssl); #endif it->second->set_lan_mode_connection_state(true); } diff --git a/src/slic3r/GUI/DeviceManager.cpp b/src/slic3r/GUI/DeviceManager.cpp index bbf0b50ca0..1542b5fbef 100644 --- a/src/slic3r/GUI/DeviceManager.cpp +++ b/src/slic3r/GUI/DeviceManager.cpp @@ -363,6 +363,30 @@ std::string MachineObject::get_ftp_folder() return DevPrinterConfigUtil::get_ftp_folder(printer_type); } +std::string MachineObject::dev_id_from_address(const std::string& host, const std::string& port) +{ + std::string result = host; + // Normalize host: strip protocol and path + if (result.find("http://") == 0) + result = result.substr(7); + else if (result.find("https://") == 0) + result = result.substr(8); + auto slash = result.find('/'); + if (slash != std::string::npos) + result = result.substr(0, slash); + + // Build full address (host:port) + if (!port.empty()) { + // Strip inline port if present (port comes from printhost_port) + auto colon = result.find(':'); + if (colon != std::string::npos) + result = result.substr(0, colon); + + result += ":" + port; + } + return result; +} + bool MachineObject::HasRecentCloudMessage() { auto curr_time = std::chrono::system_clock::now(); diff --git a/src/slic3r/GUI/DeviceManager.hpp b/src/slic3r/GUI/DeviceManager.hpp index 4a871e6bdb..5454c62ca9 100644 --- a/src/slic3r/GUI/DeviceManager.hpp +++ b/src/slic3r/GUI/DeviceManager.hpp @@ -164,7 +164,11 @@ public: std::string get_dev_id() const { return dev_id; } void set_dev_id(std::string val) { dev_id = val; } - bool local_use_ssl_for_mqtt { true }; + // Generate consistent dev_id from host address and optional port + // Returns "host:port" or "host" if port is empty + static std::string dev_id_from_address(const std::string& host, const std::string& port = ""); + + bool local_use_ssl { true }; bool local_use_ssl_for_ftp { true }; std::string get_ftp_folder(); diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index f891b839f1..1376e4ce24 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -3520,32 +3520,6 @@ void GUI_App::switch_printer_agent(const std::string& agent_id) // Swap the agent m_agent->set_printer_agent(new_printer_agent); - // Update dependent managers - if (m_device_manager) { - m_device_manager->set_agent(m_agent); - - // If there's a selected machine that was deferred due to no printer agent, - // trigger a connection now that the agent is ready - MachineObject* selected = m_device_manager->get_selected_machine(); - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": checking for deferred connection - selected=" - << (selected ? selected->get_dev_id() : "null"); - if (selected) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": selected machine - is_lan_mode=" << selected->is_lan_mode_printer() - << " is_connected=" << selected->is_connected(); - } - if (selected && selected->is_lan_mode_printer() && !selected->is_connected()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": connecting deferred LAN machine dev_id=" << selected->get_dev_id(); -#if !BBL_RELEASE_TO_PUBLIC - selected->connect(app_config->get("enable_ssl_for_mqtt") == "true" ? true : false); -#else - selected->connect(selected->local_use_ssl_for_mqtt); -#endif - selected->set_lan_mode_connection_state(true); - } - } else { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": m_device_manager is null, cannot check for deferred connection"; - } - // Auto-switch MachineObject select_machine(effective_agent_id); @@ -3571,41 +3545,12 @@ void GUI_App::select_machine(const std::string& agent_id) std::string print_host = host_cfg->opt_string("print_host"); if (print_host.empty()) { - if (auto* physical_cfg = preset_bundle->physical_printers.get_selected_printer_config()) { - if (!physical_cfg->opt_string("print_host").empty()) { - host_cfg = physical_cfg; - print_host = host_cfg->opt_string("print_host"); - } - } - } - if (print_host.empty()) { - BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": no print_host configured, skipping auto-switch"; return; } - - // Normalize host: strip protocol and path - std::string host = print_host; - if (host.find("http://") == 0) host = host.substr(7); - else if (host.find("https://") == 0) host = host.substr(8); - auto slash = host.find('/'); - if (slash != std::string::npos) host = host.substr(0, slash); - // Strip inline port if present (port comes from printhost_port) - auto colon = host.find(':'); - if (colon != std::string::npos) host = host.substr(0, colon); - - // Get port from separate config std::string port = host_cfg->opt_string("printhost_port"); - // Build full address (host:port) for dev_ip - std::string full_addr = host; - if (!port.empty()) { - full_addr += ":" + port; - } - - // Generate dev_id (replace . and : with _) - std::string dev_id = full_addr; - std::replace(dev_id.begin(), dev_id.end(), '.', '_'); - std::replace(dev_id.begin(), dev_id.end(), ':', '_'); + // Generate dev_id from host and port + std::string dev_id = MachineObject::dev_id_from_address(print_host, port); // Check if already exists by dev_id MachineObject* existing = m_device_manager->get_local_machine(dev_id); @@ -3614,9 +3559,8 @@ void GUI_App::select_machine(const std::string& agent_id) if (!existing) { auto local_machines = m_device_manager->get_local_machinelist(); for (auto& [id, machine] : local_machines) { - if (machine && machine->get_dev_ip() == full_addr) { + if (machine && machine->get_dev_ip() == dev_id) { existing = machine; - dev_id = existing->get_dev_id(); // Use existing dev_id break; } } @@ -3626,8 +3570,9 @@ void GUI_App::select_machine(const std::string& agent_id) if (!existing) { BBLocalMachine machine; machine.dev_id = dev_id; - machine.dev_ip = full_addr; - machine.dev_name = agent_id + " (" + full_addr + ")"; + // We use dev_id as dev_ip to store the address (host:port) + machine.dev_ip = dev_id; + machine.dev_name = dev_id; machine.printer_type = preset.config.opt_string("printer_model"); existing = m_device_manager->insert_local_device( @@ -3639,6 +3584,7 @@ void GUI_App::select_machine(const std::string& agent_id) } BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": created new machine dev_id=" << dev_id; } + existing->local_use_ssl = boost::istarts_with(print_host, "https://"); // Use MonitorPanel::select_machine() to trigger full selection flow // This reuses existing logic for machine switching (UI updates, callbacks, etc.) diff --git a/src/slic3r/GUI/Jobs/PrintJob.cpp b/src/slic3r/GUI/Jobs/PrintJob.cpp index a09bca61fe..a5f1b4833c 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.cpp +++ b/src/slic3r/GUI/Jobs/PrintJob.cpp @@ -202,7 +202,7 @@ void PrintJob::process(Ctl &ctl) // local print access params.dev_ip = m_dev_ip; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; - params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt; + params.use_ssl_for_mqtt = m_local_use_ssl; params.username = "bblp"; params.password = m_access_code; diff --git a/src/slic3r/GUI/Jobs/PrintJob.hpp b/src/slic3r/GUI/Jobs/PrintJob.hpp index c93761a303..f186cda202 100644 --- a/src/slic3r/GUI/Jobs/PrintJob.hpp +++ b/src/slic3r/GUI/Jobs/PrintJob.hpp @@ -74,7 +74,7 @@ public: int m_print_from_sdc_plate_idx = 0; - bool m_local_use_ssl_for_mqtt { true }; + bool m_local_use_ssl { true }; bool m_local_use_ssl_for_ftp { true }; bool task_bed_leveling; bool task_flow_cali; diff --git a/src/slic3r/GUI/Jobs/SendJob.cpp b/src/slic3r/GUI/Jobs/SendJob.cpp index 578d0d804b..e7fe82eecd 100644 --- a/src/slic3r/GUI/Jobs/SendJob.cpp +++ b/src/slic3r/GUI/Jobs/SendJob.cpp @@ -183,7 +183,7 @@ void SendJob::process(Ctl &ctl) params.username = "bblp"; params.password = m_access_code; params.use_ssl_for_ftp = m_local_use_ssl_for_ftp; - params.use_ssl_for_mqtt = m_local_use_ssl_for_mqtt; + params.use_ssl_for_mqtt = m_local_use_ssl; wxString error_text; std::string msg_text; diff --git a/src/slic3r/GUI/Jobs/SendJob.hpp b/src/slic3r/GUI/Jobs/SendJob.hpp index e9330ad33d..1d4e025d6e 100644 --- a/src/slic3r/GUI/Jobs/SendJob.hpp +++ b/src/slic3r/GUI/Jobs/SendJob.hpp @@ -42,7 +42,7 @@ public: std::string connection_type; bool m_local_use_ssl_for_ftp{true}; - bool m_local_use_ssl_for_mqtt{true}; + bool m_local_use_ssl{true}; bool cloud_print_only { false }; bool has_sdcard { false }; bool task_use_ams { true }; diff --git a/src/slic3r/GUI/ReleaseNote.cpp b/src/slic3r/GUI/ReleaseNote.cpp index 9b156c5228..4af803d17d 100644 --- a/src/slic3r/GUI/ReleaseNote.cpp +++ b/src/slic3r/GUI/ReleaseNote.cpp @@ -1844,10 +1844,10 @@ void InputIpAddressDialog::on_send_retry() m_send_job->m_access_code = str_access_code.ToStdString(); #if !BBL_RELEASE_TO_PUBLIC - m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + m_send_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; #else - m_send_job->m_local_use_ssl_for_mqtt = m_obj->local_use_ssl_for_mqtt; + m_send_job->m_local_use_ssl = m_obj->local_use_ssl; m_send_job->m_local_use_ssl_for_ftp = m_obj->local_use_ssl_for_ftp; #endif diff --git a/src/slic3r/GUI/SelectMachine.cpp b/src/slic3r/GUI/SelectMachine.cpp index eeb472e98a..e0e9ec3b79 100644 --- a/src/slic3r/GUI/SelectMachine.cpp +++ b/src/slic3r/GUI/SelectMachine.cpp @@ -2464,10 +2464,10 @@ void SelectMachineDialog::on_send_print() m_print_job->m_access_code = obj_->get_access_code(); #if !BBL_RELEASE_TO_PUBLIC m_print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; - m_print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + m_print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else m_print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; - m_print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; + m_print_job->m_local_use_ssl = obj_->local_use_ssl; #endif m_print_job->connection_type = obj_->connection_type(); m_print_job->cloud_print_only = obj_->is_support_cloud_print_only; diff --git a/src/slic3r/GUI/SendToPrinter.cpp b/src/slic3r/GUI/SendToPrinter.cpp index 670a8bcad4..54bdf3a458 100644 --- a/src/slic3r/GUI/SendToPrinter.cpp +++ b/src/slic3r/GUI/SendToPrinter.cpp @@ -958,10 +958,10 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event) #if !BBL_RELEASE_TO_PUBLIC m_send_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; - m_send_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + m_send_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else m_send_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; - m_send_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; + m_send_job->m_local_use_ssl = obj_->local_use_ssl; #endif m_send_job->connection_type = obj_->connection_type(); diff --git a/src/slic3r/Utils/CalibUtils.cpp b/src/slic3r/Utils/CalibUtils.cpp index 3650e48acb..bba613e2c4 100644 --- a/src/slic3r/Utils/CalibUtils.cpp +++ b/src/slic3r/Utils/CalibUtils.cpp @@ -1791,10 +1791,10 @@ void CalibUtils::send_to_print(const CalibInfo &calib_info, wxString &error_mess #if !BBL_RELEASE_TO_PUBLIC print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; - print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; - print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; + print_job->m_local_use_ssl = obj_->local_use_ssl; #endif print_job->connection_type = obj_->connection_type(); @@ -1896,10 +1896,10 @@ void CalibUtils::send_to_print(const std::vector &calib_infos, wxStri #if !BBL_RELEASE_TO_PUBLIC print_job->m_local_use_ssl_for_ftp = wxGetApp().app_config->get("enable_ssl_for_ftp") == "true" ? true : false; - print_job->m_local_use_ssl_for_mqtt = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; + print_job->m_local_use_ssl = wxGetApp().app_config->get("enable_ssl_for_mqtt") == "true" ? true : false; #else print_job->m_local_use_ssl_for_ftp = obj_->local_use_ssl_for_ftp; - print_job->m_local_use_ssl_for_mqtt = obj_->local_use_ssl_for_mqtt; + print_job->m_local_use_ssl = obj_->local_use_ssl; #endif print_job->connection_type = obj_->connection_type(); diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp index 36341235fc..3c24ffe4db 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.cpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.cpp @@ -3,6 +3,7 @@ #include "libslic3r/Preset.hpp" #include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI_App.hpp" +#include "slic3r/GUI/DeviceManager.hpp" #include "nlohmann/json.hpp" #include @@ -27,90 +28,6 @@ namespace websocket = beast::websocket; namespace net = boost::asio; using tcp = net::ip::tcp; -constexpr const char* k_no_api_key = "__NO_API_KEY__"; - -bool is_numeric(const std::string& value) -{ - return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); -} - -std::string normalize_base_url(std::string host, const std::string& port) -{ - boost::trim(host); - if (host.empty()) { - return ""; - } - - std::string value = host; - if (is_numeric(port) && value.find("://") == std::string::npos && value.find(':') == std::string::npos) { - value += ":" + port; - } - - if (!boost::istarts_with(value, "http://") && !boost::istarts_with(value, "https://")) { - value = "http://" + value; - } - - if (value.size() > 1 && value.back() == '/') { - value.pop_back(); - } - - return value; -} - -std::string extract_host(const std::string& base_url) -{ - std::string host = base_url; - auto pos = host.find("://"); - if (pos != std::string::npos) { - host = host.substr(pos + 3); - } - pos = host.find('/'); - if (pos != std::string::npos) { - host = host.substr(0, pos); - } - return host; -} - -std::string join_url(const std::string& base_url, const std::string& path) -{ - if (base_url.empty()) { - return ""; - } - if (path.empty()) { - return base_url; - } - if (base_url.back() == '/' && path.front() == '/') { - return base_url.substr(0, base_url.size() - 1) + path; - } - if (base_url.back() != '/' && path.front() != '/') { - return base_url + "/" + path; - } - return base_url + path; -} - -std::string normalize_api_key(const std::string& api_key) -{ - if (api_key.empty() || api_key == k_no_api_key) { - return ""; - } - return api_key; -} - -// Sanitize filename to prevent path traversal attacks -// Extracts only the basename, removing any path components -std::string sanitize_filename(const std::string& filename) -{ - if (filename.empty()) { - return "print.gcode"; - } - namespace fs = boost::filesystem; - fs::path p(filename); - std::string basename = p.filename().string(); - if (basename.empty() || basename == "." || basename == "..") { - return "print.gcode"; - } - return basename; -} struct WsEndpoint { @@ -197,7 +114,6 @@ void MoonrakerPrinterAgent::set_cloud_agent(std::shared_ptr { std::lock_guard lock(state_mutex); m_cloud_agent = cloud; - BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Cloud agent set"; } int MoonrakerPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag) @@ -216,37 +132,25 @@ int MoonrakerPrinterAgent::send_message_to_printer(std::string dev_id, std::stri int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) { - (void) username; - (void) use_ssl; - std::string base_url = normalize_base_url(dev_ip, ""); - std::string api_key = normalize_api_key(password); - - PrinthostConfig config; - if (get_printhost_config(config)) { - if (base_url.empty()) { - base_url = config.base_url; - } - if (api_key.empty()) { - api_key = normalize_api_key(config.api_key); - } - } - - if (base_url.empty()) { - BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing host"; - dispatch_local_connect(ConnectStatusFailed, dev_id, "host_missing"); + if (dev_id.empty() || dev_ip.empty()) { + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer missing dev_id or dev_ip"; return BAMBU_NETWORK_ERR_INVALID_HANDLE; } - if (dev_id.empty()) { - dev_id = extract_host(base_url); + init_device_info(dev_id, dev_ip, username, password, use_ssl); + + if (device_info.dev_id != dev_id) { + BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: connect_printer dev_id mismatch: expected " << device_info.dev_id << ", got " << dev_id; + dispatch_local_connect(ConnectStatusFailed, dev_id, "dev_id_mismatch"); + return BAMBU_NETWORK_ERR_INVALID_HANDLE; } // Check if connection already in progress { std::lock_guard lock(connect_mutex); if (connect_in_progress.load()) { - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Connection already in progress, waiting..."; + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Connection already in progress, waiting..."; // Don't reject - wait for previous connection to complete // This can happen if MonitorPanel triggers connect while previous connect is still running } else { @@ -278,15 +182,12 @@ int MoonrakerPrinterAgent::connect_printer(std::string dev_id, std::string dev_i ws_last_dispatch_ms.store(0); last_print_state.clear(); - store_host(dev_id, base_url, api_key); - // Launch connection in background thread - connect_thread = std::thread([this, dev_id, base_url, api_key]() { - perform_connection_async(dev_id, base_url, api_key); + connect_thread = std::thread([this, dev_id]() { + perform_connection_async(dev_id, device_info.base_url, device_info.api_key); }); - // Return immediately - UI is not blocked - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: connect_printer launched in background - dev_id=" << dev_id; + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: connect_printer launched in background - dev_id=" << dev_id; return BAMBU_NETWORK_SUCCESS; } @@ -346,30 +247,18 @@ int MoonrakerPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, return BAMBU_NETWORK_ERR_INVALID_HANDLE; } - PrinthostConfig config; - get_printhost_config(config); - const std::string api_key = normalize_api_key(config.api_key); - - MoonrakerDeviceInfo info; std::string error; - if (!fetch_device_info(base_url, api_key, info, error)) { + if (!fetch_device_info(base_url, device_info.api_key, device_info, error)) { BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: bind_detect failed: " << error; return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED; } - detect.dev_id = info.dev_id.empty() ? dev_ip : info.dev_id; - if (!info.model_id.empty()) { - detect.model_id = info.model_id; - } else if (!config.model_id.empty()) { - detect.model_id = config.model_id; - } else { - detect.model_id = config.model_name; - } + detect.dev_id = device_info.dev_id.empty() ? dev_ip : device_info.dev_id; + detect.model_id = device_info.model_id.empty() ? device_info.model_name : device_info.model_id; // Prefer fetched hostname, then preset model name, then generic fallback - std::string fallback_name = config.model_name.empty() ? "Moonraker Printer" : config.model_name; - detect.dev_name = info.dev_name.empty() ? fallback_name : info.dev_name; - detect.model_id = "moonraker"; - detect.version = info.version; + detect.dev_name = device_info.dev_name; + detect.model_id = device_info.model_id; + detect.version = device_info.version; detect.connect_type = "lan"; detect.bind_state = "free"; @@ -447,12 +336,6 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpda if (update_fn) update_fn(PrintingStageCreate, 0, "Preparing..."); - const std::string base_url = resolve_host(params.dev_id); - if (base_url.empty()) { - return BAMBU_NETWORK_ERR_INVALID_HANDLE; - } - const std::string api_key = resolve_api_key(params.dev_id, params.password); - std::string filename = params.filename; if (filename.empty()) { filename = params.task_name; @@ -465,7 +348,7 @@ int MoonrakerPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpda std::string safe_filename = sanitize_filename(filename); // Upload only, don't start print - if (!upload_gcode(params.filename, safe_filename, base_url, api_key, update_fn, cancel_fn)) { + if (!upload_gcode(params.filename, safe_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) { return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED; } @@ -481,13 +364,6 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF if (cancel_fn && cancel_fn()) { return BAMBU_NETWORK_ERR_CANCELED; } - - const std::string base_url = resolve_host(params.dev_id); - if (base_url.empty()) { - return BAMBU_NETWORK_ERR_INVALID_HANDLE; - } - const std::string api_key = resolve_api_key(params.dev_id, params.password); - // Determine the G-code file to upload // params.filename may be .3mf, params.dst_file contains actual G-code std::string gcode_path = params.filename; @@ -513,7 +389,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF // Upload file if (update_fn) update_fn(PrintingStageUpload, 0, "Uploading G-code..."); - if (!upload_gcode(gcode_path, upload_filename, base_url, api_key, update_fn, cancel_fn)) { + if (!upload_gcode(gcode_path, upload_filename, device_info.base_url, device_info.api_key, update_fn, cancel_fn)) { return BAMBU_NETWORK_ERR_PRINT_LP_UPLOAD_FTP_FAILED; } @@ -525,7 +401,7 @@ int MoonrakerPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusF // Start print via gcode script (simpler than JSON-RPC) if (update_fn) update_fn(PrintingStageSending, 0, "Starting print..."); std::string gcode = "SDCARD_PRINT_FILE FILENAME=" + upload_filename; - if (!send_gcode(params.dev_id, gcode)) { + if (!send_gcode(device_info.dev_id, gcode)) { return BAMBU_NETWORK_ERR_PRINT_LP_PUBLISH_MSG_FAILED; } @@ -548,11 +424,6 @@ int MoonrakerPrinterAgent::set_on_ssdp_msg_fn(OnMsgArrivedFn fn) std::lock_guard lock(state_mutex); on_ssdp_msg_fn = fn; } - // Call announce_printhost_device() outside the lock to avoid deadlock - // since announce_printhost_device() also acquires state_mutex - if (fn) { - announce_printhost_device(); - } return BAMBU_NETWORK_SUCCESS; } @@ -607,16 +478,11 @@ int MoonrakerPrinterAgent::set_queue_on_main_fn(QueueOnMainFn fn) void MoonrakerPrinterAgent::fetch_filament_info(std::string dev_id) { - // Moonraker doesn't have standard filament tracking like Qidi - // This is a no-op for standard Moonraker installations - // Note: QidiPrinterAgent overrides this method with actual implementation - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info (base class no-op) called for dev_id=" << dev_id - << " - if you see this for Qidi printer, virtual dispatch is broken!"; + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent::fetch_filament_info (base class no-op) called for dev_id=" << dev_id; } int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std::string& json_str) { - BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: handle_request received: " << json_str; auto json = nlohmann::json::parse(json_str, nullptr, false); if (json.is_discarded()) { BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Invalid JSON request"; @@ -648,7 +514,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: } const std::string cmd = command.get(); - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Received print command: " << cmd; + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Received print command: " << cmd; // Handle gcode_line command - this is how G-code commands are sent from OrcaSlicer if (cmd == "gcode_line") { @@ -681,7 +547,7 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: return BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED; } - // ===== NEW: Print control commands ===== + // Print control commands if (cmd == "pause") { return pause_print(dev_id); } @@ -725,36 +591,27 @@ int MoonrakerPrinterAgent::handle_request(const std::string& dev_id, const std:: return BAMBU_NETWORK_SUCCESS; } -bool MoonrakerPrinterAgent::get_printhost_config(PrinthostConfig& config) const +bool MoonrakerPrinterAgent::init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) { + device_info = MoonrakerDeviceInfo{}; auto* preset_bundle = GUI::wxGetApp().preset_bundle; if (!preset_bundle) { return false; } - auto& preset = preset_bundle->printers.get_edited_preset(); + auto& preset = preset_bundle->printers.get_edited_preset(); const auto& printer_cfg = preset.config; - const DynamicPrintConfig* host_cfg = &printer_cfg; - config.host = host_cfg->opt_string("print_host"); - if (config.host.empty()) { - if (auto* physical_cfg = preset_bundle->physical_printers.get_selected_printer_config()) { - if (!physical_cfg->opt_string("print_host").empty()) { - host_cfg = physical_cfg; - config.host = host_cfg->opt_string("print_host"); - } - } - } - if (config.host.empty()) { - return false; - } + device_info.dev_ip = dev_ip; - config.port = host_cfg->opt_string("printhost_port"); - config.api_key = host_cfg->opt_string("printhost_apikey"); - config.model_name = printer_cfg.opt_string("printer_model"); - config.base_url = normalize_base_url(config.host, config.port); - config.model_id = preset.get_printer_type(preset_bundle); + device_info.api_key = password; + device_info.model_name = printer_cfg.opt_string("printer_model"); + device_info.model_id = preset.get_printer_type(preset_bundle); + device_info.base_url = use_ssl ? "https://" + dev_ip : "http://" + dev_ip; + device_info.dev_id = dev_id; + device_info.version = ""; + device_info.dev_name = device_info.dev_id; - return !config.base_url.empty(); + return true; } bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, @@ -771,8 +628,8 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response_body = body; @@ -809,12 +666,7 @@ bool MoonrakerPrinterAgent::fetch_device_info(const std::string& base_url, } nlohmann::json result = json.contains("result") ? json["result"] : json; - info.dev_name = result.value("machine_name", result.value("hostname", "")); - info.dev_id = result.value("machine_uuid", ""); - if (info.dev_id.empty()) { - info.dev_id = result.value("serial_number", ""); - } - info.model_id = result.value("model", ""); + info.dev_name = result.value("machine_name", result.value("hostname", "")); info.version = result.value("software_version", result.value("firmware_version", "")); return true; @@ -833,8 +685,8 @@ bool MoonrakerPrinterAgent::fetch_server_info(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response_body = body; @@ -885,8 +737,8 @@ bool MoonrakerPrinterAgent::fetch_server_info_json(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response_body = body; @@ -932,8 +784,8 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status_code) { if (status_code == 200) { response_body = body; @@ -972,31 +824,24 @@ bool MoonrakerPrinterAgent::query_printer_status(const std::string& base_url, bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::string& gcode) const { - const std::string base_url = resolve_host(dev_id); - if (base_url.empty()) { - BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: send_gcode - empty base_url for dev_id=" << dev_id; - return false; - } - const std::string api_key = resolve_api_key(dev_id, ""); - nlohmann::json payload; payload["script"] = gcode; std::string payload_str = payload.dump(); - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: send_gcode to " << base_url << " with payload: " << payload_str; + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: send_gcode to " << device_info.base_url << " with payload: " << payload_str; std::string response_body; bool success = false; std::string http_error; - auto http = Http::post(join_url(base_url, "/printer/gcode/script")); - if (!api_key.empty()) { - http.header("X-Api-Key", api_key); + auto http = Http::post(join_url(device_info.base_url, "/printer/gcode/script")); + if (!device_info.api_key.empty()) { + http.header("X-Api-Key", device_info.api_key); } http.header("Content-Type", "application/json") .set_post_body(payload_str) - .timeout_connect(10) - .timeout_max(30) + .timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status_code) { BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: send_gcode response status=" << status_code << " body=" << body; if (status_code == 200) { @@ -1020,7 +865,7 @@ bool MoonrakerPrinterAgent::send_gcode(const std::string& dev_id, const std::str return false; } - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: sent gcode successfully: " << gcode; + BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: sent gcode successfully: " << gcode; return true; } @@ -1037,8 +882,8 @@ bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url, if (!api_key.empty()) { http.header("X-Api-Key", api_key); } - http.timeout_connect(10) - .timeout_max(30) + http.timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response_body = body; @@ -1084,15 +929,9 @@ bool MoonrakerPrinterAgent::fetch_object_list(const std::string& base_url, int MoonrakerPrinterAgent::send_version_info(const std::string& dev_id) { - const std::string base_url = resolve_host(dev_id); - if (base_url.empty()) { - return BAMBU_NETWORK_ERR_INVALID_HANDLE; - } - const std::string api_key = resolve_api_key(dev_id, ""); - std::string version; std::string error; - if (!fetch_server_info(base_url, api_key, version, error)) { + if (!fetch_server_info(device_info.base_url, device_info.api_key, version, error)) { BOOST_LOG_TRIVIAL(warning) << "MoonrakerPrinterAgent: Failed to fetch server info: " << error; } if (version.empty()) { @@ -1118,7 +957,7 @@ int MoonrakerPrinterAgent::send_access_code(const std::string& dev_id) { nlohmann::json payload; payload["system"]["command"] = "get_access_code"; - payload["system"]["access_code"] = resolve_api_key(dev_id, ""); + payload["system"]["access_code"] = device_info.api_key; dispatch_message(dev_id, payload.dump()); return BAMBU_NETWORK_SUCCESS; } @@ -1127,20 +966,6 @@ void MoonrakerPrinterAgent::announce_printhost_device() { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device() called"; - PrinthostConfig config; - if (!get_printhost_config(config)) { - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - no printhost config"; - return; - } - - const std::string base_url = config.base_url; - if (base_url.empty()) { - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - empty base_url"; - return; - } - - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - base_url=" << base_url; - OnMsgArrivedFn ssdp_fn; { std::lock_guard lock(state_mutex); @@ -1149,43 +974,38 @@ void MoonrakerPrinterAgent::announce_printhost_device() BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - no ssdp callback"; return; } - if (ssdp_announced_host == base_url && !ssdp_announced_id.empty()) { + if (ssdp_announced_host == device_info.base_url && !ssdp_announced_id.empty()) { BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: announce_printhost_device - already announced"; return; } } - const std::string dev_id = extract_host(base_url); - const std::string api_key = normalize_api_key(config.api_key); - // Try to fetch actual device name from Moonraker // Priority: 1) Moonraker hostname, 2) Preset model name, 3) Generic fallback std::string dev_name; MoonrakerDeviceInfo info; std::string fetch_error; - if (fetch_device_info(base_url, api_key, info, fetch_error) && !info.dev_name.empty()) { + if (fetch_device_info(device_info.base_url, device_info.api_key, info, fetch_error) && !info.dev_name.empty()) { dev_name = info.dev_name; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Got device name from printer: " << dev_name; } else { - dev_name = config.model_name.empty() ? "Moonraker Printer" : config.model_name; + dev_name = device_info.model_name.empty() ? "Moonraker Printer" : device_info.model_name; BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Using fallback device name: " << dev_name << " (fetch_error=" << fetch_error << ")"; } - const std::string model_id = config.model_id; + const std::string model_id = device_info.model_id; if (auto* app_config = GUI::wxGetApp().app_config) { - const std::string access_code = api_key.empty() ? k_no_api_key : api_key; - app_config->set_str("access_code", dev_id, access_code); - app_config->set_str("user_access_code", dev_id, access_code); + const std::string access_code = device_info.api_key; + app_config->set_str("access_code", device_info.dev_id, access_code); + app_config->set_str("user_access_code", device_info.dev_id, access_code); } - store_host(dev_id, base_url, api_key); - nlohmann::json payload; payload["dev_name"] = dev_name; - payload["dev_id"] = dev_id; - payload["dev_ip"] = extract_host(base_url); + payload["dev_id"] = device_info.dev_id; + payload["dev_ip"] = device_info.dev_ip; payload["dev_type"] = model_id.empty() ? dev_name : model_id; payload["dev_signal"] = "0"; payload["connect_type"] = "lan"; @@ -1197,14 +1017,14 @@ void MoonrakerPrinterAgent::announce_printhost_device() { std::lock_guard lock(state_mutex); - ssdp_announced_host = base_url; - ssdp_announced_id = dev_id; + ssdp_announced_host = device_info.base_url; + ssdp_announced_id = device_info.dev_id; // Set this as the selected machine if nothing is currently selected // This ensures auto-connect works when MonitorPanel opens if (selected_machine.empty()) { - selected_machine = dev_id; - BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Auto-selected machine: " << dev_id; + selected_machine = device_info.dev_id; + BOOST_LOG_TRIVIAL(info) << "MoonrakerPrinterAgent: Auto-selected machine: " << device_info.dev_id; } } } @@ -1560,7 +1380,6 @@ void MoonrakerPrinterAgent::handle_ws_message(const std::string& dev_id, const s message = build_print_payload_locked(); } - BOOST_LOG_TRIVIAL(trace) << "MoonrakerPrinterAgent: Dispatching payload (critical=" << is_critical << "): " << message.dump(); dispatch_message(dev_id, message.dump()); ws_last_dispatch_ms.store(now_ms); ws_last_emit_ms.store(now_ms); // Also update heartbeat timer @@ -1609,7 +1428,6 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const } payload["print"]["gcode_state"] = state; - // ===== NEW: Print Stage ===== // Map Moonraker state to Bambu stage numbers int mc_print_stage = 0; if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("state")) { @@ -1621,14 +1439,12 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const } payload["print"]["mc_print_stage"] = mc_print_stage; - // ===== NEW: Error Codes ===== // Leave mc_print_error_code and print_error at 0 // UI expects numeric HMS codes - setting to 1 shows generic error dialog // Only set if real mapping from Moonraker error strings to HMS codes is defined payload["print"]["mc_print_error_code"] = 0; payload["print"]["print_error"] = 0; - // ===== NEW: Home Flag ===== // Map homed axes to bit field: X=bit0, Y=bit1, Z=bit2 // WARNING: This only sets bits 0-2, clearing support flags (bit 3+) // Bit 3 = 220V voltage, bit 4 = auto recovery, etc. @@ -1642,12 +1458,10 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const } payload["print"]["home_flag"] = home_flag; - // ===== NEW: Temperature Ranges ===== - // Moonraker doesn't provide this via API - use hardcoded defaults + // Moonraker doesn't provide temperature ranges via API - use hardcoded defaults payload["print"]["nozzle_temp_range"] = {100, 370}; // Typical Klipper range payload["print"]["bed_temp_range"] = {0, 120}; // Typical bed range - // ===== NEW: Feature Flags ===== payload["print"]["support_send_to_sd"] = true; // Detect bed_leveling support from available objects (bed_mesh or probe) // Default to 0 (not supported) if neither object exists @@ -1713,7 +1527,6 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const payload["print"]["subtask_name"] = status_cache["print_stats"]["filename"].get(); } - // ===== NEW: G-code File Path ===== if (status_cache.contains("print_stats") && status_cache["print_stats"].contains("filename")) { payload["print"]["gcode_file"] = status_cache["print_stats"]["filename"]; } @@ -1749,69 +1562,11 @@ nlohmann::json MoonrakerPrinterAgent::build_print_payload_locked() const std::chrono::system_clock::now().time_since_epoch()).count()); payload["t_utc"] = now_ms; - BOOST_LOG_TRIVIAL(trace) << "MoonrakerPrinterAgent: Built payload with gcode_state=" << state; - return payload; } -std::string MoonrakerPrinterAgent::resolve_host(const std::string& dev_id) const -{ - { - std::lock_guard lock(state_mutex); - auto it = host_by_device.find(dev_id); - if (it != host_by_device.end()) { - return it->second; - } - } - - PrinthostConfig config; - if (get_printhost_config(config)) { - return config.base_url; - } - - return ""; -} - -std::string MoonrakerPrinterAgent::resolve_api_key(const std::string& dev_id, const std::string& fallback) const -{ - std::string api_key = normalize_api_key(fallback); - if (!api_key.empty()) { - return api_key; - } - - { - std::lock_guard lock(state_mutex); - auto it = api_key_by_device.find(dev_id); - if (it != api_key_by_device.end() && !it->second.empty()) { - return it->second; - } - } - - PrinthostConfig config; - if (get_printhost_config(config)) { - return normalize_api_key(config.api_key); - } - - return ""; -} - -void MoonrakerPrinterAgent::store_host(const std::string& dev_id, const std::string& host, const std::string& api_key) -{ - if (host.empty()) { - return; - } - std::lock_guard lock(state_mutex); - host_by_device[dev_id] = host; - if (!api_key.empty()) { - api_key_by_device[dev_id] = api_key; - } -} - void MoonrakerPrinterAgent::dispatch_message(const std::string& dev_id, const std::string& payload) { - BOOST_LOG_TRIVIAL(trace) << "MoonrakerPrinterAgent: dispatch_message dev_id=" << dev_id - << " payload_size=" << payload.size(); - OnMessageFn local_fn; OnMessageFn cloud_fn; QueueOnMainFn queue_fn; @@ -1882,7 +1637,7 @@ bool MoonrakerPrinterAgent::upload_gcode( http.form_add("root", "gcodes") // Upload to gcodes directory .form_add("print", "false") // Don't auto-start print .form_add_file("file", source_path.string(), safe_filename) - .timeout_connect(10) + .timeout_connect(5) .timeout_max(300) // 5 minutes for large files .on_complete([&](std::string body, unsigned status) { BOOST_LOG_TRIVIAL(debug) << "MoonrakerPrinterAgent: Upload complete: HTTP " << status << " body: " << body; @@ -1917,9 +1672,6 @@ bool MoonrakerPrinterAgent::upload_gcode( int MoonrakerPrinterAgent::pause_print(const std::string& dev_id) { - const std::string base_url = resolve_host(dev_id); - const std::string api_key = resolve_api_key(dev_id, ""); - nlohmann::json request; request["jsonrpc"] = "2.0"; request["method"] = "printer.print.pause"; @@ -1963,8 +1715,8 @@ bool MoonrakerPrinterAgent::send_jsonrpc_command( } http.header("Content-Type", "application/json") .set_post_body(request_str) - .timeout_connect(10) - .timeout_max(30) + .timeout_connect(5) + .timeout_max(10) .on_complete([&](std::string body, unsigned status) { if (status == 200) { response = body; @@ -2007,29 +1759,7 @@ void MoonrakerPrinterAgent::perform_connection_async( ? server_info["result"] : server_info; std::string klippy_state = result_json.value("klippy_state", ""); - // Poll for Klippy ready state (with stop check) - if (klippy_state == "startup") { - for (int i = 0; i < 30; i++) { // 30 second max - { - std::unique_lock lock(connect_mutex); - if (connect_stop_requested.load()) { - result = BAMBU_NETWORK_ERR_CANCELED; - break; - } - } - - std::this_thread::sleep_for(std::chrono::seconds(1)); - - if (fetch_server_info_json(base_url, api_key, server_info, error_msg)) { - result_json = server_info.contains("result") - ? server_info["result"] : server_info; - klippy_state = result_json.value("klippy_state", ""); - if (klippy_state == "ready") break; - } - } - } - - // Check final state + // Check state if (klippy_state != "ready" && result == BAMBU_NETWORK_ERR_CONNECTION_TO_PRINTER_FAILED) { std::string state_message = result_json.value("state_message", "Unknown error"); BOOST_LOG_TRIVIAL(error) << "MoonrakerPrinterAgent: Klippy not ready: " << klippy_state @@ -2081,4 +1811,66 @@ void MoonrakerPrinterAgent::finish_connection() connect_in_progress.store(false); } +bool MoonrakerPrinterAgent::is_numeric(const std::string& value) +{ + return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); +} + +std::string MoonrakerPrinterAgent::normalize_base_url(std::string host, const std::string& port) +{ + boost::trim(host); + if (host.empty()) { + return ""; + } + + std::string value = host; + if (is_numeric(port) && value.find("://") == std::string::npos && value.find(':') == std::string::npos) { + value += ":" + port; + } + + if (!boost::istarts_with(value, "http://") && !boost::istarts_with(value, "https://")) { + value = "http://" + value; + } + + if (value.size() > 1 && value.back() == '/') { + value.pop_back(); + } + + return value; +} + +std::string MoonrakerPrinterAgent::join_url(const std::string& base_url, const std::string& path) const +{ + if (base_url.empty()) { + return ""; + } + if (path.empty()) { + return base_url; + } + if (base_url.back() == '/' && path.front() == '/') { + return base_url.substr(0, base_url.size() - 1) + path; + } + if (base_url.back() != '/' && path.front() != '/') { + return base_url + "/" + path; + } + return base_url + path; +} + + +// Sanitize filename to prevent path traversal attacks +// Extracts only the basename, removing any path components +std::string MoonrakerPrinterAgent::sanitize_filename(const std::string& filename) +{ + if (filename.empty()) { + return "print.gcode"; + } + namespace fs = boost::filesystem; + fs::path p(filename); + std::string basename = p.filename().string(); + if (basename.empty() || basename == "." || basename == "..") { + return "print.gcode"; + } + return basename; +} + } // namespace Slic3r diff --git a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp index cfdd663810..94d32a4016 100644 --- a/src/slic3r/Utils/MoonrakerPrinterAgent.hpp +++ b/src/slic3r/Utils/MoonrakerPrinterAgent.hpp @@ -74,39 +74,31 @@ public: virtual void fetch_filament_info(std::string dev_id) override; protected: - // Types exposed for derived classes - struct PrinthostConfig + struct MoonrakerDeviceInfo { - std::string host; - std::string port; + std::string dev_id; + std::string dev_ip; std::string api_key; std::string base_url; std::string model_id; std::string model_name; - }; - - struct MoonrakerDeviceInfo - { - std::string dev_id; std::string dev_name; - std::string model_id; std::string version; - }; + bool use_ssl = false; + } device_info; // Methods that derived classes may need to override or access - virtual bool get_printhost_config(PrinthostConfig& config) const; + virtual bool init_device_info(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl); virtual bool fetch_device_info(const std::string& base_url, const std::string& api_key, MoonrakerDeviceInfo& info, std::string& error) const; - virtual std::string get_dev_type() const { return "moonraker"; } - - // Host resolution methods - std::string resolve_host(const std::string& dev_id) const; - std::string resolve_api_key(const std::string& dev_id, const std::string& fallback) const; - void store_host(const std::string& dev_id, const std::string& host, const std::string& api_key); // State access for derived classes mutable std::recursive_mutex state_mutex; - std::map host_by_device; - std::map api_key_by_device; + + // Helpers + bool is_numeric(const std::string& value); + std::string normalize_base_url(std::string host, const std::string& port); + std::string sanitize_filename(const std::string& filename); + std::string join_url(const std::string& base_url, const std::string& path) const; private: int handle_request(const std::string& dev_id, const std::string& json_str); diff --git a/src/slic3r/Utils/OrcaPrinterAgent.cpp b/src/slic3r/Utils/OrcaPrinterAgent.cpp index 09acf3465d..712024a286 100644 --- a/src/slic3r/Utils/OrcaPrinterAgent.cpp +++ b/src/slic3r/Utils/OrcaPrinterAgent.cpp @@ -1,15 +1,12 @@ #include "OrcaPrinterAgent.hpp" #include "NetworkAgentFactory.hpp" -#include - namespace Slic3r { const std::string OrcaPrinterAgent_VERSION = "0.0.1"; OrcaPrinterAgent::OrcaPrinterAgent(std::string log_dir) : log_dir(std::move(log_dir)) { - BOOST_LOG_TRIVIAL(info) << "OrcaPrinterAgent: Constructor - log_dir=" << this->log_dir; } OrcaPrinterAgent::~OrcaPrinterAgent() = default; @@ -18,7 +15,6 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr cloud { std::lock_guard lock(state_mutex); m_cloud_agent = cloud; - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: Cloud agent set"; } // ============================================================================ @@ -27,25 +23,21 @@ void OrcaPrinterAgent::set_cloud_agent(std::shared_ptr cloud int OrcaPrinterAgent::send_message(std::string dev_id, std::string json_str, int qos, int flag) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: send_message (stub) - dev_id=" << dev_id; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::connect_printer(std::string dev_id, std::string dev_ip, std::string username, std::string password, bool use_ssl) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: connect_printer (stub) - dev_id=" << dev_id << ", dev_ip=" << dev_ip; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::disconnect_printer() { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: disconnect_printer (stub)"; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string json_str, int qos, int flag) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: send_message_to_printer (stub) - dev_id=" << dev_id; return BAMBU_NETWORK_SUCCESS; } @@ -55,13 +47,11 @@ int OrcaPrinterAgent::send_message_to_printer(std::string dev_id, std::string js int OrcaPrinterAgent::check_cert() { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: check_cert (stub)"; return BAMBU_NETWORK_SUCCESS; } void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: install_device_cert (stub) - dev_id=" << dev_id; } // ============================================================================ @@ -70,7 +60,6 @@ void OrcaPrinterAgent::install_device_cert(std::string dev_id, bool lan_only) bool OrcaPrinterAgent::start_discovery(bool start, bool sending) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_discovery (stub) - start=" << start << ", sending=" << sending; return true; } @@ -80,32 +69,27 @@ bool OrcaPrinterAgent::start_discovery(bool start, bool sending) int OrcaPrinterAgent::ping_bind(std::string ping_code) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: ping_bind (stub) - ping_code=" << ping_code; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::bind_detect(std::string dev_ip, std::string sec_link, detectResult& detect) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: bind_detect (stub) - dev_ip=" << dev_ip; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::bind( std::string dev_ip, std::string dev_id, std::string sec_link, std::string timezone, bool improved, OnUpdateStatusFn update_fn) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: bind (stub) - dev_id=" << dev_id; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::unbind(std::string dev_id) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: unbind (stub) - dev_id=" << dev_id; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::request_bind_ticket(std::string* ticket) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: request_bind_ticket (stub)"; if (ticket) *ticket = ""; return BAMBU_NETWORK_SUCCESS; @@ -152,7 +136,6 @@ AgentInfo OrcaPrinterAgent::get_agent_info_static() int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_print (stub) - task_name=" << params.task_name; return BAMBU_NETWORK_SUCCESS; } @@ -161,25 +144,21 @@ int OrcaPrinterAgent::start_local_print_with_record(PrintParams params, WasCancelledFn cancel_fn, OnWaitFn wait_fn) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_local_print_with_record (stub)"; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_send_gcode_to_sdcard (stub)"; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_local_print (stub)"; return BAMBU_NETWORK_SUCCESS; } int OrcaPrinterAgent::start_sdcard_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn) { - BOOST_LOG_TRIVIAL(debug) << "OrcaPrinterAgent: start_sdcard_print (stub)"; return BAMBU_NETWORK_SUCCESS; } diff --git a/src/slic3r/Utils/QidiPrinterAgent.cpp b/src/slic3r/Utils/QidiPrinterAgent.cpp index f3d740f209..5f16ab9339 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.cpp +++ b/src/slic3r/Utils/QidiPrinterAgent.cpp @@ -1,7 +1,5 @@ #include "QidiPrinterAgent.hpp" #include "Http.hpp" -#include "libslic3r/Preset.hpp" -#include "libslic3r/PresetBundle.hpp" #include "slic3r/GUI/GUI_App.hpp" #include "slic3r/GUI/DeviceCore/DevFilaSystem.h" #include "slic3r/GUI/DeviceCore/DevManager.h" @@ -13,103 +11,12 @@ #include #include -namespace { - -bool is_numeric(const std::string& value) -{ - return !value.empty() && std::all_of(value.begin(), value.end(), [](unsigned char c) { return std::isdigit(c) != 0; }); -} - -std::string join_url(const std::string& base_url, const std::string& path) -{ - if (base_url.empty()) { - return ""; - } - if (path.empty()) { - return base_url; - } - if (base_url.back() == '/' && path.front() == '/') { - return base_url.substr(0, base_url.size() - 1) + path; - } - if (base_url.back() != '/' && path.front() != '/') { - return base_url + "/" + path; - } - return base_url + path; -} - -std::string normalize_model_key(std::string value) -{ - boost::algorithm::to_lower(value); - std::string normalized; - normalized.reserve(value.size()); - for (unsigned char c : value) { - if (std::isalnum(c)) { - normalized.push_back(static_cast(c)); - } - } - return normalized; -} - -std::string infer_series_id(const std::string& model_id, const std::string& dev_name) -{ - std::string source = model_id.empty() ? dev_name : model_id; - boost::trim(source); - if (source.empty()) { - return ""; - } - if (is_numeric(source)) { - return source; - } - - const std::string key = normalize_model_key(source); - if (key.find("q2") != std::string::npos) { - return "1"; - } - if (key.find("xmax") != std::string::npos && key.find("4") != std::string::npos) { - return "3"; - } - if ((key.find("xplus") != std::string::npos || key.find("plus") != std::string::npos) && key.find("4") != std::string::npos) { - return "0"; - } - return ""; -} - -std::string normalize_filament_type(const std::string& filament_type) -{ - std::string trimmed = filament_type; - boost::trim(trimmed); - std::string upper = trimmed; - std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); - - if (upper.find("PLA") != std::string::npos) - return "PLA"; - if (upper.find("ABS") != std::string::npos) - return "ABS"; - if (upper.find("PETG") != std::string::npos) - return "PETG"; - if (upper.find("TPU") != std::string::npos) - return "TPU"; - if (upper.find("ASA") != std::string::npos) - return "ASA"; - if (upper.find("PA") != std::string::npos || upper.find("NYLON") != std::string::npos) - return "PA"; - if (upper.find("PC") != std::string::npos) - return "PC"; - if (upper.find("PVA") != std::string::npos) - return "PVA"; - - return trimmed; -} - -} // namespace - namespace Slic3r { const std::string QidiPrinterAgent_VERSION = "0.0.1"; QidiPrinterAgent::QidiPrinterAgent(std::string log_dir) : MoonrakerPrinterAgent(std::move(log_dir)) { - BOOST_LOG_TRIVIAL(info) << "QidiPrinterAgent: Constructor"; } AgentInfo QidiPrinterAgent::get_agent_info_static() @@ -131,23 +38,16 @@ void QidiPrinterAgent::fetch_filament_info(std::string dev_id) return; } - const std::string base_url = resolve_host(dev_id); - if (base_url.empty()) { - BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: Missing host for dev_id=" << dev_id; - return; - } - const std::string api_key = resolve_api_key(dev_id, ""); - std::vector slots; int box_count = 0; std::string error; - if (!fetch_slot_info(base_url, api_key, slots, box_count, error)) { + if (!fetch_slot_info(device_info.base_url, device_info.api_key, slots, box_count, error)) { BOOST_LOG_TRIVIAL(error) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch slot info: " << error; return; } QidiFilamentDict dict; - if (!fetch_filament_dict(base_url, api_key, dict, error)) { + if (!fetch_filament_dict(device_info.base_url, device_info.api_key, dict, error)) { BOOST_LOG_TRIVIAL(warning) << "QidiPrinterAgent::fetch_filament_info: Failed to fetch filament dict: " << error; } @@ -155,7 +55,7 @@ void QidiPrinterAgent::fetch_filament_info(std::string dev_id) { MoonrakerDeviceInfo info; std::string device_error; - if (fetch_device_info(base_url, api_key, info, device_error)) { + if (fetch_device_info(device_info.base_url, device_info.api_key, info, device_error)) { series_id = infer_series_id(info.model_id, info.dev_name); } } @@ -496,4 +396,68 @@ std::string QidiPrinterAgent::map_filament_type_to_setting_id(const std::string& return ""; } +std::string QidiPrinterAgent::normalize_model_key(std::string value) +{ + boost::algorithm::to_lower(value); + std::string normalized; + normalized.reserve(value.size()); + for (unsigned char c : value) { + if (std::isalnum(c)) { + normalized.push_back(static_cast(c)); + } + } + return normalized; +} + +std::string QidiPrinterAgent::infer_series_id(const std::string& model_id, const std::string& dev_name) +{ + std::string source = model_id.empty() ? dev_name : model_id; + boost::trim(source); + if (source.empty()) { + return ""; + } + if (is_numeric(source)) { + return source; + } + + const std::string key = normalize_model_key(source); + if (key.find("q2") != std::string::npos) { + return "1"; + } + if (key.find("xmax") != std::string::npos && key.find("4") != std::string::npos) { + return "3"; + } + if ((key.find("xplus") != std::string::npos || key.find("plus") != std::string::npos) && key.find("4") != std::string::npos) { + return "0"; + } + return ""; +} + +std::string QidiPrinterAgent::normalize_filament_type(const std::string& filament_type) +{ + std::string trimmed = filament_type; + boost::trim(trimmed); + std::string upper = trimmed; + std::transform(upper.begin(), upper.end(), upper.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); + + if (upper.find("PLA") != std::string::npos) + return "PLA"; + if (upper.find("ABS") != std::string::npos) + return "ABS"; + if (upper.find("PETG") != std::string::npos) + return "PETG"; + if (upper.find("TPU") != std::string::npos) + return "TPU"; + if (upper.find("ASA") != std::string::npos) + return "ASA"; + if (upper.find("PA") != std::string::npos || upper.find("NYLON") != std::string::npos) + return "PA"; + if (upper.find("PC") != std::string::npos) + return "PC"; + if (upper.find("PVA") != std::string::npos) + return "PVA"; + + return trimmed; +} + } // namespace Slic3r diff --git a/src/slic3r/Utils/QidiPrinterAgent.hpp b/src/slic3r/Utils/QidiPrinterAgent.hpp index a2a51c423b..8657443288 100644 --- a/src/slic3r/Utils/QidiPrinterAgent.hpp +++ b/src/slic3r/Utils/QidiPrinterAgent.hpp @@ -39,8 +39,15 @@ private: }; // Qidi-specific methods - bool fetch_slot_info(const std::string& base_url, const std::string& api_key, std::vector& slots, int& box_count, std::string& error) const; + bool fetch_slot_info(const std::string& base_url, + const std::string& api_key, + std::vector& slots, + int& box_count, + std::string& error) const; bool fetch_filament_dict(const std::string& base_url, const std::string& api_key, QidiFilamentDict& dict, std::string& error) const; + std::string normalize_filament_type(const std::string& filament_type); + std::string infer_series_id(const std::string& model_id, const std::string& dev_name); + std::string normalize_model_key(std::string value); // Static helpers static void parse_ini_section(const std::string& content, const std::string& section_name, std::map& result);