mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-10-08 08:11:14 +00:00
OrcaPrinterAgent: Thumbnails over http
This commit is contained in:
@@ -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<int>() : 0;
|
||||
if (width > best_width) {
|
||||
best_width = width;
|
||||
path = item[key].get<std::string>();
|
||||
}
|
||||
}
|
||||
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)
|
||||
|
||||
@@ -177,10 +177,6 @@ protected:
|
||||
// back to the name and size/modified default to 0. protected static for the Probe.
|
||||
static std::vector<PrinterFileEntry> 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,
|
||||
|
||||
@@ -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"({
|
||||
|
||||
Reference in New Issue
Block a user