Files
OrcaSlicer/src/slic3r/Utils/OrcaCloudServiceAgent.cpp
2026-01-26 21:11:52 +08:00

2595 lines
87 KiB
C++

#include "OrcaCloudServiceAgent.hpp"
#include "Http.hpp"
#include "slic3r/Utils/InstanceID.hpp"
#include "libslic3r/Utils.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include "libslic3r/AppConfig.hpp"
#include <boost/asio.hpp>
#include <boost/beast/core/detail/base64.hpp>
#include <boost/filesystem.hpp>
#include <boost/log/trivial.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#include <openssl/sha.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <random>
#include <sstream>
#include <wx/filename.h>
#include <wx/filefn.h>
#include <wx/secretstore.h>
#include <wx/stdpaths.h>
#include <wx/utils.h>
#if defined(_WIN32)
#include <Windows.h>
#endif
#if defined(__APPLE__)
#include <sys/sysctl.h>
#endif
namespace pt = boost::property_tree;
namespace Slic3r {
namespace {
constexpr const char* ORCA_DEFAULT_API_URL = "https://xxx.orcaslicer.com";
constexpr const char* ORCA_DEFAULT_AUTH_URL = "https://xxx.orcaslicer.com";
constexpr const char* ORCA_DEFAULT_PUB_KEY = "xxxxxxxxxxxxx";
constexpr const char* ORCA_HEALTH_PATH = "/api/v1/health";
constexpr const char* ORCA_SYNC_PULL_PATH = "/api/v1/sync/pull";
constexpr const char* ORCA_SYNC_PUSH_PATH = "/api/v1/sync/push";
constexpr const char* ORCA_PROFILES_PATH = "/api/v1/profiles";
constexpr const char* ORCA_SYNC_STATE_FILE = "sync_state";
constexpr const char* CONFIG_ORCA_API_URL = "orca_api_url";
constexpr const char* CONFIG_ORCA_AUTH_URL = "orca_auth_url";
constexpr const char* CONFIG_ORCA_PUB_KEY = "orca_pub_key";
constexpr const char* SECRET_STORE_SERVICE = "OrcaSlicer/Auth";
constexpr const char* SECRET_STORE_USER = "orca_refresh_token";
constexpr std::chrono::seconds TOKEN_REFRESH_SKEW{900}; // 15 minutes
std::string generate_uuid(const std::string& name = "")
{
if (name.empty()) {
return "";
}
// Use a fixed namespace UUID for OrcaSlicer profiles
// This ensures the same name always generates the same UUID
static const boost::uuids::uuid orca_namespace =
boost::uuids::string_generator()("f47ac10b-58cc-4372-a567-0e02b2c3d479");
boost::uuids::name_generator_sha1 gen(orca_namespace);
boost::uuids::uuid id = gen(name);
return boost::uuids::to_string(id);
}
std::string base64url_encode(const std::vector<unsigned char>& data)
{
std::string out;
out.resize(boost::beast::detail::base64::encoded_size(data.size()));
out.resize(boost::beast::detail::base64::encode(out.data(), data.data(), data.size()));
std::replace(out.begin(), out.end(), '+', '-');
std::replace(out.begin(), out.end(), '/', '_');
out.erase(std::remove(out.begin(), out.end(), '='), out.end());
return out;
}
bool base64url_decode(const std::string& input, std::vector<unsigned char>& out)
{
std::string padded = input;
while (padded.size() % 4 != 0) padded.push_back('=');
std::string normalized = padded;
std::replace(normalized.begin(), normalized.end(), '-', '+');
std::replace(normalized.begin(), normalized.end(), '_', '/');
out.resize(boost::beast::detail::base64::decoded_size(normalized.size()));
auto res = boost::beast::detail::base64::decode(out.data(), normalized.data(), normalized.size());
if (!res.second) return false;
out.resize(res.first);
return true;
}
std::vector<unsigned char> random_bytes(size_t len)
{
std::vector<unsigned char> bytes(len);
if (RAND_bytes(bytes.data(), static_cast<int>(len)) != 1) {
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(0, 255);
for (auto& b : bytes) b = static_cast<unsigned char>(dist(gen));
}
return bytes;
}
std::string generate_code_verifier()
{
constexpr int PKCE_VERIFIER_BYTES = 32;
auto bytes = random_bytes(PKCE_VERIFIER_BYTES);
return base64url_encode(bytes);
}
std::string generate_state_token()
{
auto bytes = random_bytes(16);
std::stringstream ss;
for (auto b : bytes) {
ss << std::hex << std::setw(2) << std::setfill('0') << static_cast<int>(b);
}
return ss.str();
}
std::string sha256_base64url(const std::string& input)
{
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(reinterpret_cast<const unsigned char*>(input.data()), input.size(), hash);
std::vector<unsigned char> hash_vec(hash, hash + sizeof(hash));
return base64url_encode(hash_vec);
}
std::string machine_identifier()
{
if (auto* cfg = Slic3r::GUI::wxGetApp().app_config) {
const auto iid = Slic3r::instance_id::ensure(*cfg);
if (!iid.empty()) return iid;
}
#if defined(__linux__)
std::ifstream f("/etc/machine-id");
std::string id;
if (f.good()) {
std::getline(f, id);
}
if (!id.empty()) return id;
#elif defined(_WIN32)
char buffer[MAX_COMPUTERNAME_LENGTH + 1] = {0};
DWORD size = MAX_COMPUTERNAME_LENGTH + 1;
if (GetComputerNameA(buffer, &size)) {
return std::string(buffer, size);
}
#elif defined(__APPLE__)
char uuid_str[128] = {0};
size_t len = sizeof(uuid_str);
if (sysctlbyname("kern.uuid", uuid_str, &len, nullptr, 0) == 0 && len > 0) {
return std::string(uuid_str, len - 1);
}
#endif
return wxGetUserId().ToStdString() + "@" + wxGetHostName().ToStdString();
}
std::vector<unsigned char> sha256_bytes(const std::string& input)
{
std::vector<unsigned char> out(SHA256_DIGEST_LENGTH, 0);
SHA256(reinterpret_cast<const unsigned char*>(input.data()), input.size(), out.data());
return out;
}
std::string hmac_sha256_hex(const std::string& data, const std::vector<unsigned char>& key)
{
unsigned int len = 0;
unsigned char result[EVP_MAX_MD_SIZE];
if (HMAC(EVP_sha256(), key.data(), static_cast<int>(key.size()),
reinterpret_cast<const unsigned char*>(data.data()), data.size(), result, &len) == nullptr) {
return {};
}
std::ostringstream oss;
oss << std::hex << std::setfill('0');
for (unsigned int i = 0; i < len; ++i) {
oss << std::setw(2) << static_cast<int>(result[i]);
}
return oss.str();
}
bool is_port_available(int port)
{
if (port <= 0 || port > 65535) return false;
using boost::asio::ip::tcp;
boost::asio::io_context ctx;
boost::system::error_code ec;
tcp::acceptor acceptor(ctx);
tcp::endpoint endpoint(tcp::v4(), static_cast<unsigned short>(port));
acceptor.open(endpoint.protocol(), ec);
if (ec) return false;
acceptor.set_option(tcp::acceptor::reuse_address(true), ec);
if (ec) return false;
acceptor.bind(endpoint, ec);
if (ec) return false;
acceptor.close(ec);
return true;
}
int choose_loopback_port()
{
int base_port = auth_constants::LOOPBACK_PORT;
if (const char* env_port = std::getenv("ORCA_LOOPBACK_PORT")) {
try {
int parsed = std::stoi(env_port);
if (parsed > 0 && parsed <= 65535) {
base_port = parsed;
}
} catch (...) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: invalid ORCA_LOOPBACK_PORT value, falling back to default";
}
}
std::vector<int> candidates = {base_port, base_port + 1, base_port + 2};
for (int port : candidates) {
if (is_port_available(port)) return port;
}
return base_port;
}
bool aes256gcm_encrypt(const std::string& plaintext, const std::vector<unsigned char>& key, std::string& out_b64)
{
const int iv_len = 12;
auto iv = random_bytes(iv_len);
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return false;
bool ok = true;
int len = 0;
std::vector<unsigned char> ciphertext(plaintext.size());
std::vector<unsigned char> tag(16);
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) ok = false;
if (ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr) != 1) ok = false;
if (ok && EVP_EncryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) ok = false;
if (ok && EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
reinterpret_cast<const unsigned char*>(plaintext.data()), plaintext.size()) != 1) ok = false;
int ciphertext_len = len;
if (ok && EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len) != 1) ok = false;
ciphertext_len += len;
if (ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag.size(), tag.data()) != 1) ok = false;
EVP_CIPHER_CTX_free(ctx);
if (!ok) return false;
ciphertext.resize(ciphertext_len);
std::vector<unsigned char> payload;
payload.reserve(iv.size() + tag.size() + ciphertext.size());
payload.insert(payload.end(), iv.begin(), iv.end());
payload.insert(payload.end(), tag.begin(), tag.end());
payload.insert(payload.end(), ciphertext.begin(), ciphertext.end());
out_b64 = base64url_encode(payload);
return true;
}
bool aes256gcm_decrypt(const std::string& b64_payload, const std::vector<unsigned char>& key, std::string& plaintext)
{
std::vector<unsigned char> payload;
if (!base64url_decode(b64_payload, payload)) return false;
if (payload.size() < 12 + 16) return false;
const size_t iv_len = 12;
const size_t tag_len = 16;
std::vector<unsigned char> iv(payload.begin(), payload.begin() + iv_len);
std::vector<unsigned char> tag(payload.begin() + iv_len, payload.begin() + iv_len + tag_len);
std::vector<unsigned char> ciphertext(payload.begin() + iv_len + tag_len, payload.end());
EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
if (!ctx) return false;
bool ok = true;
int len = 0;
std::vector<unsigned char> plain(ciphertext.size());
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) != 1) ok = false;
if (ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr) != 1) ok = false;
if (ok && EVP_DecryptInit_ex(ctx, nullptr, nullptr, key.data(), iv.data()) != 1) ok = false;
if (ok && EVP_DecryptUpdate(ctx, plain.data(), &len, ciphertext.data(), ciphertext.size()) != 1) ok = false;
int plain_len = len;
if (ok && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag.size(), tag.data()) != 1) ok = false;
if (ok && EVP_DecryptFinal_ex(ctx, plain.data() + len, &len) != 1) ok = false;
plain_len += len;
EVP_CIPHER_CTX_free(ctx);
if (!ok) return false;
plain.resize(plain_len);
plaintext.assign(reinterpret_cast<char*>(plain.data()), plain.size());
return true;
}
} // namespace
// ============================================================================
// Constructor / Destructor
// ============================================================================
OrcaCloudServiceAgent::OrcaCloudServiceAgent(std::string log_dir)
: log_dir(std::move(log_dir))
, api_base_url(ORCA_DEFAULT_API_URL)
, auth_base_url(ORCA_DEFAULT_AUTH_URL)
{
auth_headers["apikey"] = ORCA_DEFAULT_PUB_KEY;
pkce_bundle.loopback_port = choose_loopback_port();
update_redirect_uri();
regenerate_pkce();
compute_fallback_path();
}
OrcaCloudServiceAgent::~OrcaCloudServiceAgent()
{
if (refresh_thread.joinable()) {
refresh_thread.join();
}
}
void OrcaCloudServiceAgent::configure_urls(AppConfig* app_config)
{
if (!app_config) return;
// Read token storage preference
m_use_encrypted_token_file = app_config->get_bool(SETTING_USE_ENCRYPTED_TOKEN_FILE);
std::string api_url = app_config->get(CONFIG_ORCA_API_URL);
if (!api_url.empty()) {
api_base_url = api_url;
}
std::string auth_url = app_config->get(CONFIG_ORCA_AUTH_URL);
if (!auth_url.empty()) {
auth_base_url = auth_url;
}
std::string pub_key = app_config->get(CONFIG_ORCA_PUB_KEY);
if (!pub_key.empty()) {
auth_headers["apikey"] = pub_key;
}
}
void OrcaCloudServiceAgent::set_api_base_url(const std::string& url)
{
api_base_url = url;
}
void OrcaCloudServiceAgent::set_auth_base_url(const std::string& url)
{
auth_base_url = url;
}
void OrcaCloudServiceAgent::set_use_encrypted_token_file(bool use)
{
m_use_encrypted_token_file = use;
}
bool OrcaCloudServiceAgent::get_use_encrypted_token_file() const
{
return m_use_encrypted_token_file;
}
// ============================================================================
// ICloudServiceAgent - Lifecycle Methods
// ============================================================================
int OrcaCloudServiceAgent::init_log()
{
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_config_dir(std::string cfg_dir)
{
config_dir = cfg_dir;
wxFileName fallback(wxString::FromUTF8(cfg_dir.c_str()), "orca_refresh_token.sec");
fallback.Normalize();
refresh_fallback_path = fallback.GetFullPath().ToStdString();
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_cert_file(std::string folder, std::string filename)
{
// Not used by OrcaCloudServiceAgent (OAuth doesn't need client certs)
(void) folder;
(void) filename;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_country_code(std::string code)
{
country_code = code;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::start()
{
regenerate_pkce();
// Attempt silent sign-in from stored refresh token
std::string stored_refresh;
if (load_refresh_token(stored_refresh) && !stored_refresh.empty()) {
refresh_now(stored_refresh, "refresh token", false);
}
return BAMBU_NETWORK_SUCCESS;
}
// ============================================================================
// ICloudServiceAgent - User Session Management
// ============================================================================
bool OrcaCloudServiceAgent::exchange_auth_code(const std::string& auth_code, const std::string& state, std::string& session_payload)
{
// Validate PKCE state
const auto expected_state = pkce_bundle.state;
if (!expected_state.empty() && state != expected_state) {
BOOST_LOG_TRIVIAL(warning) << "[auth] event=code_exchange result=failure reason=state_mismatch";
return false;
}
std::string url = auth_base_url + auth_constants::TOKEN_PATH;
std::string redirect_uri;
std::string code_verifier;
std::map<std::string, std::string> headers_copy;
{
std::lock_guard<std::mutex> lock(headers_mutex);
headers_copy = extra_headers;
}
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
redirect_uri = pkce_bundle.redirect;
code_verifier = pkce_bundle.verifier;
for (const auto& pair : auth_headers) {
headers_copy[pair.first] = pair.second;
}
}
bool has_apikey = false;
for (const auto& pair : headers_copy) {
if (pair.first == "apikey") {
has_apikey = true;
break;
}
}
if (!has_apikey) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: exchange_auth_code - apikey header MISSING! Token request may fail.";
}
std::string response;
unsigned int http_code = 0;
bool success = false;
try {
auto http = Http::post(url);
for (const auto& pair : headers_copy) {
http.header(pair.first, pair.second);
}
http.remove_header("Authorization");
http.remove_header("Content-Type");
http.form_add("grant_type", "authorization_code");
http.form_add("code", auth_code);
http.form_add("redirect_uri", redirect_uri);
http.form_add("code_verifier", code_verifier);
http.on_complete([&](std::string body, unsigned resp_status) {
success = true;
http_code = resp_status;
response = body;
})
.on_error([&](std::string body, std::string error, unsigned resp_status) {
success = false;
http_code = resp_status;
response = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
})
.timeout_max(30)
.perform_sync();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: exchange_auth_code exception - " << e.what();
}
if (!success || http_code >= 400) {
BOOST_LOG_TRIVIAL(error) << "[auth] event=code_exchange result=failure http_code=" << http_code;
return false;
}
session_payload = response;
BOOST_LOG_TRIVIAL(info) << "[auth] event=code_exchange result=success";
return true;
}
int OrcaCloudServiceAgent::change_user(std::string user_info)
{
try {
std::stringstream ss(user_info);
pt::ptree tree;
pt::read_json(ss, tree);
auto read_str = [](const pt::ptree& node, const std::string& path) {
return node.get<std::string>(path, "");
};
// Check if this is a WebView login message (PKCE flow completion)
std::string command = tree.get<std::string>("command", "");
if (command == "user_login") {
auto data_opt = tree.get_child_optional("data");
if (!data_opt) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: WebView login payload missing data field";
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
pt::ptree data = *data_opt;
std::string state = read_str(data, "state");
// Check for auth code (PKCE authorization code flow)
std::string auth_code = read_str(data, "code");
if (!auth_code.empty()) {
std::string session_payload;
if (!exchange_auth_code(auth_code, state, session_payload)) {
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
// Recursively process the session payload (contains access_token, user, etc.)
return change_user(session_payload);
}
// Direct token flow (tokens already obtained by WebView)
std::string token = read_str(data, "token");
std::string user_id = read_str(data, "user_id");
std::string username = read_str(data, "username");
std::string name = read_str(data, "name");
std::string nickname = read_str(data, "nickname");
std::string avatar = read_str(data, "avatar");
std::string refresh_token = read_str(data, "refresh_token");
// Validate PKCE state
const auto expected_state = pkce_bundle.state;
if (!expected_state.empty() && state != expected_state) {
BOOST_LOG_TRIVIAL(warning) << "[auth] event=login result=failure reason=state_mismatch";
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
if (token.empty() || user_id.empty()) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: WebView login - token or user_id empty";
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
bool success = set_user_session(token, user_id, username, name, nickname, avatar, refresh_token);
if (success) {
invoke_user_login_callback(1, true);
if (on_login_complete_handler) {
on_login_complete_handler(true, user_id);
}
} else {
invoke_user_login_callback(0, false);
}
BOOST_LOG_TRIVIAL(info) << "[auth] event=login result=" << (success ? "success" : "failure")
<< " source=webview user_id=" << user_id;
return success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
// Orca cloud session payload (default flow)
const pt::ptree* session_node = nullptr;
auto data_opt = tree.get_child_optional("data");
if (data_opt) {
if (data_opt->get_child_optional("session")) {
session_node = &data_opt->get_child("session");
} else if (data_opt->get_optional<std::string>("access_token") ||
data_opt->get_optional<std::string>("token")) {
session_node = &*data_opt;
}
}
if (!session_node) {
if (tree.get_child_optional("session")) {
session_node = &tree.get_child("session");
} else if (tree.get_optional<std::string>("access_token") ||
tree.get_optional<std::string>("token")) {
session_node = &tree;
}
}
if (session_node) {
std::string access_token = read_str(*session_node, "access_token");
if (access_token.empty()) {
access_token = read_str(*session_node, "token");
}
std::string refresh_token = read_str(*session_node, "refresh_token");
std::string user_id = read_str(*session_node, "user.id");
std::string email = read_str(*session_node, "user.email");
std::string full_name = read_str(*session_node, "user.user_metadata.full_name");
std::string preferred_username = read_str(*session_node, "user.user_metadata.preferred_username");
std::string avatar = read_str(*session_node, "user.user_metadata.avatar_url");
std::string username = !preferred_username.empty() ? preferred_username : email;
std::string name = !full_name.empty() ? full_name : (!preferred_username.empty() ? preferred_username : email);
std::string nickname = !preferred_username.empty() ? preferred_username : username;
if (nickname.empty()) nickname = name;
if (nickname.empty()) nickname = email;
if (access_token.empty() || user_id.empty()) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: Orca cloud login payload missing access_token or user.id";
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
bool success = set_user_session(access_token, user_id, username, name, nickname, avatar, refresh_token);
if (success) {
invoke_user_login_callback(1, true);
if (on_login_complete_handler) {
on_login_complete_handler(true, user_id);
}
} else {
invoke_user_login_callback(0, false);
}
return success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: Username/password login is disabled. Use the Orca cloud PKCE flow.";
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: change_user exception - " << e.what();
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_ERR_INVALID_RESULT;
}
}
bool OrcaCloudServiceAgent::is_user_login()
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.logged_in;
}
int OrcaCloudServiceAgent::user_logout(bool request)
{
// Send logout request to backend if requested
if (request) {
std::string token;
std::string refresh_copy;
{
std::lock_guard<std::mutex> lock(session_mutex);
token = session.access_token;
refresh_copy = session.refresh_token;
}
if (!token.empty()) {
std::string response;
unsigned int http_code = 0;
pt::ptree logout_req;
if (!refresh_copy.empty()) {
logout_req.put("refresh_token", refresh_copy);
}
std::stringstream body_ss;
if (!logout_req.empty()) {
pt::write_json(body_ss, logout_req);
} else {
body_ss << "{}";
}
int result = http_post_auth(auth_constants::LOGOUT_PATH, body_ss.str(), &response, &http_code) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_INVALID_HANDLE;
if (result != BAMBU_NETWORK_SUCCESS || http_code >= 400) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: Orca cloud logout request failed - http_code=" << http_code;
}
}
}
clear_session();
invoke_user_login_callback(0, false);
return BAMBU_NETWORK_SUCCESS;
}
std::string OrcaCloudServiceAgent::get_user_id()
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.user_id;
}
std::string OrcaCloudServiceAgent::get_user_name()
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.user_name;
}
std::string OrcaCloudServiceAgent::get_user_avatar()
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.user_avatar;
}
std::string OrcaCloudServiceAgent::get_user_nickname()
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.user_nickname;
}
// ============================================================================
// ICloudServiceAgent - Login UI Support
// ============================================================================
std::string OrcaCloudServiceAgent::build_login_cmd()
{
// When already signed in, emit the homepage payload so the web UI
// can flip to the logged-in state without re-opening the login flow.
if (is_user_login()) {
pt::ptree cmd;
cmd.put("command", "studio_userlogin");
pt::ptree data;
std::string display_name = get_user_nickname();
if (display_name.empty()) {
display_name = get_user_name();
}
data.put("name", display_name);
data.put("avatar", get_user_avatar());
cmd.add_child("data", data);
std::stringstream ss;
pt::write_json(ss, cmd, false);
return ss.str();
}
update_redirect_uri();
regenerate_pkce();
const auto bundle = pkce();
pt::ptree tree;
tree.put("action", "login_config");
tree.put("backend_url", auth_base_url);
// Include API key for direct Supabase calls from JavaScript
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
auto it = auth_headers.find("apikey");
if (it != auth_headers.end()) {
tree.put("apikey", it->second);
}
}
pt::ptree pkce_node;
pkce_node.put("code_challenge", bundle.challenge);
pkce_node.put("code_challenge_method", "S256");
pkce_node.put("state", bundle.state);
pkce_node.put("redirect_uri", bundle.redirect);
pkce_node.put("code_verifier", bundle.verifier);
pkce_node.put("loopback_port", bundle.loopback_port);
tree.add_child("pkce", pkce_node);
std::stringstream ss;
pt::write_json(ss, tree, false);
return ss.str();
}
std::string OrcaCloudServiceAgent::build_logout_cmd()
{
pt::ptree tree;
tree.put("action", "logout");
tree.put("provider", "orca");
std::stringstream ss;
pt::write_json(ss, tree);
return ss.str();
}
std::string OrcaCloudServiceAgent::build_login_info()
{
pt::ptree tree;
{
std::lock_guard<std::mutex> lock(session_mutex);
tree.put("user_id", session.user_id);
tree.put("user_name", session.user_name);
tree.put("nickname", session.user_nickname);
tree.put("avatar", session.user_avatar);
tree.put("logged_in", session.logged_in);
}
// Do not expose tokens to the WebView
tree.put("access_token", "");
tree.put("refresh_token", "");
tree.put("backend_url", api_base_url);
tree.put("auth_url", auth_base_url);
std::stringstream ss;
pt::write_json(ss, tree);
return ss.str();
}
// ============================================================================
// ICloudServiceAgent - Token Access
// ============================================================================
std::string OrcaCloudServiceAgent::get_access_token() const
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.access_token;
}
std::string OrcaCloudServiceAgent::get_refresh_token() const
{
std::lock_guard<std::mutex> lock(session_mutex);
return session.refresh_token;
}
bool OrcaCloudServiceAgent::ensure_token_fresh(const std::string& reason)
{
return refresh_if_expiring(TOKEN_REFRESH_SKEW, reason);
}
// ============================================================================
// ICloudServiceAgent - Server Connectivity
// ============================================================================
int OrcaCloudServiceAgent::connect_server()
{
std::string response;
unsigned int http_code = 0;
int result = http_get(ORCA_HEALTH_PATH, &response, &http_code);
bool connected = (result == BAMBU_NETWORK_SUCCESS && http_code >= 200 && http_code < 300);
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
is_connected = connected;
}
invoke_server_connected_callback(connected ? 0 : -1, http_code);
return connected ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED;
}
bool OrcaCloudServiceAgent::is_server_connected()
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
return is_connected;
}
int OrcaCloudServiceAgent::refresh_connection()
{
return connect_server();
}
int OrcaCloudServiceAgent::start_subscribe(std::string module)
{
(void) module;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::stop_subscribe(std::string module)
{
(void) module;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::add_subscribe(std::vector<std::string> dev_list)
{
(void) dev_list;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::del_subscribe(std::vector<std::string> dev_list)
{
(void) dev_list;
return BAMBU_NETWORK_SUCCESS;
}
void OrcaCloudServiceAgent::enable_multi_machine(bool enable)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
multi_machine_enabled = enable;
}
// ============================================================================
// ICloudServiceAgent - Settings Synchronization
// ============================================================================
int OrcaCloudServiceAgent::get_user_presets(std::map<std::string, std::map<std::string, std::string>>* user_presets)
{
if (!user_presets) return BAMBU_NETWORK_ERR_INVALID_HANDLE;
if (!is_user_login()) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: Not logged in";
return BAMBU_NETWORK_ERR_INVALID_HANDLE;
}
bool success = false;
std::string error_msg;
int http_code_out = 0;
SyncState saved_state;
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
saved_state = sync_state;
sync_state = SyncState{};
}
auto on_success = [&](const SyncPullResponse& resp) {
for (const auto& upsert : resp.upserts) {
std::string preset_type = IOT_PRINT_TYPE_STRING;
if (upsert.content.contains("type") && upsert.content["type"].is_string()) {
preset_type = upsert.content["type"].get<std::string>();
}
(*user_presets)[preset_type][upsert.id] = upsert.content.dump();
}
if (!resp.next_cursor.empty()) {
std::lock_guard<std::recursive_mutex> lock(state_mutex);
sync_state.last_sync_timestamp = resp.next_cursor;
save_sync_state();
} else {
std::lock_guard<std::recursive_mutex> lock(state_mutex);
sync_state = saved_state;
}
success = true;
};
auto on_error = [&](int code, const std::string& err) {
http_code_out = code;
error_msg = err;
success = false;
};
sync_pull(on_success, on_error);
if (!success) {
std::lock_guard<std::recursive_mutex> lock(state_mutex);
sync_state = saved_state;
}
return success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
}
std::string OrcaCloudServiceAgent::request_setting_id(std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
{
std::string new_id = generate_uuid(name);
if (new_id.empty()) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: request_setting_id failed - name is empty";
return "";
}
nlohmann::json content;
content["name"] = name;
content["type"] = IOT_PRINT_TYPE_STRING; // Default type
if (values_map && !values_map->empty()) {
for (const auto& pair : *values_map) {
// Skip updated_time - it's metadata, not content
if (pair.first == IOT_JSON_KEY_UPDATED_TIME) continue;
content[pair.first] = pair.second;
}
}
// Use sync_push to create the profile (no original_updated_at for new profiles per spec)
auto result = sync_push(new_id, name, content, "");
if (http_code) *http_code = result.http_code;
if (result.success) {
if (values_map && !result.new_updated_at.empty()) {
(*values_map)[IOT_JSON_KEY_UPDATED_TIME] = result.new_updated_at;
}
return new_id;
}
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: request_setting_id failed - " << result.error_message;
return "";
}
int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map<std::string, std::string>* values_map, unsigned int* http_code)
{
// Extract original_updated_at for Optimistic Concurrency Control
// If present, server will verify version before update. If absent, treated as insert.
// If present, server will verify version before update. If absent, treated as insert.
std::string original_updated_at;
if (values_map) {
auto it = values_map->find(IOT_JSON_KEY_UPDATED_TIME);
if (it != values_map->end()) {
original_updated_at = it->second;
}
}
// Build content JSON
nlohmann::json content;
content["name"] = name;
if (values_map && !values_map->empty()) {
for (const auto& pair : *values_map) {
// Skip updated_time - it's used for OCC, not as content
if (pair.first == IOT_JSON_KEY_UPDATED_TIME) continue;
content[pair.first] = pair.second;
}
}
auto result = sync_push(setting_id, name, content, original_updated_at);
if (http_code) *http_code = result.http_code;
if (result.success) {
if (values_map && !result.new_updated_at.empty()) {
(*values_map)[IOT_JSON_KEY_UPDATED_TIME] = result.new_updated_at;
}
return BAMBU_NETWORK_SUCCESS;
}
if (result.http_code == 409) {
// Conflict - update values_map with server version
if (values_map && !result.server_version.updated_at.empty()) {
(*values_map)[IOT_JSON_KEY_UPDATED_TIME] = result.server_version.updated_at;
}
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: put_setting conflict - server_updated_at="
<< result.server_version.updated_at;
}
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: put_setting failed - " << result.error_message;
return BAMBU_NETWORK_ERR_PUT_SETTING_FAILED;
}
int OrcaCloudServiceAgent::get_setting_list(std::string bundle_version, ProgressFn pro_fn, WasCancelledFn cancel_fn)
{
return get_setting_list2(bundle_version, nullptr, pro_fn, cancel_fn);
}
int OrcaCloudServiceAgent::get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn, WasCancelledFn cancel_fn)
{
(void) bundle_version;
bool success = false;
int error_code = 0;
auto on_success = [&](const SyncPullResponse& resp) {
int total = static_cast<int>(resp.upserts.size() + resp.deletes.size());
int processed = 0;
bool cancelled = false;
for (const auto& upsert : resp.upserts) {
if (cancel_fn && cancel_fn()) {
cancelled = true;
break;
}
if (chk_fn) {
std::map<std::string, std::string> info;
info[IOT_JSON_KEY_SETTING_ID] = upsert.id;
info[IOT_JSON_KEY_UPDATED_TIME] = upsert.updated_at;
if (upsert.content.is_object()) {
for (auto& [key, value] : upsert.content.items()) {
if (value.is_string()) {
info[key] = value.get<std::string>();
} else {
info[key] = value.dump();
}
}
}
if (!info.count(IOT_JSON_KEY_NAME) && !upsert.name.empty()) {
info[IOT_JSON_KEY_NAME] = upsert.name;
}
if (!info.count(IOT_JSON_KEY_TYPE)) {
info[IOT_JSON_KEY_TYPE] = IOT_PRINT_TYPE_STRING;
}
chk_fn(info);
}
if (pro_fn) {
int progress = total > 0 ? (processed * 100 / total) : 100;
pro_fn(progress);
}
processed++;
}
if (!cancelled) {
for (const auto& deleted_id : resp.deletes) {
if (cancel_fn && cancel_fn()) {
cancelled = true;
break;
}
if (chk_fn) {
std::map<std::string, std::string> info;
info[IOT_JSON_KEY_SETTING_ID] = deleted_id;
info["deleted"] = "true";
chk_fn(info);
}
if (pro_fn) {
int progress = total > 0 ? (processed * 100 / total) : 100;
pro_fn(progress);
}
processed++;
}
}
if (!cancelled && !resp.next_cursor.empty()) {
std::lock_guard<std::recursive_mutex> lock(state_mutex);
sync_state.last_sync_timestamp = resp.next_cursor;
save_sync_state();
}
if (pro_fn) {
pro_fn(100);
}
success = !cancelled;
};
auto on_error = [&](int code, const std::string& err) {
error_code = code;
success = false;
};
sync_pull(on_success, on_error);
return success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
}
int OrcaCloudServiceAgent::delete_setting(std::string setting_id)
{
std::string path = std::string(ORCA_PROFILES_PATH) + "/" + setting_id;
std::string response;
unsigned int http_code = 0;
int result = http_delete(path, &response, &http_code);
if (result != BAMBU_NETWORK_SUCCESS || http_code >= 400) {
return BAMBU_NETWORK_ERR_DEL_SETTING_FAILED;
}
return BAMBU_NETWORK_SUCCESS;
}
// ============================================================================
// Sync Protocol Implementation
// ============================================================================
int OrcaCloudServiceAgent::sync_pull(
std::function<void(const SyncPullResponse&)> on_success,
std::function<void(int http_code, const std::string& error)> on_error)
{
std::string path = ORCA_SYNC_PULL_PATH;
if (!sync_state.last_sync_timestamp.empty()) {
path += "?cursor=" + sync_state.last_sync_timestamp;
}
std::string response;
unsigned int http_code = 0;
int result = http_get(path, &response, &http_code);
// Handle 410 Gone - cursor too old, need full resync
if (http_code == 410) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: sync_pull returned 410 Gone - cursor too old, triggering full resync";
clear_sync_state();
// Retry without cursor
path = ORCA_SYNC_PULL_PATH;
result = http_get(path, &response, &http_code);
}
if (result != BAMBU_NETWORK_SUCCESS || (http_code != 200 && http_code != 304)) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: sync_pull failed - http_code=" << http_code;
if (on_error) on_error(http_code, response);
return BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
}
if (http_code == 304) {
if (on_success) {
SyncPullResponse empty_response;
on_success(empty_response);
}
return BAMBU_NETWORK_SUCCESS;
}
try {
auto json = nlohmann::json::parse(response);
SyncPullResponse resp;
resp.next_cursor = json.value("next_cursor", "");
if (json.contains("upserts") && json["upserts"].is_array()) {
for (const auto& item : json["upserts"]) {
ProfileUpsert upsert;
upsert.id = item.value("id", "");
upsert.name = item.value("name", "");
upsert.updated_at = item.value(ORCA_JSON_KEY_UPDATE_TIME, "");
upsert.created_at = item.value(ORCA_JSON_KEY_CREATED_TIME, "");
if (item.contains("content")) {
upsert.content = item["content"];
}
resp.upserts.push_back(upsert);
}
}
if (json.contains("deletes") && json["deletes"].is_array()) {
for (const auto& item : json["deletes"]) {
resp.deletes.push_back(item.get<std::string>());
}
}
if (on_success) on_success(resp);
return BAMBU_NETWORK_SUCCESS;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: sync_pull parse error - " << e.what();
if (on_error) on_error(http_code, e.what());
return BAMBU_NETWORK_ERR_INVALID_RESULT;
}
}
SyncPushResult OrcaCloudServiceAgent::sync_push(
const std::string& profile_id,
const std::string& name,
const nlohmann::json& content,
const std::string& original_updated_at)
{
SyncPushResult result;
result.success = false;
result.http_code = 0;
result.server_deleted = false;
nlohmann::json body;
body["id"] = profile_id;
body["name"] = name;
body["content"] = content;
if (!original_updated_at.empty()) {
body["original_updated_at"] = original_updated_at;
}
std::string response;
unsigned int http_code = 0;
int http_result = http_post(ORCA_SYNC_PUSH_PATH, body.dump(), &response, &http_code);
result.http_code = http_code;
if (http_result != BAMBU_NETWORK_SUCCESS) {
result.error_message = "HTTP request failed";
return result;
}
if (http_code == 409) {
// Conflict - parse server version
try {
auto json = nlohmann::json::parse(response);
if (json.is_null()) {
result.server_deleted = true;
} else {
result.server_version.id = json.value("id", "");
result.server_version.name = json.value("name", "");
result.server_version.updated_at = json.value(ORCA_JSON_KEY_UPDATE_TIME, "");
}
} catch (...) {}
result.error_message = response;
return result;
}
if (http_code != 200) {
result.error_message = response;
return result;
}
// Success
try {
auto json = nlohmann::json::parse(response);
result.new_updated_at = json.value(ORCA_JSON_KEY_UPDATE_TIME, "");
if (!result.new_updated_at.empty()) {
result.success = true;
} else {
result.error_message = "Server response missing required updated_at timestamp";
}
} catch (const std::exception& e) {
result.error_message = e.what();
}
return result;
}
// ============================================================================
// Sync State Management
// ============================================================================
void OrcaCloudServiceAgent::load_sync_state()
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
if (sync_state_path.empty()) return;
try {
std::ifstream ifs(sync_state_path);
if (ifs.good()) {
std::string line;
if (std::getline(ifs, line)) {
sync_state.last_sync_timestamp = line;
}
}
} catch (...) {}
}
void OrcaCloudServiceAgent::save_sync_state()
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
if (sync_state_path.empty()) return;
try {
std::string tmp_path = sync_state_path + ".tmp";
std::ofstream ofs(tmp_path, std::ios::out | std::ios::trunc);
if (ofs.good()) {
ofs << sync_state.last_sync_timestamp;
ofs.close();
boost::filesystem::rename(tmp_path, sync_state_path);
}
} catch (...) {}
}
void OrcaCloudServiceAgent::clear_sync_state()
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
sync_state = SyncState{};
if (!sync_state_path.empty() && boost::filesystem::exists(sync_state_path)) {
boost::filesystem::remove(sync_state_path);
}
}
// ============================================================================
// Auth - PKCE and Session Management
// ============================================================================
void OrcaCloudServiceAgent::set_session_handler(SessionHandler handler)
{
session_handler = std::move(handler);
}
void OrcaCloudServiceAgent::set_on_login_complete_handler(OnLoginCompleteHandler handler)
{
on_login_complete_handler = std::move(handler);
}
const OrcaCloudServiceAgent::PkceBundle& OrcaCloudServiceAgent::pkce()
{
if (pkce_bundle.verifier.empty() || pkce_bundle.challenge.empty() || pkce_bundle.state.empty()) {
regenerate_pkce();
}
return pkce_bundle;
}
void OrcaCloudServiceAgent::regenerate_pkce()
{
pkce_bundle.verifier = generate_code_verifier();
pkce_bundle.challenge = sha256_base64url(pkce_bundle.verifier);
pkce_bundle.state = generate_state_token();
if (pkce_bundle.redirect.empty()) {
pkce_bundle.redirect = "http://localhost:" + std::to_string(pkce_bundle.loopback_port) + auth_constants::LOOPBACK_PATH;
}
}
void OrcaCloudServiceAgent::update_redirect_uri()
{
int selected_port = choose_loopback_port();
pkce_bundle.loopback_port = selected_port;
pkce_bundle.redirect = "http://localhost:" + std::to_string(selected_port) + auth_constants::LOOPBACK_PATH;
}
// ============================================================================
// Auth - Token Persistence
// ============================================================================
void OrcaCloudServiceAgent::persist_refresh_token(const std::string& token)
{
if (token.empty()) {
clear_refresh_token();
return;
}
bool stored = false;
if (m_use_encrypted_token_file) {
// Use encrypted file only
auto key = sha256_bytes(machine_identifier());
if (key.empty()) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: cannot derive key for refresh-token file storage";
return;
}
std::string payload;
if (!aes256gcm_encrypt(token, key, payload)) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: failed to encrypt refresh token for file storage";
return;
}
std::string signed_payload = payload;
if (auto mac = hmac_sha256_hex(payload, key); !mac.empty()) {
signed_payload = "v2:" + mac + ":" + payload;
}
compute_fallback_path();
wxFileName path(wxString::FromUTF8(refresh_fallback_path.c_str()));
path.Normalize();
if (!wxFileName::DirExists(path.GetPath())) {
wxFileName::Mkdir(path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
}
const std::string tmp_path = refresh_fallback_path + ".tmp";
std::ofstream ofs(tmp_path, std::ios::out | std::ios::trunc | std::ios::binary);
if (ofs.good()) {
ofs << signed_payload;
ofs.flush();
ofs.close();
if (wxRenameFile(wxString::FromUTF8(tmp_path.c_str()), wxString::FromUTF8(refresh_fallback_path.c_str()), true)) {
stored = true;
} else {
wxRemoveFile(wxString::FromUTF8(tmp_path.c_str()));
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: failed to atomically replace refresh-token file";
}
} else {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: cannot open refresh-token file for write - " << refresh_fallback_path;
}
} else {
// Use wxSecretStore only
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
wxSecretValue secret(wxString::FromUTF8(token.c_str()));
if (store.Save(SECRET_STORE_SERVICE, SECRET_STORE_USER, secret)) {
stored = true;
} else {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: System Keychain save failed";
}
} else {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: System Keychain not available";
}
}
(void) stored;
}
bool OrcaCloudServiceAgent::load_refresh_token(std::string& out_token)
{
out_token.clear();
if (m_use_encrypted_token_file) {
// Load from encrypted file only
compute_fallback_path();
if (wxFileExists(wxString::FromUTF8(refresh_fallback_path.c_str()))) {
std::ifstream ifs(refresh_fallback_path, std::ios::binary);
std::string payload((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
auto key = sha256_bytes(machine_identifier());
std::string plain;
if (!key.empty()) {
std::string encoded_payload = payload;
bool integrity_ok = true;
if (payload.rfind("v2:", 0) == 0) {
auto delim = payload.find(':', 3);
if (delim == std::string::npos) {
integrity_ok = false;
} else {
std::string stored_hmac = payload.substr(3, delim - 3);
std::string lower_stored = stored_hmac;
std::transform(lower_stored.begin(), lower_stored.end(), lower_stored.begin(), ::tolower);
encoded_payload = payload.substr(delim + 1);
std::string computed_hmac = hmac_sha256_hex(encoded_payload, key);
std::transform(computed_hmac.begin(), computed_hmac.end(), computed_hmac.begin(), ::tolower);
if (computed_hmac.empty() || computed_hmac != lower_stored) {
integrity_ok = false;
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: refresh token integrity check failed (HMAC mismatch)";
}
}
}
if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
out_token = plain;
// Upgrade legacy payloads to signed format
if (payload.rfind("v2:", 0) != 0) {
persist_refresh_token(out_token);
}
return true;
}
}
}
} else {
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
wxString username;
wxSecretValue secret;
if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
out_token.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
if (!out_token.empty()) {
return true;
}
}
}
}
return false;
}
void OrcaCloudServiceAgent::clear_refresh_token()
{
wxSecretStore store = wxSecretStore::GetDefault();
if (store.IsOk()) {
store.Delete(SECRET_STORE_SERVICE);
}
compute_fallback_path();
if (!refresh_fallback_path.empty() && wxFileExists(wxString::FromUTF8(refresh_fallback_path.c_str()))) {
wxRemoveFile(wxString::FromUTF8(refresh_fallback_path.c_str()));
}
}
// ============================================================================
// Auth - Token Refresh
// ============================================================================
bool OrcaCloudServiceAgent::should_refresh_locked(std::chrono::seconds skew) const
{
if (!session.logged_in) return false;
if (session.expires_at.time_since_epoch().count() == 0) return true;
auto now = std::chrono::system_clock::now();
return (session.expires_at - now) <= skew;
}
bool OrcaCloudServiceAgent::decode_jwt_expiry(const std::string& token, std::chrono::system_clock::time_point& out_tp)
{
out_tp = {};
if (token.empty()) return false;
auto first = token.find('.');
auto second = token.find('.', first == std::string::npos ? 0 : first + 1);
if (first == std::string::npos || second == std::string::npos) return false;
std::string payload_b64 = token.substr(first + 1, second - first - 1);
std::vector<unsigned char> payload_bytes;
if (!base64url_decode(payload_b64, payload_bytes)) return false;
std::string payload_str(payload_bytes.begin(), payload_bytes.end());
try {
pt::ptree payload;
std::stringstream ss(payload_str);
pt::read_json(ss, payload);
auto exp_opt = payload.get_optional<long long>("exp");
if (exp_opt) {
out_tp = std::chrono::system_clock::time_point{std::chrono::seconds(*exp_opt)};
return true;
}
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: failed to decode JWT exp - " << e.what();
}
return false;
}
bool OrcaCloudServiceAgent::refresh_now(const std::string& refresh_token, const std::string& reason, bool async)
{
if (refresh_token.empty()) return false;
bool expected = false;
if (!refresh_running.compare_exchange_strong(expected, true)) {
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: refresh already running, skip (reason=" << reason << ")";
return false;
}
auto worker = [this, refresh_token, reason]() {
(void) reason;
bool ok = refresh_session_with_token(refresh_token);
refresh_running.store(false);
return ok;
};
if (async) {
if (refresh_thread.joinable()) {
refresh_thread.join();
}
refresh_thread = std::thread([worker]() { worker(); });
return true;
}
return worker();
}
bool OrcaCloudServiceAgent::refresh_from_storage(const std::string& reason, bool async)
{
std::string refresh_token = get_refresh_token();
if (refresh_token.empty()) {
load_refresh_token(refresh_token);
}
if (refresh_token.empty()) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: no refresh token available for refresh (reason=" << reason << ")";
return false;
}
return refresh_now(refresh_token, reason, async);
}
bool OrcaCloudServiceAgent::refresh_if_expiring(std::chrono::seconds skew, const std::string& reason)
{
bool needs_refresh = false;
{
std::lock_guard<std::mutex> lock(session_mutex);
needs_refresh = should_refresh_locked(skew);
}
if (!needs_refresh) return true;
if (refresh_from_storage(reason, false)) return true;
std::this_thread::sleep_for(std::chrono::milliseconds(750));
return refresh_from_storage(reason + "_retry", false);
}
bool OrcaCloudServiceAgent::refresh_session_with_token(const std::string& refresh_token)
{
std::string body = "{\"refresh_token\":\"" + refresh_token + "\"}";
std::string url = auth_base_url + auth_constants::TOKEN_PATH + "?grant_type=refresh_token";
std::string response;
unsigned int http_code = 0;
if (!http_post_token(body, &response, &http_code, url) || http_code >= 400) {
std::string truncated_response = response.size() > 200 ? response.substr(0, 200) + "..." : response;
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: token refresh failed - http_code=" << http_code
<< ", response_body=" << truncated_response;
return false;
}
if (session_handler) {
return session_handler(response);
}
// No session handler set - parse the token response directly and establish session
// This makes OrcaCloudServiceAgent self-contained without requiring external setup
try {
std::stringstream ss(response);
pt::ptree tree;
pt::read_json(ss, tree);
auto read_str = [](const pt::ptree& node, const std::string& path) {
return node.get<std::string>(path, "");
};
// Token refresh response has the same structure as login response
std::string access_token = read_str(tree, "access_token");
if (access_token.empty()) {
access_token = read_str(tree, "token");
}
std::string new_refresh_token = read_str(tree, "refresh_token");
std::string user_id = read_str(tree, "user.id");
std::string email = read_str(tree, "user.email");
std::string full_name = read_str(tree, "user.user_metadata.full_name");
std::string preferred_username = read_str(tree, "user.user_metadata.preferred_username");
std::string avatar = read_str(tree, "user.user_metadata.avatar_url");
std::string username = !preferred_username.empty() ? preferred_username : email;
std::string name = !full_name.empty() ? full_name : (!preferred_username.empty() ? preferred_username : email);
std::string nickname = !preferred_username.empty() ? preferred_username : username;
if (nickname.empty()) nickname = name;
if (nickname.empty()) nickname = email;
if (access_token.empty() || user_id.empty()) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: token refresh response missing access_token or user.id";
invoke_user_login_callback(0, false);
return false;
}
bool success = set_user_session(access_token, user_id, username, name, nickname, avatar, new_refresh_token);
if (success) {
invoke_user_login_callback(0, true);
if (on_login_complete_handler) {
on_login_complete_handler(true, user_id);
}
} else {
invoke_user_login_callback(0, false);
}
return success;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: token refresh parse exception - " << e.what();
invoke_user_login_callback(0, false);
return false;
}
}
// ============================================================================
// Auth - Session State Helpers
// ============================================================================
bool OrcaCloudServiceAgent::set_user_session(const std::string& token,
const std::string& user_id,
const std::string& username,
const std::string& name,
const std::string& nickname,
const std::string& avatar,
const std::string& refresh_token)
{
std::chrono::system_clock::time_point exp_tp{};
decode_jwt_expiry(token, exp_tp);
{
std::lock_guard<std::mutex> lock(session_mutex);
session.access_token = token;
session.refresh_token = refresh_token;
session.user_id = user_id;
session.user_name = name.empty() ? username : name;
session.user_nickname = nickname.empty() ? (!username.empty() ? username : name) : nickname;
session.user_avatar = avatar;
session.expires_at = exp_tp;
session.logged_in = true;
}
if (!refresh_token.empty()) {
persist_refresh_token(refresh_token);
}
// Set per-user sync state path
if (!config_dir.empty() && !user_id.empty()) {
boost::filesystem::path user_dir = boost::filesystem::path(config_dir) / "user" / user_id;
if (!boost::filesystem::exists(user_dir)) {
boost::filesystem::create_directories(user_dir);
}
sync_state_path = (user_dir / ORCA_SYNC_STATE_FILE).string();
load_sync_state();
}
BOOST_LOG_TRIVIAL(info) << "OrcaCloudServiceAgent: set_user_session - user_id=" << user_id << ", username=" << username;
return true;
}
void OrcaCloudServiceAgent::clear_session()
{
{
std::lock_guard<std::mutex> lock(session_mutex);
session = SessionInfo{};
}
clear_refresh_token();
}
// ============================================================================
// HTTP Helpers
// ============================================================================
bool OrcaCloudServiceAgent::attempt_refresh_after_unauthorized(const std::string& reason)
{
if (refresh_from_storage(reason, false)) return true;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
if (refresh_from_storage(reason + "_retry", false)) return true;
BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh result=failure source=" << reason << " action=logout";
return false;
}
std::map<std::string, std::string> OrcaCloudServiceAgent::data_headers()
{
std::scoped_lock lock(state_mutex, headers_mutex);
auto headers = auth_headers;
for (const auto& pair : extra_headers) {
headers[pair.first] = pair.second;
}
return headers;
}
int OrcaCloudServiceAgent::http_get(const std::string& path, std::string* response_body, unsigned int* http_code)
{
std::string url = api_base_url + path;
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: GET " << url;
ensure_token_fresh("http_get_" + path);
struct HttpResult {
bool success{false};
unsigned int status{0};
std::string body;
};
auto perform = [&]() {
HttpResult result;
try {
auto http = Http::get(url);
std::string token = get_access_token();
auto headers = data_headers();
for (const auto& pair : headers) {
http.header(pair.first, pair.second);
}
if (!token.empty()) {
http.header("Authorization", "Bearer " + token);
}
http.on_complete([&](std::string body, unsigned resp_status) {
result.success = true;
result.status = resp_status;
result.body = body;
})
.on_error([&](std::string body, std::string error, unsigned resp_status) {
result.success = false;
result.status = resp_status;
result.body = body;
})
.timeout_max(30)
.perform_sync();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_get exception - " << e.what();
}
return result;
};
HttpResult res = perform();
// Single retry on 401 - no recursion
if (res.status == 401 && attempt_refresh_after_unauthorized("http_get_" + path)) {
res = perform();
}
if (response_body) *response_body = res.body;
if (http_code) *http_code = res.status;
if (!res.success || res.status >= 400) {
invoke_http_error_callback(res.status, res.body);
}
return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code)
{
std::string url = api_base_url + path;
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url;
ensure_token_fresh("http_post_" + path);
struct HttpResult {
bool success{false};
unsigned int status{0};
std::string body;
};
auto perform = [&]() {
HttpResult result;
try {
auto http = Http::post(url);
std::string token = get_access_token();
auto headers = data_headers();
for (const auto& pair : headers) {
http.header(pair.first, pair.second);
}
if (!token.empty()) {
http.header("Authorization", "Bearer " + token);
}
http.header("Content-Type", "application/json");
http.set_post_body(body);
http.on_complete([&](std::string resp_body, unsigned resp_status) {
result.success = true;
result.status = resp_status;
result.body = resp_body;
})
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
result.success = false;
result.status = resp_status;
result.body = resp_body;
})
.timeout_max(30)
.perform_sync();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_post exception - " << e.what();
}
return result;
};
HttpResult res = perform();
// Single retry on 401 - no recursion
if (res.status == 401 && attempt_refresh_after_unauthorized("http_post_" + path)) {
res = perform();
}
if (response_body) *response_body = res.body;
if (http_code) *http_code = res.status;
if (!res.success || res.status >= 400) {
invoke_http_error_callback(res.status, res.body);
}
return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
int OrcaCloudServiceAgent::http_put(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code)
{
std::string url = api_base_url + path;
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: PUT " << url;
ensure_token_fresh("http_put_" + path);
struct HttpResult {
bool success{false};
unsigned int status{0};
std::string body;
};
auto perform = [&]() {
HttpResult result;
try {
auto http = Http::put(url);
std::string token = get_access_token();
auto headers = data_headers();
for (const auto& pair : headers) {
http.header(pair.first, pair.second);
}
if (!token.empty()) {
http.header("Authorization", "Bearer " + token);
}
http.header("Content-Type", "application/json");
http.set_post_body(body);
http.on_complete([&](std::string resp_body, unsigned resp_status) {
result.success = true;
result.status = resp_status;
result.body = resp_body;
})
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
result.success = false;
result.status = resp_status;
result.body = resp_body;
})
.timeout_max(30)
.perform_sync();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_put exception - " << e.what();
}
return result;
};
HttpResult res = perform();
// Single retry on 401 - no recursion
if (res.status == 401 && attempt_refresh_after_unauthorized("http_put_" + path)) {
res = perform();
}
if (response_body) *response_body = res.body;
if (http_code) *http_code = res.status;
if (!res.success || res.status >= 400) {
invoke_http_error_callback(res.status, res.body);
}
return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
int OrcaCloudServiceAgent::http_delete(const std::string& path, std::string* response_body, unsigned int* http_code)
{
std::string url = api_base_url + path;
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: DELETE " << url;
ensure_token_fresh("http_delete_" + path);
struct HttpResult {
bool success{false};
unsigned int status{0};
std::string body;
};
auto perform = [&]() {
HttpResult result;
try {
auto http = Http::del(url);
std::string token = get_access_token();
auto headers = data_headers();
for (const auto& pair : headers) {
http.header(pair.first, pair.second);
}
if (!token.empty()) {
http.header("Authorization", "Bearer " + token);
}
http.on_complete([&](std::string resp_body, unsigned resp_status) {
result.success = true;
result.status = resp_status;
result.body = resp_body;
})
.on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
result.success = false;
result.status = resp_status;
result.body = resp_body;
})
.timeout_max(30)
.perform_sync();
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_delete exception - " << e.what();
}
return result;
};
HttpResult res = perform();
// Single retry on 401 - no recursion
if (res.status == 401 && attempt_refresh_after_unauthorized("http_delete_" + path)) {
res = perform();
}
if (response_body) *response_body = res.body;
if (http_code) *http_code = res.status;
if (!res.success || res.status >= 400) {
invoke_http_error_callback(res.status, res.body);
}
return res.success ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
}
bool OrcaCloudServiceAgent::http_post_token(const std::string& body, std::string* response_body, unsigned int* http_code, const std::string& custom_url)
{
std::map<std::string, std::string> headers_copy;
std::string url;
{
std::lock_guard<std::mutex> lock(headers_mutex);
url = custom_url.empty() ? (auth_base_url + auth_constants::TOKEN_PATH) : custom_url;
headers_copy = extra_headers;
}
// Add auth headers
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
for (const auto& pair : auth_headers) {
headers_copy[pair.first] = pair.second;
}
}
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url;
bool has_apikey = false;
for (const auto& pair : headers_copy) {
if (pair.first == "apikey")
has_apikey = true;
}
if (!has_apikey) {
BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: http_post_token - apikey header MISSING! Token request will likely fail.";
}
try {
auto http = Http::post(url);
for (const auto& pair : headers_copy) {
http.header(pair.first, pair.second);
}
http.remove_header("Authorization");
http.remove_header("Content-Type");
http.header("Content-Type", "application/json");
http.set_post_body(body);
bool success = false;
unsigned int status = 0;
std::string resp_body;
http.on_complete([&](std::string body, unsigned resp_status) {
success = true;
status = resp_status;
resp_body = body;
})
.on_error([&](std::string body, std::string error, unsigned resp_status) {
success = false;
status = resp_status;
resp_body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
})
.timeout_max(30)
.perform_sync();
if (response_body)
*response_body = resp_body;
if (http_code)
*http_code = status;
return success;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_post_token exception - " << e.what();
if (http_code)
*http_code = 0;
return false;
}
}
bool OrcaCloudServiceAgent::http_post_auth(const std::string& path, const std::string& body, std::string* response_body, unsigned int* http_code)
{
std::string url = auth_base_url + path;
std::string token;
std::map<std::string, std::string> headers_copy;
{
std::lock_guard<std::mutex> lock(session_mutex);
token = session.access_token;
}
{
std::lock_guard<std::mutex> lock(headers_mutex);
headers_copy = extra_headers;
}
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
for (const auto& pair : auth_headers) {
headers_copy[pair.first] = pair.second;
}
}
BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST (auth) " << url;
try {
auto http = Http::post(url);
for (const auto& pair : headers_copy) {
http.header(pair.first, pair.second);
}
if (!token.empty()) {
http.header("Authorization", "Bearer " + token);
}
http.remove_header("Content-Type");
http.header("Content-Type", "application/json");
http.set_post_body(body);
bool success = false;
unsigned int status = 0;
std::string resp_body;
http.on_complete([&](std::string body, unsigned resp_status) {
success = true;
status = resp_status;
resp_body = body;
})
.on_error([&](std::string body, std::string error, unsigned resp_status) {
success = false;
status = resp_status;
resp_body = body;
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP (auth) error - " << error;
})
.timeout_max(30)
.perform_sync();
if (response_body)
*response_body = resp_body;
if (http_code)
*http_code = status;
return success;
} catch (const std::exception& e) {
BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_post_auth exception - " << e.what();
if (http_code)
*http_code = 0;
return false;
}
}
void OrcaCloudServiceAgent::compute_fallback_path()
{
if (!refresh_fallback_path.empty()) return;
wxFileName fallback(wxStandardPaths::Get().GetUserDataDir(), "orca_refresh_token.sec");
fallback.Normalize();
refresh_fallback_path = fallback.GetFullPath().ToStdString();
}
// ============================================================================
// JSON Helpers
// ============================================================================
std::string OrcaCloudServiceAgent::map_to_json(const std::map<std::string, std::string>& map)
{
nlohmann::json j;
for (const auto& pair : map) {
j[pair.first] = pair.second;
}
return j.dump();
}
void OrcaCloudServiceAgent::json_to_map(const std::string& json, std::map<std::string, std::string>& map)
{
try {
auto j = nlohmann::json::parse(json);
for (auto it = j.begin(); it != j.end(); ++it) {
if (it.value().is_string()) {
map[it.key()] = it.value().get<std::string>();
} else {
map[it.key()] = it.value().dump();
}
}
} catch (...) {}
}
// ============================================================================
// Callback Invocation
// ============================================================================
void OrcaCloudServiceAgent::invoke_user_login_callback(int online_login, bool login)
{
OnUserLoginFn callback;
QueueOnMainFn queue_fn;
{
std::lock_guard<std::mutex> lock(callback_mutex);
callback = on_user_login_fn;
queue_fn = queue_on_main_fn;
}
if (callback) {
if (queue_fn) {
queue_fn([callback, online_login, login]() {
callback(online_login, login);
});
} else {
callback(online_login, login);
}
}
}
void OrcaCloudServiceAgent::invoke_server_connected_callback(int return_code, int reason_code)
{
OnServerConnectedFn callback;
QueueOnMainFn queue_fn;
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
callback = on_server_connected_fn;
queue_fn = queue_on_main_fn;
}
if (callback) {
if (queue_fn) {
queue_fn([callback, return_code, reason_code]() {
callback(return_code, reason_code);
});
} else {
callback(return_code, reason_code);
}
}
}
void OrcaCloudServiceAgent::invoke_http_error_callback(unsigned http_code, const std::string& http_body)
{
OnHttpErrorFn callback;
QueueOnMainFn queue_fn;
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
callback = on_http_error_fn;
queue_fn = queue_on_main_fn;
}
if (callback) {
if (queue_fn) {
queue_fn([callback, http_code, http_body]() {
callback(http_code, http_body);
});
} else {
callback(http_code, http_body);
}
}
}
// ============================================================================
// Callback Registration
// ============================================================================
int OrcaCloudServiceAgent::set_on_user_login_fn(OnUserLoginFn fn)
{
std::lock_guard<std::mutex> lock(callback_mutex);
on_user_login_fn = fn;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_on_server_connected_fn(OnServerConnectedFn fn)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
on_server_connected_fn = fn;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_on_http_error_fn(OnHttpErrorFn fn)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
on_http_error_fn = fn;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_get_country_code_fn(GetCountryCodeFn fn)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
get_country_code_fn = fn;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_queue_on_main_fn(QueueOnMainFn fn)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
queue_on_main_fn = fn;
return BAMBU_NETWORK_SUCCESS;
}
// ============================================================================
// Stub Implementations (Cloud Services, Model Mall, Analytics, Ratings)
// ============================================================================
int OrcaCloudServiceAgent::get_my_message(int type, int after, int limit, unsigned int* http_code, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_my_message (stub)";
if (http_code) *http_code = 200;
if (http_body) *http_body = "[]";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::check_user_task_report(int* task_id, bool* printable)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: check_user_task_report (stub)";
if (task_id) *task_id = 0;
if (printable) *printable = false;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_user_print_info(unsigned int* http_code, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_print_info (stub)";
if (http_code) *http_code = 200;
if (http_body) *http_body = "{}";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_user_tasks(TaskQueryParams params, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_tasks (stub)";
if (http_body) *http_body = "[]";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_printer_firmware(std::string dev_id, unsigned* http_code, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_printer_firmware (stub)";
if (http_code) *http_code = 200;
if (http_body) *http_body = "{}";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_task_plate_index(std::string task_id, int* plate_index)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_task_plate_index (stub)";
if (plate_index) *plate_index = 0;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_user_info(int* identifier)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_info (stub)";
if (identifier) *identifier = 0;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_subtask_info(std::string subtask_id, std::string* task_json, unsigned int* http_code, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_subtask_info (stub)";
if (task_json) *task_json = "{}";
if (http_code) *http_code = 200;
if (http_body) *http_body = "{}";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_slice_info(std::string project_id, std::string profile_id, int plate_index, std::string* slice_json)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_slice_info (stub)";
if (slice_json) *slice_json = "{}";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::query_bind_status(std::vector<std::string> query_list, unsigned int* http_code, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: query_bind_status (stub)";
if (http_code) *http_code = 200;
if (http_body) *http_body = "{}";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::modify_printer_name(std::string dev_id, std::string dev_name)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: modify_printer_name (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_camera_url(std::string dev_id, std::function<void(std::string)> callback)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_camera_url (stub)";
if (callback) callback("");
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_design_staffpick(int offset, int limit, std::function<void(std::string)> callback)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_design_staffpick (stub)";
if (callback) callback("[]");
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::start_publish(PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string* out)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: start_publish (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_model_publish_url(std::string* url)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_publish_url (stub)";
if (url) *url = "";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_subtask(BBLModelTask* task, OnGetSubTaskFn getsub_fn)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_subtask (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_model_mall_home_url(std::string* url)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_mall_home_url (stub)";
if (url) *url = "";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_model_mall_detail_url(std::string* url, std::string id)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_mall_detail_url (stub)";
if (url) *url = "";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_my_profile(std::string token, unsigned int* http_code, std::string* http_body)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_my_profile (stub)";
if (http_code) *http_code = 200;
if (http_body) *http_body = "{}";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::track_enable(bool enable)
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
enable_track = enable;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::track_remove_files()
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_remove_files (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::track_event(std::string evt_key, std::string content)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_event (stub) - " << evt_key;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::track_header(std::string header)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_header (stub)";
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::track_update_property(std::string name, std::string value, std::string type)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_update_property (stub) - " << name;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::track_get_property(std::string name, std::string& value, std::string type)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_get_property (stub) - " << name;
value = "";
return BAMBU_NETWORK_SUCCESS;
}
bool OrcaCloudServiceAgent::get_track_enable()
{
std::lock_guard<std::recursive_mutex> lock(state_mutex);
return enable_track;
}
int OrcaCloudServiceAgent::put_model_mall_rating(int design_id, int score, std::string content, std::vector<std::string> images, unsigned int& http_code, std::string& http_error)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: put_model_mall_rating (stub)";
http_code = 200;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_oss_config(std::string& config, std::string country_code, unsigned int& http_code, std::string& http_error)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_oss_config (stub)";
config = "{}";
http_code = 200;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::put_rating_picture_oss(std::string& config, std::string& pic_oss_path, std::string model_id, int profile_id, unsigned int& http_code, std::string& http_error)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: put_rating_picture_oss (stub)";
http_code = 200;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_model_mall_rating_result(int job_id, std::string& rating_result, unsigned int& http_code, std::string& http_error)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_mall_rating_result (stub)";
rating_result = "{}";
http_code = 200;
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::set_extra_http_header(std::map<std::string, std::string> headers)
{
std::lock_guard<std::mutex> lock(headers_mutex);
extra_headers = headers;
return BAMBU_NETWORK_SUCCESS;
}
std::string OrcaCloudServiceAgent::get_cloud_service_host()
{
return api_base_url;
}
std::string OrcaCloudServiceAgent::get_cloud_login_url(const std::string& language)
{
// Orca uses a local HTML file for the login flow
boost::filesystem::path login_path = boost::filesystem::path(resources_dir()) / "web" / "login" / "orca_login.html";
return "file://" + login_path.make_preferred().string();
}
std::string OrcaCloudServiceAgent::get_studio_info_url()
{
return api_base_url + "/studio/info";
}
int OrcaCloudServiceAgent::get_mw_user_preference(std::function<void(std::string)> callback)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_mw_user_preference (stub)";
if (callback) callback("{}");
return BAMBU_NETWORK_SUCCESS;
}
int OrcaCloudServiceAgent::get_mw_user_4ulist(int seed, int limit, std::function<void(std::string)> callback)
{
BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_mw_user_4ulist (stub)";
if (callback) callback("[]");
return BAMBU_NETWORK_SUCCESS;
}
std::string OrcaCloudServiceAgent::get_version()
{
return "OrcaCloudServiceAgent 1.0.0";
}
} // namespace Slic3r