mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-30 12:21:05 +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;
|
: BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||||
}
|
}
|
||||||
|
|
||||||
int OrcaCloudServiceAgent::send_print_job(const std::string& dev_id,
|
int OrcaCloudServiceAgent::upload_gcode_via_cloud(const std::string& dev_id,
|
||||||
const std::string& filename,
|
const std::string& local_gcode_path,
|
||||||
const std::string& gcode,
|
std::string* job_id,
|
||||||
bool start)
|
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;
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
|
|
||||||
const std::string path = std::string(ORCA_CLOUD_PRINTER) + "/" + Http::url_encode(dev_id) + "/print-jobs?filename=" +
|
nlohmann::json body;
|
||||||
Http::url_encode(filename) + "&start=" + (start ? "true" : "false");
|
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;
|
unsigned int http_code = 0;
|
||||||
const int result = http_post(path, gcode, nullptr, &http_code, "text/plain");
|
const int result = http_post(path, body.dump(), &response, &http_code);
|
||||||
if (result != BAMBU_NETWORK_SUCCESS)
|
if (result != BAMBU_NETWORK_SUCCESS || http_code < 200 || http_code >= 300) {
|
||||||
return result;
|
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: print-jobs/" << job_id << "/start failed http_code=" << http_code;
|
||||||
return http_code >= 200 && http_code < 300 ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
return BAMBU_NETWORK_ERR_CONNECT_FAILED;
|
||||||
|
}
|
||||||
|
return BAMBU_NETWORK_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OrcaCloudServiceAgent::enable_multi_machine(bool enable)
|
void OrcaCloudServiceAgent::enable_multi_machine(bool enable)
|
||||||
|
|||||||
@@ -230,11 +230,29 @@ public:
|
|||||||
// that need non-blocking behaviour run it on their own thread.
|
// that need non-blocking behaviour run it on their own thread.
|
||||||
int send_printer_command(const std::string& dev_id, const std::string& body);
|
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.
|
// Upload sliced G-code to the cloud printer's print job storage: requests a
|
||||||
int send_print_job(const std::string& dev_id,
|
// short-lived presigned URL (POST print-jobs/uploads), then PUTs the file
|
||||||
const std::string& filename,
|
// straight to R2 with that URL. Does not start the print or wait for
|
||||||
const std::string& gcode,
|
// OrcaSonar to download it - see OrcaPrinterAgent::start_print/start_sdcard_print
|
||||||
bool start = true);
|
// 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
|
// ICloudServiceAgent Interface Implementation - Settings Synchronization
|
||||||
|
|||||||
@@ -1411,6 +1411,19 @@ AgentInfo OrcaPrinterAgent::get_agent_info_static()
|
|||||||
// Print Job Operations - All Stubs
|
// 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)
|
int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
|
||||||
{
|
{
|
||||||
(void) wait_fn;
|
(void) wait_fn;
|
||||||
@@ -1426,28 +1439,33 @@ int OrcaPrinterAgent::start_print(PrintParams params, OnUpdateStatusFn update_fn
|
|||||||
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
|
||||||
|
|
||||||
const std::string local_path = resolve_local_gcode_path(params);
|
const std::string local_path = resolve_local_gcode_path(params);
|
||||||
const fs::path source(local_path);
|
boost::system::error_code ec;
|
||||||
boost::filesystem::ifstream input(source, std::ios::in | std::ios::binary);
|
if (!fs::exists(local_path, ec) || !fs::is_regular_file(local_path, ec)) {
|
||||||
if (!input) {
|
|
||||||
BOOST_LOG_TRIVIAL(error) << "OrcaPrinterAgent: G-code file does not exist: " << local_path;
|
BOOST_LOG_TRIVIAL(error) << "OrcaPrinterAgent: G-code file does not exist: " << local_path;
|
||||||
return BAMBU_NETWORK_ERR_FILE_NOT_EXIST;
|
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())
|
if (cancel_fn && cancel_fn())
|
||||||
return BAMBU_NETWORK_ERR_CANCELED;
|
return BAMBU_NETWORK_ERR_CANCELED;
|
||||||
|
|
||||||
if (update_fn)
|
if (update_fn)
|
||||||
update_fn(PrintingStageUpload, 0, "Uploading G-code...");
|
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)
|
if (result != BAMBU_NETWORK_SUCCESS)
|
||||||
return result;
|
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)
|
if (update_fn)
|
||||||
update_fn(PrintingStageFinished, 100, "Print started");
|
update_fn(PrintingStageFinished, 100, "Print started");
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user