revert file transfer abstraction

This commit is contained in:
Ian Chua
2026-08-17 14:19:41 +08:00
parent a34d056de2
commit 6558c52849
9 changed files with 556 additions and 752 deletions

View File

@@ -12,7 +12,9 @@
#include "slic3r/GUI/DeviceCore/DevManager.h"
#include "slic3r/GUI/DeviceCore/DevUtil.h"
#include "IPrinterAgent.hpp"
#include "slic3r/Utils/FileTransferUtils.hpp"
#include "slic3r/Utils/BBLNetworkPlugin.hpp"
#include "NetworkAgent.hpp"
namespace Slic3r {
namespace GUI {
@@ -206,6 +208,51 @@ void PrintJob::process(Ctl &ctl)
params.username = m_agent->default_lan_username();
params.password = m_access_code;
// check access code and ip address
if (this->connection_type == "lan" && m_print_type == "from_normal") {
bool emmc_ok = false;
bool ftp_ok = false;
if (could_emmc_print) {
std::string devIP = m_dev_ip;
std::string accessCode = m_access_code;
std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
try {
std::unique_ptr<FileTransferTunnel> tunnel = std::make_unique<FileTransferTunnel>(module(), url);
emmc_ok = tunnel->sync_start_connect();
} catch (const std::exception &e) {
BOOST_LOG_TRIVIAL(warning) << "eMMC tunnel unavailable, falling back to FTP: " << e.what();
emmc_ok = false;
}
}
{
params.dev_id = m_dev_id;
params.project_name = "verify_job";
params.filename = job_data._temp_path.string();
params.connection_type = this->connection_type;
result = m_agent->start_send_gcode_to_sdcard(params, nullptr, nullptr, nullptr);
ftp_ok = result == 0;
}
if (!emmc_ok && !ftp_ok) {
bool legacy_mode = BBLNetworkPlugin::instance().use_legacy_network();
BOOST_LOG_TRIVIAL(error) << "LAN connection verification failed:"
<< " emmc_ok=" << emmc_ok
<< ", ftp_ok=" << ftp_ok
<< ", ftp_result=" << result
<< ", dev_ip=" << m_dev_ip
<< ", dev_id=" << m_dev_id
<< ", password_length=" << m_access_code.size()
<< ", legacy_mode=" << (legacy_mode ? "true" : "false");
m_enter_ip_address_fun_fail();
m_job_finished = true;
return;
}
params.project_name = "";
params.filename = "";
}
params.dev_id = m_dev_id;
params.ftp_folder = m_ftp_folder;
params.filename = job_data._3mf_path.string();
@@ -596,13 +643,6 @@ void PrintJob::process(Ctl &ctl)
}
if (result < 0) {
if (result == ORCA_NETWORK_ERR_ACCESS_VERIFICATION_FAILED) {
if (m_enter_ip_address_fun_fail)
m_enter_ip_address_fun_fail();
m_job_finished = true;
return;
}
curr_percent = -1;
// The printer is still fetching its encryption flag (a transient state), so ask the

View File

@@ -14,6 +14,7 @@
#include <wx/dcgraph.h>
#include <wx/simplebook.h>
#include "NetworkAgent.hpp"
#include "Widgets/Label.hpp"
#include "Widgets/CheckBox.hpp"
#include "Widgets/Button.hpp"
@@ -117,7 +118,7 @@ private:
std::map<uint32_t, std::string> m_parts_name;
std::vector<int> m_partskip_ids;
enum URL_STATE m_url_state = URL_STATE::URL_TCP;
URL_STATE m_url_state = URL_STATE::URL_TCP;
PartsInfo GetPartsInfo();
bool is_drag_mode();

View File

@@ -1,7 +1,6 @@
#include "SendToPrinter.hpp"
#include "I18N.hpp"
#include "IPrinterAgent.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/Thread.hpp"
#include "GUI.hpp"
@@ -26,6 +25,7 @@
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevStorage.h"
#include "slic3r/Utils/FileTransferUtils.hpp"
namespace Slic3r {
@@ -845,8 +845,11 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
m_task_timer.reset();
}
if (auto agent = wxGetApp().getAgent(); agent && agent->get_printer_agent())
agent->get_printer_agent()->cancel_file_transfer();
if (m_filetransfer_uploadfile_job) {
m_filetransfer_uploadfile_job->cancel();
m_filetransfer_uploadfile_job.reset();
m_filetransfer_uploadfile_job = nullptr;
}
m_is_canceled = true;
wxCommandEvent* event = new wxCommandEvent(EVT_PRINT_JOB_CANCEL);
@@ -871,10 +874,10 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
return;
bool cancelled = false;
wxString msg = _L("Preparing print job");
m_status_bar->update_status(msg, cancelled, 10, true);
m_export_3mf_cancel = cancel = cancelled;
});
}
m_status_bar->update_status(msg, cancelled, 10, true);
m_export_3mf_cancel = cancel = cancelled;
});
}
if (m_is_canceled || m_export_3mf_cancel) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": send progress 10";
@@ -938,8 +941,7 @@ void SendToPrinterDialog::on_ok(wxCommandEvent &event)
this->Bind(wxEVT_TIMER, [this](auto e){
show_status(PrintDialogStatus::PrintStatusPublicUploadFiled);
if (auto agent = wxGetApp().getAgent(); agent && agent->get_printer_agent())
agent->get_printer_agent()->cancel_file_transfer();
m_filetransfer_uploadfile_job->cancel();
update_print_status_msg(_L("Upload file timeout, please check if the firmware version supports it."), false, true);
},m_task_timer->GetId());
m_task_timer->StartOnce(timeout_period);
@@ -1295,8 +1297,10 @@ void SendToPrinterDialog::update_show_status()
else
m_if_has_sdcard = true;
if (auto agent = wxGetApp().getAgent(); agent && agent->get_printer_agent())
agent->get_printer_agent()->cancel_file_transfer();
if (m_filetransfer_tunnel) {
m_filetransfer_tunnel.reset();
m_filetransfer_tunnel = nullptr;
}
GetConnection();
}
@@ -1655,6 +1659,7 @@ bool SendToPrinterDialog::Show(bool show)
return DPIDialog::Show(show);
}
extern wxString hide_passwd(wxString url, std::vector<wxString> const &passwords);
extern void refresh_agora_url(char const *device, char const *dev_ver, char const *channel, void *context, void (*callback)(void *context, char const *url));
void SendToPrinterDialog::GetConnection()
@@ -1665,80 +1670,102 @@ void SendToPrinterDialog::GetConnection()
if (obj == nullptr) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : obj is empty";
m_connection_status = ConnectionStatus::NOT_START;
return;
}
int remote_proto = obj->get_file_remote();
if (!remote_proto) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : remote_proto is not support";
m_connection_status = ConnectionStatus::NOT_START;
return;
}
if (obj->is_camera_busy_off()) {
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : camera is busy";
m_connection_status = ConnectionStatus::NOT_START;
return;
}
NetworkAgent *agent = wxGetApp().getAgent();
if (!agent || !agent->get_printer_agent()) {
show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard,
_L("The selected printer does not support file transfer."));
return;
NetworkAgent *agent = wxGetApp().getAgent();
std::string agent_version = agent ? agent->get_version() : "";
std::string dev_ver = obj->get_ota_version();
std::string dev_id = obj->get_dev_id();
if (m_url_timer && m_url_timer->IsRunning())
{
m_url_timer->Stop();
}
m_url_timer.reset(new wxTimer());
m_url_timer->SetOwner(this);
this->Bind(
wxEVT_TIMER,
[this](wxTimerEvent &e) {
BOOST_LOG_TRIVIAL(info) << "Timer callback triggered!";
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
m_ftp_try_connect = true;
if (m_filetransfer_tunnel)
{
m_filetransfer_tunnel.reset();
m_filetransfer_tunnel = nullptr;
}
},
m_url_timer->GetId());
m_url_timer->StartOnce(8000);
if (agent) {
if (m_tcp_try_connect) {
std::string devIP = obj->get_dev_ip();
std::string accessCode = obj->get_access_code();
std::string url = "bambu:///local/" + devIP + "?port=6000&user=" + "bblp" + "&passwd=" + accessCode;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tcp";
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) {
CallAfter([this, is_success, err_code, error_msg]() {
OnConnection(is_success, err_code, error_msg);
});
});
m_filetransfer_tunnel->start_connect();
}
else if (m_tutk_try_connect)
{
std::string protocols[] = {"", "\"tutk\"", "\"agora\"", "\"tutk\",\"agora\""};
agent->get_camera_url(obj->get_dev_id() + "|" + dev_ver + "|" + protocols[1], [this, m = dev_id](CameraURLResult result) {
std::string url = std::move(result.url);
if (m_url_timer && m_url_timer->IsRunning())
{
m_url_timer->Stop();
}
#if !BBL_RELEASE_TO_PUBLIC
BOOST_LOG_TRIVIAL(info) << "SendToPrinter::camera_url: " << hide_passwd(url, {"?uid=", "authkey=", "passwd="});
#endif
if (result.is_success)
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Connect method tutk";
m_filetransfer_tunnel = std::make_unique<FileTransferTunnel>(module(), url);
m_filetransfer_tunnel->on_connection([this](bool is_success, int err_code, std::string error_msg) {
CallAfter([this, is_success, err_code, error_msg]() { OnConnection(is_success, err_code, error_msg); });
});
m_filetransfer_tunnel->start_connect();
}
else
{
std::string res = "";
if (!url.empty() && boost::ends_with(url, "]"))
{
size_t n = url.find_last_of('[');
if (n != std::string::npos)
res = url.substr(n + 1, url.length() - n - 2);
}
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " : Tutk url error: ress = " << res;
}
}, wxGetApp().get_printer_cloud_provider(),
CameraURLParams{"", "", "", LVL_None, dev_id, agent->get_version(), dev_ver,
boost::lexical_cast<std::string>(&refresh_agora_url), wxGetApp().app_config->get("slicer_uuid"), SLIC3R_VERSION, true});
}
}
if (m_url_timer && m_url_timer->IsRunning()) {
m_url_timer->Stop();
}
m_url_timer.reset(new wxTimer());
m_url_timer->SetOwner(this);
this->Bind(
wxEVT_TIMER,
[this](wxTimerEvent& e) {
BOOST_LOG_TRIVIAL(info) << "Timer callback triggered!";
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
m_ftp_try_connect = true;
if (auto agent = wxGetApp().getAgent(); agent && agent->get_printer_agent())
agent->get_printer_agent()->cancel_file_transfer();
},
m_url_timer->GetId());
m_url_timer->StartOnce(8000);
IPrinterAgent::FileTransferRequest request;
request.device_id = obj->get_dev_id();
request.device_ip = obj->get_dev_ip();
request.access_code = obj->get_access_code();
request.network_version = agent->get_version();
request.device_version = obj->get_ota_version();
request.refresh_url = boost::lexical_cast<std::string>(&refresh_agora_url);
request.client_id = wxGetApp().app_config->get("slicer_uuid");
request.client_version = SLIC3R_VERSION;
request.lan_mode = obj->connection_type() == "lan";
IPrinterAgent::FileTransferCallbacks callbacks;
callbacks.on_connection = [this](bool is_success, int error_code, std::string error_msg) {
CallAfter([this, is_success, error_code, error_msg = std::move(error_msg)] {
OnConnection(is_success, error_code, std::move(error_msg));
});
};
callbacks.file_transfer_error = [this] {
CallAfter([this] {
show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard,
_L("The selected printer does not support file transfer."));
});
};
agent->get_printer_agent()->prepare_file_transfer(request, std::move(callbacks));
}
void SendToPrinterDialog::show_file_transfer_error(PrintDialogStatus status, wxString message)
{
if (m_url_timer && m_url_timer->IsRunning())
m_url_timer->Stop();
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
show_status(status);
update_print_status_msg(message, false, true);
}
void SendToPrinterDialog::OnConnection(bool is_success, int error_code, std::string error_msg) {
@@ -1753,13 +1780,43 @@ void SendToPrinterDialog::OnConnection(bool is_success, int error_code, std::str
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << "Connect failed, error_code is:" << error_code << "error_msg is :" << error_msg;
m_connection_status = ConnectionStatus::CONNECTION_FAILED;
m_tcp_try_connect = false;
m_tutk_try_connect = false;
m_ftp_try_connect = true;
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
ChangeConnectMethod();
if (!m_tcp_try_connect && !m_tutk_try_connect) {
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
return;
}
m_filetransfer_tunnel.reset();
m_filetransfer_tunnel = nullptr;
GetConnection();
}
}
void SendToPrinterDialog::ChangeConnectMethod()
{
DeviceManager *dev = Slic3r::GUI::wxGetApp().getDeviceManager();
if (!dev) return;
MachineObject *obj = dev->get_my_machine(m_printer_last_select);
if (!obj) return;
bool is_lan = (obj->connection_type() == "lan");
m_tcp_try_connect = false;
if (is_lan) {
m_ftp_try_connect = true;
m_tutk_try_connect = false;
} else {
if (m_connect_try_times == 0) {
m_ftp_try_connect = false;
m_tutk_try_connect = true;
} else {
m_ftp_try_connect = true;
m_tutk_try_connect = false;
}
}
m_connect_try_times++;
}
void SendToPrinterDialog::ResetConnectMethod()
{
m_tcp_try_connect = true;
@@ -1771,21 +1828,32 @@ void SendToPrinterDialog::ResetConnectMethod()
void SendToPrinterDialog::ResetTunnelAndJob()
{
if (auto agent = wxGetApp().getAgent(); agent && agent->get_printer_agent())
agent->get_printer_agent()->cancel_file_transfer();
if (m_filetransfer_uploadfile_job)
{
m_filetransfer_uploadfile_job->cancel();
m_filetransfer_uploadfile_job.reset();
m_filetransfer_uploadfile_job = nullptr;
}
if (m_filetransfer_mediability_job)
{
m_filetransfer_mediability_job->cancel();
m_filetransfer_mediability_job.reset();
m_filetransfer_mediability_job = nullptr;
}
if (m_filetransfer_tunnel)
{
m_filetransfer_tunnel.reset();
m_filetransfer_tunnel = nullptr;
}
}
void SendToPrinterDialog::CreateMediaAbilityJob()
{
NetworkAgent *agent = wxGetApp().getAgent();
if (!agent || !agent->get_printer_agent()) {
show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard,
_L("The selected printer does not support file transfer."));
return;
}
IPrinterAgent::FileTransferCallbacks callbacks;
callbacks.on_destinations = [this](int res, int resp_ec, std::string json_res) {
CallAfter([this, res, resp_ec, json_res = std::move(json_res)] {
nlohmann::json media_ability = {{"cmd_type", 7}};
m_filetransfer_mediability_job = std::make_unique<FileTransferJob>(module(), std::string(media_ability.dump()));
m_filetransfer_mediability_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) {
//this pl
CallAfter([this, res, resp_ec, json_res] {
if (res == 0) // 0 is success
{
show_status(PrintDialogStatus::PrintStatusReadingFinished);
@@ -1821,41 +1889,59 @@ void SendToPrinterDialog::CreateMediaAbilityJob()
show_status(PrintDialogStatus::PrintStatusPublicInitFailed);
update_print_status_msg(ParseErrorCode(resp_ec), false, true);
}
});
};
callbacks.file_transfer_error = [this] {
CallAfter([this] {
show_file_transfer_error(PrintDialogStatus::PrintStatusNotSupportedSendToSDCard,
_L("The selected printer does not support file transfer."));
});
};
agent->get_printer_agent()->get_file_destinations(std::move(callbacks));
});
});
// Guard against a null transfer tunnel before dereferencing.
if (m_filetransfer_tunnel) {
m_filetransfer_mediability_job->start_on(*m_filetransfer_tunnel);
} else {
BOOST_LOG_TRIVIAL(info) << "CreateMediaAbilityJob: file transfer tunnel is null";
}
}
void SendToPrinterDialog::CreateUploadFileJob(const std::string &path, const std::string &name)
{
NetworkAgent *agent = wxGetApp().getAgent();
if (!agent || !agent->get_printer_agent()) {
show_file_transfer_error(PrintDialogStatus::PrintStatusPublicUploadFiled,
_L("The selected printer does not support file transfer."));
return;
nlohmann::json upload_params = {
{"cmd_type", 5},
};
upload_params["dest_storage"] = m_selected_storage;
upload_params["dest_name"] = name; // filenme no path
upload_params["file_path"] = path;
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": Begin CreateUploadFileJob";
m_filetransfer_uploadfile_job = std::make_unique<FileTransferJob>(module(), std::string(upload_params.dump()));
m_filetransfer_uploadfile_job->on_result([this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) { //
CallAfter([this, res, resp_ec, json_res, bin_res] {
UploadFileRessultCallback(res, resp_ec,json_res, bin_res);
});
});
m_filetransfer_uploadfile_job->on_msg([this](int kind, std::string json_res) {
CallAfter([this, kind, json_res] {
if (kind == 0) {
try
{
auto js = nlohmann::json::parse(json_res);
int progress = js["progress"].get<int>();
UploadFileProgressCallback(progress);
}
catch (const nlohmann::json::exception& e)
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": " << e.what();
}
catch (...)
{
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": " << "parse_json failed! ";
}
}
});
});
// Guard against a null transfer tunnel before dereferencing.
if (m_filetransfer_tunnel) {
m_filetransfer_uploadfile_job->start_on(*m_filetransfer_tunnel);
} else {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": file transfer tunnel is null";
}
IPrinterAgent::FileTransferCallbacks callbacks;
callbacks.on_progress = [this](int progress) {
CallAfter([this, progress] { UploadFileProgressCallback(progress); });
};
callbacks.on_result = [this](int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res) {
CallAfter([this, res, resp_ec, json_res = std::move(json_res), bin_res = std::move(bin_res)] {
UploadFileRessultCallback(res, resp_ec, std::move(json_res), std::move(bin_res));
});
};
callbacks.file_transfer_error = [this] {
CallAfter([this] {
show_file_transfer_error(PrintDialogStatus::PrintStatusPublicUploadFiled,
_L("The selected printer does not support file transfer."));
});
};
agent->get_printer_agent()->upload_file(path, name, m_selected_storage, std::move(callbacks));
}
void SendToPrinterDialog::UploadFileProgressCallback(int progress)
@@ -1874,8 +1960,6 @@ void SendToPrinterDialog::UploadFileProgressCallback(int progress)
wxEVT_TIMER,
[this](auto e) {
show_status(PrintDialogStatus::PrintStatusPublicUploadFiled);
if (auto agent = wxGetApp().getAgent(); agent && agent->get_printer_agent())
agent->get_printer_agent()->cancel_file_transfer();
update_print_status_msg(
_L("File upload timed out. Please check if the firmware version supports this operation or verify if the printer is functioning properly."), false, true);
},
@@ -1904,6 +1988,8 @@ void SendToPrinterDialog::UploadFileRessultCallback(int res, int resp_ec, std::s
update_print_status_msg(ParseErrorCode(resp_ec), false, true);
else
update_print_status_msg(_L("Sending failed, please try again!"), false, true);
m_filetransfer_uploadfile_job.reset();
m_filetransfer_uploadfile_job = nullptr;
}
}

