diff --git a/src/slic3r/Utils/OrcaPrinterAgent.cpp b/src/slic3r/Utils/OrcaPrinterAgent.cpp index 65f143defc..d4ea1c20b4 100644 --- a/src/slic3r/Utils/OrcaPrinterAgent.cpp +++ b/src/slic3r/Utils/OrcaPrinterAgent.cpp @@ -1964,39 +1964,6 @@ int OrcaPrinterAgent::list_printer_files(const std::string& dev_id, PrinterFileL }); } -// Pure pick of the widest thumbnail path from Moonraker's /server/files/thumbnails -// reply. The `result` array is ordered smallest-first, so the largest `width` is -// chosen. The key was renamed across Moonraker versions; both spellings are accepted. -// Malformed replies and entries without a usable path yield an empty string. -std::string OrcaPrinterAgent::parse_thumbnail_path(const std::string& body) -{ - const nlohmann::json envelope = nlohmann::json::parse(body, nullptr, false); - if (envelope.is_discarded() || !envelope.is_object()) - return {}; - - const auto result_it = envelope.find("result"); - if (result_it == envelope.end() || !result_it->is_array()) - return {}; - - std::string path; - int best_width = -1; - for (const auto& item : *result_it) { - if (!item.is_object()) - continue; - - const char* key = item.contains("thumbnail_path") ? "thumbnail_path" : "relative_path"; - if (!item.contains(key) || !item[key].is_string()) - continue; - - const int width = (item.contains("width") && item["width"].is_number()) ? item["width"].get() : 0; - if (width > best_width) { - best_width = width; - path = item[key].get(); - } - } - return path; -} - // Pure normalization of the files.metadata MQTT reply: the `files` object's // estimated_time (seconds), filament_total (mm) and filament_weight_total (grams; // OrcaSonar currently reports only filament_total). A malformed reply or any @@ -2028,27 +1995,7 @@ PrinterFileMetadata OrcaPrinterAgent::parse_files_metadata_reply(const std::stri return meta; } -// Percent-encode each '/'-separated segment, leaving the separators themselves -// intact so the caller keeps the directory structure of a gcodes-relative path. -std::string OrcaPrinterAgent::encode_file_path(const std::string& path) -{ - std::string encoded; - size_t segment_start = 0; - while (segment_start <= path.size()) { - const size_t segment_end = path.find('/', segment_start); - if (!encoded.empty() || segment_start > 0) - encoded += '/'; - encoded += Http::url_encode(path.substr(segment_start, segment_end - segment_start)); - if (segment_end == std::string::npos) - break; - segment_start = segment_end + 1; - } - return encoded; -} - -// Fetch one file's embedded thumbnail: first resolve its path via the thumbnails -// listing, then download the image bytes. Mirrors list_printer_files for the -// connection snapshot and off-thread marshalling; the worker captures no `this`. +// Fetch one file's thumbnail over HTTP; the worker captures no `this`. int OrcaPrinterAgent::get_printer_file_thumbnail(const std::string& dev_id, const std::string& path, PrinterFileThumbnailFn callback) { std::string origin; @@ -2081,9 +2028,8 @@ int OrcaPrinterAgent::get_printer_file_thumbnail(const std::string& dev_id, cons std::string image; int result = BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED; - std::string thumb_body; - bool listed = false; - auto http = Http::get(origin + "/server/files/thumbnails?filename=" + Http::url_encode(path)); + std::string url = origin + "/files/thumbnail?root=gcodes&path=" + Http::url_encode(path); + auto http = Http::get(url); http.tls_verify(use_ssl); if (!ca_file.empty()) http.ca_file(ca_file); @@ -2091,47 +2037,19 @@ int OrcaPrinterAgent::get_printer_file_thumbnail(const std::string& dev_id, cons .timeout_max(15) .on_complete([&](std::string b, unsigned status) { if (status == 200) { - thumb_body = std::move(b); - listed = true; + image = std::move(b); } + result = BAMBU_NETWORK_SUCCESS; }) .on_error([&](std::string, std::string err, unsigned status) { - BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: thumbnail list request failed status=" << status << " err=" << err; + if (status == 404) { + result = BAMBU_NETWORK_SUCCESS; + return; + } + BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: thumbnail fetch failed status=" << status << " err=" << err; }) .perform_sync(); - if (listed) { - if (nlohmann::json::parse(thumb_body, nullptr, false).is_discarded()) { - result = BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED; - } else { - const std::string thumb_path = parse_thumbnail_path(thumb_body); - if (thumb_path.empty()) { - // No embedded thumbnail: a success the caller caches. - result = BAMBU_NETWORK_SUCCESS; - } else { - // The returned path is relative to the gcodes root, served at - // /server/files/gcodes; an explicit gcodes/ prefix is served at /server/files. - const std::string root = thumb_path.rfind("gcodes/", 0) == 0 ? "/server/files/" : "/server/files/gcodes/"; - auto image_http = Http::get(origin + root + encode_file_path(thumb_path)); - image_http.tls_verify(use_ssl); - if (!ca_file.empty()) - image_http.ca_file(ca_file); - image_http.timeout_connect(5) - .timeout_max(15) - .on_complete([&](std::string b, unsigned status) { - if (status == 200) { - image = std::move(b); - result = BAMBU_NETWORK_SUCCESS; - } - }) - .on_error([&](std::string, std::string err, unsigned status) { - BOOST_LOG_TRIVIAL(warning) << "OrcaPrinterAgent: thumbnail fetch failed status=" << status << " err=" << err; - }) - .perform_sync(); - } - } - } - if (!callback) return; if (queue) diff --git a/src/slic3r/Utils/OrcaPrinterAgent.hpp b/src/slic3r/Utils/OrcaPrinterAgent.hpp index 9ad66f5e92..635f023634 100644 --- a/src/slic3r/Utils/OrcaPrinterAgent.hpp +++ b/src/slic3r/Utils/OrcaPrinterAgent.hpp @@ -177,10 +177,6 @@ protected: // back to the name and size/modified default to 0. protected static for the Probe. static std::vector parse_files_list_reply(const std::string& payload); - // Pick the widest thumbnail path from OrcaSonar's /server/files/thumbnails - // reply (the array is smallest-first). Empty when none carry a path. - static std::string parse_thumbnail_path(const std::string& body); - // Pure JSON -> metadata normalization for the files.metadata MQTT reply. Missing // or malformed fields default to 0. protected static for the Probe. static PrinterFileMetadata parse_files_metadata_reply(const std::string& payload); @@ -197,9 +193,6 @@ protected: // when it matched, so the caller does not forward it to the machine-state sink. bool try_consume_files_reply(const std::string& dev_id, const std::string& payload); - // Percent-encode each '/'-separated segment for a Moonraker URL while keeping - // the separators intact. protected static for the test Probe. - static std::string encode_file_path(const std::string& path); // Test hook: the ws:// URL connect_printer built for the current LAN session ("" if none). std::string lan_connection_target() const; // Shared post-connect sequence: SUBSCRIBE, then pushing.start, pushall, diff --git a/tests/slic3rutils/test_orca_printer_agent.cpp b/tests/slic3rutils/test_orca_printer_agent.cpp index e444f89cba..0cee1a6323 100644 --- a/tests/slic3rutils/test_orca_printer_agent.cpp +++ b/tests/slic3rutils/test_orca_printer_agent.cpp @@ -32,8 +32,6 @@ struct Probe : OrcaPrinterAgent { using OrcaPrinterAgent::build_gcode_file_payload; using OrcaPrinterAgent::prepare_outgoing_request; using OrcaPrinterAgent::parse_files_list_reply; - using OrcaPrinterAgent::parse_thumbnail_path; - using OrcaPrinterAgent::encode_file_path; using OrcaPrinterAgent::parse_files_metadata_reply; using OrcaPrinterAgent::try_consume_files_reply; }; @@ -254,44 +252,6 @@ TEST_CASE("OrcaPrinterAgent::try_consume_files_reply ignores reports it did not CHECK_FALSE(agent.try_consume_files_reply("dev-1", R"({"files": {"command": "list", "sequence_id": "999"}})")); // not pending } -TEST_CASE("OrcaPrinterAgent::parse_thumbnail_path picks the largest width", "[OrcaPrinterAgent]") { - const std::string path = Probe::parse_thumbnail_path(R"({ - "result": [ - {"width": 32, "height": 32, "thumbnail_path": ".thumbs/foo.gcode-32x32.png"}, - {"width": 300, "height": 300, "thumbnail_path": ".thumbs/foo.gcode-300x300.png"}, - {"width": 100, "height": 100, "thumbnail_path": ".thumbs/foo.gcode-100x100.png"} - ] - })"); - CHECK(path == ".thumbs/foo.gcode-300x300.png"); -} - -TEST_CASE("OrcaPrinterAgent::parse_thumbnail_path accepts both key spellings", "[OrcaPrinterAgent]") { - CHECK(Probe::parse_thumbnail_path(R"({"result": [{"width": 32, "relative_path": ".thumbs/old.png"}]})") == ".thumbs/old.png"); - CHECK(Probe::parse_thumbnail_path(R"({"result": [{"width": 32, "thumbnail_path": ".thumbs/new.png"}]})") == ".thumbs/new.png"); -} - -TEST_CASE("OrcaPrinterAgent::parse_thumbnail_path handles an empty result", "[OrcaPrinterAgent]") { - CHECK(Probe::parse_thumbnail_path(R"({"result": []})").empty()); -} - -TEST_CASE("OrcaPrinterAgent::parse_thumbnail_path rejects malformed JSON", "[OrcaPrinterAgent]") { - CHECK(Probe::parse_thumbnail_path("not json").empty()); - CHECK(Probe::parse_thumbnail_path(R"({"result": "nope"})").empty()); -} - -TEST_CASE("OrcaPrinterAgent::parse_thumbnail_path skips an entry without a path", "[OrcaPrinterAgent]") { - CHECK(Probe::parse_thumbnail_path(R"({"result": [{"width": 300, "height": 300}]})").empty()); - CHECK(Probe::parse_thumbnail_path(R"({"result": [{"width": 300, "thumbnail_path": 7}]})").empty()); -} - -TEST_CASE("OrcaPrinterAgent::encode_file_path preserves separators and encodes segments", "[OrcaPrinterAgent]") { - CHECK(Probe::encode_file_path("foo.gcode") == "foo.gcode"); // flat path - CHECK(Probe::encode_file_path("sub/foo.gcode") == "sub/foo.gcode"); // '/' kept as separator - CHECK(Probe::encode_file_path("a/b/c.gcode") == "a/b/c.gcode"); - CHECK(Probe::encode_file_path("sub dir/my file #1.gcode") == "sub%20dir/my%20file%20%231.gcode"); - CHECK(Probe::encode_file_path("design+part.gcode") == "design%2Bpart.gcode"); -} - TEST_CASE("OrcaPrinterAgent::parse_files_metadata_reply parses the metadata fields", "[OrcaPrinterAgent]") { using Catch::Matchers::WithinAbs; const Slic3r::PrinterFileMetadata meta = Probe::parse_files_metadata_reply(R"({