mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-15 21:17:41 +00:00
feat: cloud download via HTTP
This commit is contained in:
@@ -1127,21 +1127,113 @@ int OrcaCloudServiceAgent::send_printer_command(const std::string& dev_id, const
|
||||
: BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
int OrcaCloudServiceAgent::send_print_job(const std::string& dev_id,
|
||||
const std::string& filename,
|
||||
const std::string& gcode,
|
||||
bool start)
|
||||
int OrcaCloudServiceAgent::upload_gcode_via_cloud(const std::string& dev_id,
|
||||
const std::string& local_gcode_path,
|
||||
std::string* job_id,
|
||||
OnUpdateStatusFn update_fn,
|
||||
WasCancelledFn cancel_fn)
|
||||
{
|
||||
if (dev_id.empty() || filename.empty() || !is_user_login())
|
||||
if (dev_id.empty() || local_gcode_path.empty() || !is_user_login())
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
if (cancel_fn && cancel_fn())
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
|
||||
// Step 1: POST print-jobs/uploads -> a short-lived presigned R2 PUT URL. No
|
||||
// metadata rides this request; filename/start are only relevant to the HTTP
|
||||
// .../start finalize route, which this MQTT-driven flow does not call.
|
||||
const std::string uploads_path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs/uploads";
|
||||
std::string response;
|
||||
unsigned int http_code = 0;
|
||||
int result = http_post(uploads_path, "{}", &response, &http_code);
|
||||
if (result != BAMBU_NETWORK_SUCCESS || http_code < 200 || http_code >= 300) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: print-jobs/uploads failed http_code=" << http_code;
|
||||
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
std::string upload_job_id;
|
||||
std::string upload_url;
|
||||
try {
|
||||
const nlohmann::json j = nlohmann::json::parse(response);
|
||||
upload_job_id = j.value("job_id", "");
|
||||
upload_url = j.value("upload_url", "");
|
||||
} catch (const std::exception& e) {
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: failed to parse print-jobs/uploads response: " << e.what();
|
||||
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
if (upload_job_id.empty() || upload_url.empty()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: print-jobs/uploads response missing job_id/upload_url";
|
||||
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
|
||||
if (cancel_fn && cancel_fn())
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
|
||||
// Step 2: PUT the G-code straight to R2 with the one-time URL from step 1. This
|
||||
// is a scoped, PUT-only, short-TTL capability with no bearer token of its own,
|
||||
// so it bypasses http_put (which always prefixes api_base_url and attaches the
|
||||
// cloud session's Authorization header - neither belongs on an R2 PUT).
|
||||
bool canceled = false;
|
||||
unsigned put_status = 0;
|
||||
std::string put_error;
|
||||
Http::put(upload_url)
|
||||
.tls_verify(true)
|
||||
.header("Content-Type", "text/x.gcode")
|
||||
.set_put_body(boost::filesystem::path(local_gcode_path))
|
||||
.timeout_connect(5)
|
||||
.timeout_max(300) // large G-code over a slow link
|
||||
.on_progress([&](Http::Progress progress, bool& cancel) {
|
||||
if (cancel_fn && cancel_fn()) {
|
||||
cancel = true;
|
||||
canceled = true;
|
||||
return;
|
||||
}
|
||||
if (update_fn && progress.ultotal > 0) {
|
||||
const int percent = static_cast<int>((progress.ulnow * 100) / progress.ultotal);
|
||||
update_fn(PrintingStageUpload, percent, "Uploading...");
|
||||
}
|
||||
})
|
||||
.on_complete([&](std::string, unsigned status) { put_status = status; })
|
||||
.on_error([&](std::string, std::string err, unsigned status) {
|
||||
put_status = status;
|
||||
put_error = std::move(err);
|
||||
})
|
||||
.perform_sync();
|
||||
|
||||
if (canceled)
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
if (put_status < 200 || put_status >= 300) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: R2 upload failed status=" << put_status << " error=" << put_error;
|
||||
return BAMBU_NETWORK_ERR_PRINT_SG_UPLOAD_FTP_FAILED;
|
||||
}
|
||||
|
||||
if (job_id)
|
||||
*job_id = std::move(upload_job_id);
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
int OrcaCloudServiceAgent::start_cloud_print_job(const std::string& dev_id,
|
||||
const std::string& job_id,
|
||||
const std::string& filename,
|
||||
bool start)
|
||||
{
|
||||
if (dev_id.empty() || job_id.empty() || !is_user_login())
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
|
||||
const std::string path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs?filename=" +
|
||||
Http::url_encode(filename) + "&start=" + (start ? "true" : "false");
|
||||
nlohmann::json body;
|
||||
if (!filename.empty())
|
||||
body["filename"] = filename;
|
||||
body["start"] = start;
|
||||
|
||||
const std::string path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs/" +
|
||||
Http::url_encode(job_id) + "/start";
|
||||
std::string response;
|
||||
unsigned int http_code = 0;
|
||||
const int result = http_post(path, gcode, nullptr, &http_code, "text/plain");
|
||||
if (result != BAMBU_NETWORK_SUCCESS)
|
||||
return result;
|
||||
return http_code >= 200 && http_code < 300 ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
const int result = http_post(path, body.dump(), &response, &http_code);
|
||||
if (result != BAMBU_NETWORK_SUCCESS || http_code < 200 || http_code >= 300) {
|
||||
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: print-jobs/" << job_id << "/start failed http_code=" << http_code;
|
||||
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||
}
|
||||
return BAMBU_NETWORK_SUCCESS;
|
||||
}
|
||||
|
||||
void OrcaCloudServiceAgent::enable_multi_machine(bool enable)
|
||||
|
||||
@@ -230,11 +230,29 @@ public:
|
||||
// that need non-blocking behaviour run it on their own thread.
|
||||
int send_printer_command(const std::string& dev_id, const std::string& body);
|
||||
|
||||
// Submit raw sliced G-code to the cloud printer print-jobs endpoint.
|
||||
int send_print_job(const std::string& dev_id,
|
||||
const std::string& filename,
|
||||
const std::string& gcode,
|
||||
bool start = true);
|
||||
// Upload sliced G-code to the cloud printer's print job storage: requests a
|
||||
// short-lived presigned URL (POST print-jobs/uploads), then PUTs the file
|
||||
// straight to R2 with that URL. Does not start the print or wait for
|
||||
// OrcaSonar to download it - see OrcaPrinterAgent::start_print/start_sdcard_print
|
||||
// for the MQTT hand-off that follows. *job_id receives the id to correlate
|
||||
// with that hand-off; update_fn receives upload progress via Http's on_progress.
|
||||
int upload_gcode_via_cloud(const std::string& dev_id,
|
||||
const std::string& local_gcode_path,
|
||||
std::string* job_id,
|
||||
OnUpdateStatusFn update_fn,
|
||||
WasCancelledFn cancel_fn);
|
||||
|
||||
// Finalize a presigned print-job upload (step 3 of 3): POST
|
||||
// print-jobs/<job_id>/start. The gateway HEAD-verifies the object landed in
|
||||
// R2, then relays a print.project_file command (download URL + filename +
|
||||
// start) to the printer over the gateway's OWN cloud relay connection to
|
||||
// OrcaSonar - NOT this agent's MQTT session. See
|
||||
// CLOUD_PRINT_JOB_MQTT_DESIGN.md for the MQTT-native alternative this
|
||||
// stands in for until the gap documented there is closed.
|
||||
int start_cloud_print_job(const std::string& dev_id,
|
||||
const std::string& job_id,
|
||||
const std::string& filename,
|
||||
bool start = true);
|
||||
|
||||
// ========================================================================
|
||||
// ICloudServiceAgent Interface Implementation - Settings Synchronization
|
||||
|
||||
@@ -1411,6 +1411,19 @@ AgentInfo OrcaPrinterAgent::get_agent_info_static()
|
||||
// Print Job Operations - All Stubs
|
||||
// ============================================================================
|
||||
|
||||
// Orchestrates the cloud print workflow: upload the sliced G-code straight to R2
|
||||
// via a presigned URL (upload_gcode_via_cloud), then finalize over HTTP
|
||||
// (start_cloud_print_job). The finalize call is what actually gets the file to
|
||||
// the printer: the gateway HEAD-verifies the R2 object, then relays
|
||||
// print.project_file to OrcaSonar over the gateway's OWN cloud relay connection -
|
||||
// not this agent's MQTT session. See CLOUD_PRINT_JOB_MQTT_DESIGN.md for the
|
||||
// MQTT-native alternative (publishing print.project_file directly over this
|
||||
// agent's own cloud connection) and why it isn't used yet.
|
||||
//
|
||||
// This deliberately does NOT go through start_sdcard_print/print.gcode_file:
|
||||
// that command is the generic "start this file already on the printer" primitive
|
||||
// (also used by the LAN start_local_print path, and meant to stay that way as it
|
||||
// grows params like filament mapping), and has no download-awareness to give it.
|
||||
int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||
{
|
||||
(void) wait_fn;
|
||||
@@ -1426,28 +1439,33 @@ int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn
|
||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||
|
||||
const std::string local_path = resolve_local_gcode_path(params);
|
||||
const fs::path source(local_path);
|
||||
boost::filesystem::ifstream input(source, std::ios::in | std::ios::binary);
|
||||
if (!input) {
|
||||
boost::system::error_code ec;
|
||||
if (!fs::exists(local_path, ec) || !fs::is_regular_file(local_path, ec)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaPrinterAgent: G-code file does not exist: " << local_path;
|
||||
return BAMBU_NETWORK_ERR_FILE_NOT_EXIST;
|
||||
}
|
||||
|
||||
const std::string gcode((std::istreambuf_iterator<char>(input)), std::istreambuf_iterator<char>());
|
||||
if (input.bad()) {
|
||||
BOOST_LOG_TRIVIAL(error) << "OrcaPrinterAgent: failed to read G-code file: " << local_path;
|
||||
return BAMBU_NETWORK_ERR_PRINT_WR_POST_TASK_FAILED;
|
||||
}
|
||||
if (cancel_fn && cancel_fn())
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageUpload, 0, "Uploading G-code...");
|
||||
|
||||
const int result = cloud->send_print_job(params.dev_id, remote_gcode_name(params), gcode, true);
|
||||
std::string job_id;
|
||||
int result = cloud->upload_gcode_via_cloud(params.dev_id, local_path, &job_id, update_fn, cancel_fn);
|
||||
if (result != BAMBU_NETWORK_SUCCESS)
|
||||
return result;
|
||||
|
||||
if (cancel_fn && cancel_fn())
|
||||
return BAMBU_NETWORK_ERR_CANCELED;
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageSending, 0, "Starting print...");
|
||||
|
||||
const int start_rc = cloud->start_cloud_print_job(params.dev_id, job_id, remote_gcode_name(params), /*start=*/true);
|
||||
if (start_rc != BAMBU_NETWORK_SUCCESS)
|
||||
return start_rc;
|
||||
|
||||
if (update_fn)
|
||||
update_fn(PrintingStageFinished, 100, "Print started");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user