View File

@@ -24,7 +24,6 @@
#include <wx/wrapsizer.h>
#include <wx/srchctrl.h>
#include "IPrinterAgent.hpp"
#include "SelectMachine.hpp"
#include "GUI_Utils.hpp"
#include "wxExtensions.hpp"
@@ -44,6 +43,8 @@
namespace Slic3r {
class FileTransferTunnel;
class FileTransferJob;
namespace GUI {
@@ -170,6 +171,9 @@ private:
enum ConnectionStatus { NOT_START, CONNECTING, CONNECTED, CONNECTION_FAILED, DISCONNECTED };
ConnectionStatus m_connection_status{ConnectionStatus::NOT_START};
std::unique_ptr<FileTransferTunnel> m_filetransfer_tunnel;
std::unique_ptr<FileTransferJob> m_filetransfer_mediability_job;
std::unique_ptr<FileTransferJob> m_filetransfer_uploadfile_job;
wxDateTime m_last_refresh_time;
public:
@@ -219,10 +223,10 @@ public:
private:
void ResetConnectMethod();
void ResetTunnelAndJob();
void show_file_transfer_error(PrintDialogStatus status, wxString message);
void OnConnection(bool is_success, int error_code, std::string error_msg);
void CreateMediaAbilityJob();
void CreateUploadFileJob(const std::string &path, const std::string &name);
void ChangeConnectMethod();
void UploadFileProgressCallback(int progress);
void UploadFileRessultCallback(int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res);
void Reset();

View File

@@ -1,6 +1,5 @@
#include "BBLPrinterAgent.hpp"
#include "BBLNetworkPlugin.hpp"
#include "FileTransferUtils.hpp"
#include "IPrinterAgent.hpp"
#include "NetworkAgentFactory.hpp"
#include "NetworkAgent.hpp"
@@ -11,395 +10,13 @@
#include <nlohmann/json.hpp>
#include <cmath>
#include <slic3r/GUI/DeviceManager.hpp>
#include <libslic3r/Utils.hpp>
namespace Slic3r {
// ============================================================================
// File Transfer (Bambu eMMC tunnel ABI)
// ============================================================================
BBLFileTransferTunnel::BBLFileTransferTunnel(const std::string &url) : IFileTransferTunnel(url)
{
FileTransferModule &m = module();
m_ = &m;
// Guard against missing symbols in older Bambu networking plugins.
// These symbols were added in a newer plugin ABI; if the installed
// plugin predates them, ft_tunnel_create/ft_tunnel_set_status_cb
// will be null and calling them crashes.
if (!m_->ft_tunnel_create || !m_->ft_tunnel_set_status_cb) {
throw std::runtime_error("Bambu networking plugin is too old: missing ft_tunnel_* symbols. "
"Please update the networking plugin.");
}
FT_TunnelHandle *h{};
if (m_->ft_tunnel_create(url.c_str(), &h) != 0 || !h) {
throw std::runtime_error("ft_tunnel_create failed");
}
h_ = h;
// C API: ft_status_cb(void* user, int old_status, int new_status, int err, const char* msg)
auto tramp = [](void *user, int old_status, int new_status, int err_code, const char *msg) noexcept {
auto *self = reinterpret_cast<BBLFileTransferTunnel *>(user);
self->status_ = new_status;
if (!self->status_cb_) return;
try {
self->status_cb_(old_status, new_status, err_code, std::string(msg ? msg : ""));
} catch (...) {}
};
if (m_->ft_tunnel_set_status_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_set_status_cb failed"); }
}
void BBLFileTransferTunnel::start_connect()
{
// C API: ft_conn_cb(void* user, int ok, int err, const char* msg)
auto tramp = [](void *user, int ok, int ec, const char *msg) noexcept {
auto *pcb = reinterpret_cast<ConnectionCb *>(user);
if (!pcb) return;
try {
(*pcb)(ok == 0, ec, std::string(msg ? msg : ""));
} catch (...) {}
};
if (m_->ft_tunnel_start_connect(h_, tramp, &conn_cb_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_connect failed"); }
}
bool BBLFileTransferTunnel::sync_start_connect()
{
return m_->ft_tunnel_sync_connect(h_) == FT_OK;
}
void BBLFileTransferTunnel::shutdown()
{
if (m_->ft_tunnel_shutdown) (void) m_->ft_tunnel_shutdown(h_);
}
BBLFileTransferJob::BBLFileTransferJob(const std::string &params_json) : IFileTransferJob(params_json)
{
m_ = &module();
FT_JobHandle *h{};
if (m_->ft_job_create(params_json.c_str(), &h) != 0 || !h) {
throw std::runtime_error("ft_job_create failed");
}
h_ = h;
// C API: ft_job_result_cb(void* user, int tunnel_err, ft_job_result result)
auto tramp = [](void *user, ft_job_result r) noexcept {
auto *self = reinterpret_cast<BBLFileTransferJob *>(user);
if (!self) return;
try {
self->finished_ = true;
self->solve_result(r);
if (self->result_cb_) self->result_cb_(self->res_, self->resp_ec_, self->res_json_, self->res_bin_);
} catch (...) {
// swallow
}
try {
if (auto *mod = self ? self->m_ : nullptr) {
if (mod->ft_job_result_destroy)
mod->ft_job_result_destroy(&r);
else if (mod->ft_free) {
if (r.json) mod->ft_free((void *) r.json);
if (r.bin) mod->ft_free((void *) r.bin);
}
}
} catch (...) {}
};
if (m_->ft_job_set_result_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_result_cb failed"); }
}
bool BBLFileTransferJob::get_result(int &ec, int &resp_ec, std::string &json, std::vector<std::byte> &bin, uint32_t timeout_ms)
{
if (!h_) throw std::runtime_error("job handle invalid");
ft_job_result result;
if (m_->ft_job_get_result(h_, timeout_ms, &result) == ft_err::FT_EXCEPTION) return false;
solve_result(result);
m_->ft_job_result_destroy(&result);
ec = res_;
resp_ec = res_;
json = res_json_;
bin = res_bin_;
return true;
}
void BBLFileTransferJob::start_on(IFileTransferTunnel &t)
{
if (!h_) throw std::runtime_error("job handle invalid");
auto *handle = reinterpret_cast<FT_TunnelHandle *>(t.native());
if (m_->ft_tunnel_start_job(handle, h_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_job failed"); }
}
void BBLFileTransferJob::on_msg(MsgCb cb)
{
IFileTransferJob::on_msg(std::move(cb));
if (!h_) return;
// C API: ft_job_msg_cb(void* user, ft_job_msg msg)
auto tramp = [](void *user, ft_job_msg m) noexcept {
auto *self = reinterpret_cast<BBLFileTransferJob *>(user);
if (!self) return;
try {
if (self->msg_cb_) { self->msg_cb_(m.kind, std::string(m.json ? m.json : "")); }
} catch (...) {}
try {
if (auto *mod = self->m_) {
if (mod->ft_job_msg_destroy)
mod->ft_job_msg_destroy(&m);
else if (mod->ft_free && m.json)
mod->ft_free((void *) m.json);
}
} catch (...) {}
};
if (m_->ft_job_set_msg_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_msg_cb failed"); }
}
bool BBLFileTransferJob::try_get_msg(int &kind, std::string &json)
{
if (!h_) return false;
ft_job_msg m{};
int rc = m_->ft_job_try_get_msg(h_, &m);
if (rc != 0) return false;
kind = m.kind;
json.assign(m.json ? m.json : "");
if (m_->ft_job_msg_destroy)
m_->ft_job_msg_destroy(&m);
else if (m_->ft_free && m.json)
m_->ft_free((void *) m.json);
return true;
}
bool BBLFileTransferJob::get_msg(uint32_t timeout_ms, int &kind, std::string &json)
{
if (!h_) return false;
ft_job_msg m{};
int rc = m_->ft_job_get_msg(h_, timeout_ms, &m);
if (rc != 0) return false;
kind = m.kind;
json.assign(m.json ? m.json : "");
if (m_->ft_job_msg_destroy)
m_->ft_job_msg_destroy(&m);
else if (m_->ft_free && m.json)
m_->ft_free((void *) m.json);
return true;
}
void BBLFileTransferJob::solve_result(ft_job_result result)
{
res_ = result.ec;
resp_ec_ = result.resp_ec;
res_bin_.clear();
if (result.bin && result.bin_size) res_bin_.assign(reinterpret_cast<const std::byte *>(result.bin),
reinterpret_cast<const std::byte *>(result.bin) + result.bin_size);
res_json_.assign(result.json ? result.json : "");
}
BBLPrinterAgent::BBLPrinterAgent() = default;
BBLPrinterAgent::~BBLPrinterAgent() = default;
void BBLPrinterAgent::prepare_file_transfer(const FileTransferRequest& request, FileTransferCallbacks cb)
{
cancel_file_transfer();
m_file_transfer_request = request;
m_file_transfer_callbacks = std::move(cb);
m_file_transfer_tcp = true;
m_file_transfer_try_count = 0;
start_file_transfer_attempt(++m_file_transfer_generation);
}
void BBLPrinterAgent::start_file_transfer_attempt(uint64_t generation)
{
FileTransferURLParams params;
params.url_state = m_file_transfer_tcp ? URL_TCP : URL_TUTK;
params.ip_address = m_file_transfer_request.device_ip;
params.username = default_lan_username();
params.password = m_file_transfer_request.access_code;
params.device_id = m_file_transfer_request.device_id;
params.network_version = m_file_transfer_request.network_version;
params.device_version = m_file_transfer_request.device_version;
params.refresh_url = m_file_transfer_request.refresh_url;
params.client_id = m_file_transfer_request.client_id;
params.client_version = m_file_transfer_request.client_version;
auto handle_url = [this, generation](FileTransferURLResult result) {
if (generation != m_file_transfer_generation)
return;
if (!result.is_success) {
handle_file_transfer_connection(generation, false, result.error_code, "file-transfer URL unavailable");
return;
}
try {
m_file_transfer_tunnel = std::make_unique<BBLFileTransferTunnel>(result.url);
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "BBLPrinterAgent: failed to create file-transfer tunnel: " << e.what();
m_file_transfer_tunnel.reset();
}
if (!m_file_transfer_tunnel || !m_file_transfer_tunnel->check_valid()) {
handle_file_transfer_connection(generation, false, -1, "file-transfer tunnel unavailable");
return;
}
m_file_transfer_tunnel->on_connection([this, generation](bool is_success, int error_code, std::string error_msg) {
handle_file_transfer_connection(generation, is_success, error_code, std::move(error_msg));
});
m_file_transfer_tunnel->start_connect();
};
if (m_file_transfer_tcp) {
get_file_transfer_url(m_file_transfer_request.device_id, std::move(handle_url), params);
return;
}
if (!m_cloud_agent) {
handle_file_transfer_connection(generation, false, -1, "cloud file-transfer URL unavailable");
return;
}
const std::string protocols = "\"tutk\"";
m_cloud_agent->get_camera_url(
m_file_transfer_request.device_id + "|" + m_file_transfer_request.device_version + "|" + protocols,
[handle_url = std::move(handle_url)](CameraURLResult result) mutable {
FileTransferURLResult transfer_result;
transfer_result.is_success = result.is_success;
transfer_result.url = std::move(result.url);
transfer_result.error_code = result.error_code;
handle_url(std::move(transfer_result));
},
CameraURLParams{
"", "", "", LVL_None,
m_file_transfer_request.device_id,
m_file_transfer_request.network_version,
m_file_transfer_request.device_version,
m_file_transfer_request.refresh_url,
m_file_transfer_request.client_id,
m_file_transfer_request.client_version,
true
});
}
void BBLPrinterAgent::handle_file_transfer_connection(uint64_t generation, bool is_success, int error_code, std::string error_msg)
{
if (generation != m_file_transfer_generation)
return;
if (is_success) {
if (m_file_transfer_callbacks.on_connection)
m_file_transfer_callbacks.on_connection(true, error_code, std::move(error_msg));
return;
}
// Preserve the existing dialog fallback order: TCP, then TUTK for cloud
// printers, and finally the legacy FTP path handled by SendJob.
m_file_transfer_tunnel.reset();
if (!m_file_transfer_request.lan_mode && m_file_transfer_tcp && m_file_transfer_try_count == 0) {
m_file_transfer_tcp = false;
++m_file_transfer_try_count;
start_file_transfer_attempt(generation);
return;
}
if (m_file_transfer_callbacks.on_connection)
m_file_transfer_callbacks.on_connection(false, error_code, std::move(error_msg));
}
void BBLPrinterAgent::get_file_destinations(FileTransferCallbacks cb)
{
if (!m_file_transfer_tunnel || !m_file_transfer_tunnel->check_valid()) {
if (cb.file_transfer_error)
cb.file_transfer_error();
return;
}
nlohmann::json params = {{"cmd_type", 7}};
try {
m_file_transfer_job = std::make_unique<BBLFileTransferJob>(params.dump());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "BBLPrinterAgent: failed to create media-ability job: " << e.what();
m_file_transfer_job.reset();
}
if (!m_file_transfer_job || !m_file_transfer_job->check_valid()) {
if (cb.file_transfer_error)
cb.file_transfer_error();
return;
}
m_file_transfer_job->on_result([cb = std::move(cb)](int result, int response_error, std::string json_result,
std::vector<std::byte>) {
if (cb.on_destinations)
cb.on_destinations(result, response_error, std::move(json_result));
});
m_file_transfer_job->start_on(*m_file_transfer_tunnel);
}
void BBLPrinterAgent::upload_file(const std::string& path, const std::string& name, const std::string& destination,
FileTransferCallbacks cb)
{
if (!m_file_transfer_tunnel || !m_file_transfer_tunnel->check_valid()) {
if (cb.file_transfer_error)
cb.file_transfer_error();
return;
}
nlohmann::json params = {
{"cmd_type", 5},
{"dest_storage", destination},
{"dest_name", name},
{"file_path", path}
};
try {
m_file_transfer_job = std::make_unique<BBLFileTransferJob>(params.dump());
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "BBLPrinterAgent: failed to create upload job: " << e.what();
m_file_transfer_job.reset();
}
if (!m_file_transfer_job || !m_file_transfer_job->check_valid()) {
if (cb.file_transfer_error)
cb.file_transfer_error();
return;
}
auto callbacks = std::make_shared<FileTransferCallbacks>(std::move(cb));
m_file_transfer_job->on_result([callbacks](int result, int response_error, std::string json_result,
std::vector<std::byte> binary_result) {
if (callbacks->on_result)
callbacks->on_result(result, response_error, std::move(json_result), std::move(binary_result));
});
m_file_transfer_job->on_msg([callbacks](int kind, std::string json_result) {
if (kind == 0 && callbacks->on_progress) {
try {
callbacks->on_progress(nlohmann::json::parse(json_result).at("progress").get<int>());
} catch (...) {
BOOST_LOG_TRIVIAL(error) << "BBLPrinterAgent: failed to parse upload progress";
}
}
});
m_file_transfer_job->start_on(*m_file_transfer_tunnel);
}
void BBLPrinterAgent::cancel_file_transfer()
{
++m_file_transfer_generation;
if (m_file_transfer_job)
m_file_transfer_job->cancel();
m_file_transfer_job.reset();
m_file_transfer_tunnel.reset();
m_file_transfer_callbacks = {};
}
void BBLPrinterAgent::set_cloud_agent(std::shared_ptr<ICloudServiceAgent> cloud)
{
m_cloud_agent = cloud;
@@ -702,10 +319,7 @@ int BBLPrinterAgent::get_file_transfer_url(std::string dev_id, std::function<voi
callback(std::move(transfer_result));
},
CameraURLParams{
"",
"",
"",
LVL_None,
"", "", "", LVL_None,
params.device_id,
params.network_version,
params.device_version,
@@ -977,15 +591,6 @@ int BBLPrinterAgent::start_local_print_with_record(PrintParams params, OnUpdateS
BBLNetworkPlugin::instance().get_start_local_print_with_record(), params, update_fn, cancel_fn, wait_fn);
}
int BBLPrinterAgent::verify_local_print_access(PrintParams params)
{
params.project_name = "verify_job";
params.filename = Slic3r::resources_dir() + "/check_access_code.txt";
params.try_emmc_print = false;
return start_send_gcode_to_sdcard(params, nullptr, nullptr, nullptr);
}
int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, OnWaitFn wait_fn)
{
int result = dispatch_start<func_start_send_gcode_to_sdcard_legacy, func_start_send_gcode_to_sdcard_0203>(
@@ -1001,16 +606,6 @@ int BBLPrinterAgent::start_send_gcode_to_sdcard(PrintParams params, OnUpdateStat
int BBLPrinterAgent::start_local_print(PrintParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn)
{
if (params.connection_type == "lan" && params.print_type == "from_normal") {
const int verify_result = verify_local_print_access(params);
if (verify_result != 0) {
BOOST_LOG_TRIVIAL(error) << "LAN connection verification failed: result=" << verify_result
<< ", dev_ip=" << params.dev_ip << ", dev_id=" << params.dev_id
<< ", password_length=" << params.password.size();
return ORCA_NETWORK_ERR_ACCESS_VERIFICATION_FAILED;
}
}
return dispatch_start<func_start_local_print_legacy, func_start_local_print_0203>(
BBLNetworkPlugin::instance().get_start_local_print(), params, update_fn, cancel_fn);
}

View File

@@ -3,83 +3,12 @@
#include "IPrinterAgent.hpp"
#include "ICloudServiceAgent.hpp"
#include "FileTransferUtils.hpp"
#include <string>
#include <memory>
#include <nlohmann/json.hpp>
namespace Slic3r {
/**
* BBLFileTransferTunnel - Bambu eMMC tunnel, backed by the Bambu network
* plugin's ft_tunnel_* ABI (see FileTransferUtils.hpp). Only BBLPrinterAgent
* constructs these; callers only ever see them through IFileTransferTunnel.
*/
class BBLFileTransferTunnel : public IFileTransferTunnel
{
public:
BBLFileTransferTunnel(const std::string &url);
~BBLFileTransferTunnel() override { reset(); }
void start_connect() override;
bool sync_start_connect() override;
void shutdown() override;
bool check_valid() const override { return h_ != nullptr; }
void *native() const noexcept override { return h_; }
private:
void reset() noexcept
{
if (h_) {
m_->ft_tunnel_release(h_);
h_ = nullptr;
}
}
FileTransferModule *m_{};
FT_TunnelHandle *h_{};
};
/**
* BBLFileTransferJob - a single ft_job_* operation (media-ability query,
* file upload, ...) run on a BBLFileTransferTunnel. Same ABI-wrapper role
* as BBLFileTransferTunnel; only BBLPrinterAgent constructs these.
*/
class BBLFileTransferJob : public IFileTransferJob
{
public:
explicit BBLFileTransferJob(const std::string &params_json);
~BBLFileTransferJob() override { reset(); }
bool get_result(int &ec, int &resp_ec, std::string &json, std::vector<std::byte> &bin, uint32_t timeout_ms) override;
void start_on(IFileTransferTunnel &t) override;
// why: unlike on_result() (fires from a trampoline registered once in the ctor),
// ft_job_set_msg_cb is only wired up here, lazily, on first real subscriber -
// that ABI call has to happen in the concrete class, not the vendor-neutral base.
void on_msg(MsgCb cb) override;
bool try_get_msg(int &kind, std::string &json) override;
bool get_msg(uint32_t timeout_ms, int &kind, std::string &json) override;
void *native() const noexcept override { return h_; }
bool check_valid() const override { return h_ != nullptr; }
void cancel() override
{
if (m_->ft_job_cancel && h_) m_->ft_job_cancel(h_);
}
private:
void reset() noexcept
{
if (h_) {
m_->ft_job_release(h_);
h_ = nullptr;
}
}
void solve_result(ft_job_result result);
FileTransferModule *m_{};
FT_JobHandle *h_{};
};
/**
* BBLPrinterAgent - BBL DLL wrapper implementation of IPrinterAgent.
*
@@ -175,30 +104,11 @@ public:
int set_queue_on_main_fn(QueueOnMainFn fn) override;
FilamentSyncMode get_filament_sync_mode() const override;
void prepare_file_transfer(const FileTransferRequest& request, FileTransferCallbacks cb) override;
void get_file_destinations(FileTransferCallbacks cb) override;
void upload_file(const std::string& path, const std::string& name, const std::string& destination,
FileTransferCallbacks cb) override;
void cancel_file_transfer() override;
private:
int verify_local_print_access(PrintParams params);
// why: the lan/cloud DECISION stays machine-side; keep this mechanical branch in sync with publish_json.
int publish(const std::string& dev_id, const nlohmann::json& j, bool lan_mode);
std::shared_ptr<ICloudServiceAgent> m_cloud_agent;
std::unique_ptr<IFileTransferTunnel> m_file_transfer_tunnel;
std::unique_ptr<IFileTransferJob> m_file_transfer_job;
FileTransferCallbacks m_file_transfer_callbacks;
FileTransferRequest m_file_transfer_request;
bool m_file_transfer_tcp{true};
int m_file_transfer_try_count{0};
uint64_t m_file_transfer_generation{0};
void start_file_transfer_attempt(uint64_t generation);
void handle_file_transfer_connection(uint64_t generation, bool is_success, int error_code, std::string error_msg);
};
} // namespace Slic3r

View File

@@ -1,4 +1,8 @@
#include <wx/wx.h>
#include <type_traits>
#include "FileTransferUtils.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "slic3r/GUI/DeviceCore/DevManager.h"
namespace Slic3r {
@@ -33,4 +37,191 @@ FileTransferModule::FileTransferModule(ModuleHandle networking_module, int requi
ft_job_get_msg = sym_lookup<fn_ft_job_get_msg>(networking_, "ft_job_get_msg");
}
FileTransferTunnel::FileTransferTunnel(FileTransferModule &m, const std::string &url) : m_(&m)
{
// Guard against missing symbols in older Bambu networking plugins.
// These symbols were added in a newer plugin ABI; if the installed
// plugin predates them, ft_tunnel_create/ft_tunnel_set_status_cb
// will be null and calling them crashes.
if (!m_->ft_tunnel_create || !m_->ft_tunnel_set_status_cb) {
throw std::runtime_error("Bambu networking plugin is too old: missing ft_tunnel_* symbols. "
"Please update the networking plugin.");
}
FT_TunnelHandle *h{};
if (m_->ft_tunnel_create(url.c_str(), &h) != 0 || !h) {
throw std::runtime_error("ft_tunnel_create failed");
}
h_ = h;
// C API: ft_status_cb(void* user, int old_status, int new_status, int err, const char* msg)
auto tramp = [](void *user, int old_status, int new_status, int err_code, const char *msg) noexcept {
auto *self = reinterpret_cast<FileTransferTunnel *>(user);
self->status_ = new_status;
if (!self->status_cb_) return;
try {
self->status_cb_(old_status, new_status, err_code, std::string(msg ? msg : ""));
} catch (...) {}
};
if (m_->ft_tunnel_set_status_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_set_status_cb failed"); }
}
void FileTransferTunnel::start_connect()
{
// C API: ft_conn_cb(void* user, int ok, int err, const char* msg)
auto tramp = [](void *user, int ok, int ec, const char *msg) noexcept {
auto *pcb = reinterpret_cast<ConnectionCb *>(user);
if (!pcb) return;
try {
(*pcb)(ok == 0, ec, std::string(msg ? msg : ""));
} catch (...) {}
};
if (m_->ft_tunnel_start_connect(h_, tramp, &conn_cb_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_connect failed"); }
}
bool FileTransferTunnel::sync_start_connect()
{
return m_->ft_tunnel_sync_connect(h_) == FT_OK;
}
void FileTransferTunnel::on_connection(ConnectionCb cb) { conn_cb_ = std::move(cb); }
void FileTransferTunnel::on_status(TunnelStatusCb cb) { status_cb_ = std::move(cb); }
void FileTransferTunnel::shutdown()
{
if (m_->ft_tunnel_shutdown) (void) m_->ft_tunnel_shutdown(h_);
}
FileTransferJob::FileTransferJob(FileTransferModule &m, const std::string &params_json) : m_(&m)
{
FT_JobHandle *h{};
if (m_->ft_job_create(params_json.c_str(), &h) != 0 || !h) {
}
h_ = h;
// C API: ft_job_result_cb(void* user, int tunnel_err, ft_job_result result)
auto tramp = [](void *user, ft_job_result r) noexcept {
auto *self = reinterpret_cast<FileTransferJob *>(user);
if (!self) return;
try {
self->finished_ = true;
self->solve_result(r);
if (self->result_cb_) self->result_cb_(self->res_, self->resp_ec_, self->res_json_, self->res_bin_);
self->m_->ft_job_result_destroy(&r);
} catch (...) {
// swallow
}
try {
if (auto *mod = self ? self->m_ : nullptr) {
if (mod->ft_job_result_destroy)
mod->ft_job_result_destroy(&r);
else if (mod->ft_free) {
if (r.json) mod->ft_free((void *) r.json);
if (r.bin) mod->ft_free((void *) r.bin);
}
}
} catch (...) {}
};
if (m_->ft_job_set_result_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_result_cb failed"); }
}
void FileTransferJob::on_result(ResultCb cb) { result_cb_ = std::move(cb); }
bool FileTransferJob::get_result(int &ec, int &resp_ec, std::string &json, std::vector<std::byte> &bin, uint32_t timeout_ms)
{
if (!h_) throw std::runtime_error("job handle invalid");
ft_job_result result;
if (m_->ft_job_get_result(h_, timeout_ms, &result) == ft_err::FT_EXCEPTION) return false;
solve_result(result);
m_->ft_job_result_destroy(&result);
ec = res_;
resp_ec = res_;
json = res_json_;
bin = res_bin_;
return true;
}
void FileTransferJob::start_on(FileTransferTunnel &t)
{
if (!h_) throw std::runtime_error("job handle invalid");
if (m_->ft_tunnel_start_job(t.native(), h_) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_tunnel_start_job failed"); }
}
void FileTransferJob::on_msg(MsgCb cb)
{
msg_cb_ = std::move(cb);
if (!h_) return;
// C API: ft_job_msg_cb(void* user, ft_job_msg msg)
auto tramp = [](void *user, ft_job_msg m) noexcept {
auto *self = reinterpret_cast<FileTransferJob *>(user);
if (!self) return;
try {
if (self->msg_cb_) { self->msg_cb_(m.kind, std::string(m.json ? m.json : "")); }
} catch (...) {}
try {
if (auto *mod = self->m_) {
if (mod->ft_job_msg_destroy)
mod->ft_job_msg_destroy(&m);
else if (mod->ft_free && m.json)
mod->ft_free((void *) m.json);
}
} catch (...) {}
};
if (m_->ft_job_set_msg_cb(h_, tramp, this) == ft_err::FT_EXCEPTION) { throw std::runtime_error("ft_job_set_msg_cb failed"); }
}
bool FileTransferJob::try_get_msg(int &kind, std::string &json)
{
if (!h_) return false;
ft_job_msg m{};
int rc = m_->ft_job_try_get_msg(h_, &m);
if (rc != 0) return false;
kind = m.kind;
json.assign(m.json ? m.json : "");
if (m_->ft_job_msg_destroy)
m_->ft_job_msg_destroy(&m);
else if (m_->ft_free && m.json)
m_->ft_free((void *) m.json);
return true;
}
bool FileTransferJob::get_msg(uint32_t timeout_ms, int &kind, std::string &json)
{
if (!h_) return false;
ft_job_msg m{};
int rc = m_->ft_job_get_msg(h_, timeout_ms, &m);
if (rc != 0) return false;
kind = m.kind;
json.assign(m.json ? m.json : "");
if (m_->ft_job_msg_destroy)
m_->ft_job_msg_destroy(&m);
else if (m_->ft_free && m.json)
m_->ft_free((void *) m.json);
return true;
}
void FileTransferJob::solve_result(ft_job_result result)
{
res_ = result.ec;
resp_ec_ = result.resp_ec;
res_bin_.clear();
if (result.bin && result.bin_size) res_bin_.assign(reinterpret_cast<const std::byte *>(result.bin),
reinterpret_cast<const std::byte *>(result.bin) + result.bin_size);
res_json_.assign(result.json ? result.json : "");
}
} // namespace Slic3r

View File

@@ -1,7 +1,13 @@
#pragma once
#include <string>
#include <functional>
#include <vector>
#include <cstddef>
#include <cstdint>
#include <stdexcept>
#include <utility>
#include <cstring>
#include <memory>
#include <boost/log/trivial.hpp>
#ifdef _WIN32
@@ -128,11 +134,103 @@ struct FileTransferModule
FileTransferModule &operator=(const FileTransferModule &) = delete;
};
// FileTransferTunnel/FileTransferJob (the OOP wrapper around the ft_tunnel_*/
// ft_job_* ABI below) live in BBLPrinterAgent.hpp as BBLFileTransferTunnel/
// BBLFileTransferJob, implementing IFileTransferTunnel/IFileTransferJob
// (IPrinterAgent.hpp) - this header stays the low-level symbol-table layer
// only, same role as bambu_networking.hpp's function pointer typedefs.
class FileTransferTunnel
{
public:
using ConnectionCb = std::function<void(bool is_success, int err_code, std::string error_msg)>;
using TunnelStatusCb = std::function<void(int old_status, int new_status, int err_code, std::string error_msg)>;
explicit FileTransferTunnel(FileTransferModule &m, const std::string &url);
~FileTransferTunnel() { reset(); }
FileTransferTunnel(const FileTransferTunnel &) = delete;
FileTransferTunnel &operator=(const FileTransferTunnel &) = delete;
FileTransferTunnel(FileTransferTunnel &&) = delete;
FileTransferTunnel &operator=(FileTransferTunnel &&) = delete;
void start_connect();
bool sync_start_connect();
void on_connection(ConnectionCb cb);
void on_status(TunnelStatusCb cb);
void shutdown();
int get_status() const { return status_; }
bool check_valid() const { return h_ != nullptr; }
FT_TunnelHandle *native() const noexcept { return h_; }
private:
void reset() noexcept
{
if (h_) {
m_->ft_tunnel_release(h_);
h_ = nullptr;
}
}
int status_{};
FileTransferModule *m_{};
FT_TunnelHandle *h_{};
ConnectionCb conn_cb_{};
TunnelStatusCb status_cb_{};
};
class FileTransferJob
{
public:
using ResultCb = std::function<void(int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res)>;
using MsgCb = std::function<void(int kind, std::string json)>;
explicit FileTransferJob(FileTransferModule &m, const std::string &params_json);
~FileTransferJob() { reset(); }
FileTransferJob(const FileTransferJob &) = delete;
FileTransferJob &operator=(const FileTransferJob &) = delete;
FileTransferJob(FileTransferJob &&) = delete;
FileTransferJob &operator=(FileTransferJob &&) = delete;
void on_result(ResultCb cb);
bool get_result(int &ec, int &resp_ec, std::string &json, std::vector<std::byte> &bin, uint32_t timeout_ms);
void start_on(FileTransferTunnel &t);
void on_msg(MsgCb cb);
bool try_get_msg(int &kind, std::string &json);
bool get_msg(uint32_t timeout_ms, int &kind, std::string &json);
FT_JobHandle *native() const noexcept { return h_; }
bool check_valid() const { return h_ != nullptr; }
bool finished() const { return finished_; }
void cancel()
{
if (m_->ft_job_cancel && h_) m_->ft_job_cancel(h_);
}
private:
void reset() noexcept
{
if (h_) {
m_->ft_job_release(h_);
h_ = nullptr;
}
}
void solve_result(ft_job_result result);
FileTransferModule *m_{};
FT_JobHandle *h_{};
ResultCb result_cb_{};
MsgCb msg_cb_{};
bool finished_ = false;
int res_ = 0;
int resp_ec_ = 0;
std::string res_json_;
std::vector<std::byte> res_bin_;
};
namespace detail {
inline FileTransferModule *g_mod = nullptr;
@@ -154,4 +252,4 @@ inline FileTransferModule &module()
return *detail::g_mod;
}
} // namespace Slic3r
} // namespace Slic3r

View File

@@ -10,7 +10,6 @@
// -70xx is free: the vendor occupies -1..-25 and -10xx through -60xx.
#define ORCA_NETWORK_ERR_CMD_NOT_SUPPORTED -7010 // no translation exists for this command
#define ORCA_NETWORK_ERR_CAP_NOT_AVAILABLE -7020 // a translation exists; this printer lacks the capability
#define ORCA_NETWORK_ERR_ACCESS_VERIFICATION_FAILED -7030 // printer access preflight failed before printing
#include <string>
#include <memory>
#include <vector>
@@ -50,77 +49,6 @@ enum class FilamentSyncMode {
pull ///< On-demand fetch via REST API (blocking call)
};
class IFileTransferTunnel
{
public:
using ConnectionCb = std::function<void(bool is_success, int err_code, std::string error_msg)>;
using TunnelStatusCb = std::function<void(int old_status, int new_status, int err_code, std::string error_msg)>;
explicit IFileTransferTunnel(const std::string& url) : url_(url) {}
virtual ~IFileTransferTunnel() = default;
IFileTransferTunnel(const IFileTransferTunnel&) = delete;
IFileTransferTunnel& operator=(const IFileTransferTunnel&) = delete;
IFileTransferTunnel(IFileTransferTunnel&&) = delete;
IFileTransferTunnel& operator=(IFileTransferTunnel&&) = delete;
virtual void start_connect() = 0;
virtual bool sync_start_connect() = 0;
virtual void on_connection(ConnectionCb cb) { conn_cb_ = std::move(cb); }
virtual void on_status(TunnelStatusCb cb) { status_cb_ = std::move(cb); }
virtual void shutdown() = 0;
virtual int get_status() const { return status_; }
virtual bool check_valid() const = 0;
// why: IFileTransferJob::start_on() only ever sees a tunnel through this interface,
// but needs the concrete backend handle to hand to its own start-job call - native()
// is the type-erased escape hatch, same pattern IFileTransferJob::native() already uses.
virtual void *native() const noexcept { return nullptr; }
protected:
std::string url_;
int status_{};
ConnectionCb conn_cb_{};
TunnelStatusCb status_cb_{};
};
class IFileTransferJob {
public:
using ResultCb = std::function<void(int res, int resp_ec, std::string json_res, std::vector<std::byte> bin_res)>;
using MsgCb = std::function<void(int kind, std::string json)>;
explicit IFileTransferJob(const std::string &params_json) : params_json_(params_json) {}
virtual ~IFileTransferJob() = default;
IFileTransferJob(const IFileTransferJob &) = delete;
IFileTransferJob &operator=(const IFileTransferJob &) = delete;
IFileTransferJob(IFileTransferJob &&) = delete;
IFileTransferJob &operator=(IFileTransferJob &&) = delete;
virtual void on_result(ResultCb cb) { result_cb_ = std::move(cb); }
virtual bool get_result(int &ec, int &resp_ec, std::string &json, std::vector<std::byte> &bin, uint32_t timeout_ms) = 0;
virtual void start_on(IFileTransferTunnel &t) = 0;
virtual void on_msg(MsgCb cb) { msg_cb_ = std::move(cb); }
virtual bool try_get_msg(int &kind, std::string &json) = 0;
virtual bool get_msg(uint32_t timeout_ms, int &kind, std::string &json) = 0;
virtual void *native() const noexcept { return nullptr; }
virtual bool check_valid() const = 0;
virtual bool finished() const { return finished_; }
virtual void cancel() = 0;
protected:
std::string params_json_;
ResultCb result_cb_{};
MsgCb msg_cb_{};
bool finished_ = false;
int res_ = 0;
int resp_ec_ = 0;
std::string res_json_;
std::vector<std::byte> res_bin_;
};
/**
* IPrinterAgent - Interface for printer operations.
*
@@ -425,55 +353,6 @@ public:
*/
virtual bool fetch_filament_info(std::string dev_id) { return false; }
struct FileTransferRequest
{
std::string device_id;
std::string device_ip;
std::string access_code;
std::string network_version;
std::string device_version;
std::string refresh_url;
std::string client_id;
std::string client_version;
bool lan_mode{false};
};
struct FileTransferCallbacks
{
std::function<void(bool is_success, int error_code, std::string error_msg)> on_connection;
std::function<void(int result, int response_error, std::string json_result)> on_destinations;
std::function<void(int progress)> on_progress;
std::function<void(int result, int response_error, std::string json_result, std::vector<std::byte> binary_result)> on_result;
std::function<void()> file_transfer_error;
};
/**
* Prepare the agent's file-transfer session. The transport is agent-owned;
* callers must not need to know whether it is a tunnel, HTTP connection,
* or another protocol.
*/
virtual void prepare_file_transfer(const FileTransferRequest&, FileTransferCallbacks cb)
{
if (cb.file_transfer_error)
cb.file_transfer_error();
}
/** Query the destinations available for the prepared transfer session. */
virtual void get_file_destinations(FileTransferCallbacks cb)
{
if (cb.file_transfer_error)
cb.file_transfer_error();
}
/** Upload a file using the prepared transfer session. */
virtual void upload_file(const std::string&, const std::string&, const std::string&, FileTransferCallbacks cb)
{
if (cb.file_transfer_error)
cb.file_transfer_error();
}
/** Cancel the current file-transfer operation and release its resources. */
virtual void cancel_file_transfer() {}
};
} // namespace Slic3r