* Add OrcaCloud sync platform and preset bundle sharing system

  Introduce OrcaCloud, a cloud sync platform for user presets, alongside
  a preset bundle system that enables sharing printer/filament/process
  profiles as local exportable bundles or subscribed cloud bundles.

  OrcaCloud platform:
  - Auth to Orca Cloud
  - Encrypted token storage (file-based or system keychain)
  - User preset sync with
  - Profile migration from default/bambu folders on first login
  - Homepage integration with entrance to cloud.orcaslicer.com

  Preset bundles:
  - Local bundle import/export with bundle_structure.json metadata
  - Subscribed cloud bundles with version-based update checking
  - Thread-safe concurrent bundle access with read-write mutex
  - Canonical bundle preset naming (_local/<id>/... and _subscribed/<id>/...)
  - Bundle presets are read-only; grouped under subheaders in combo boxes
  - PresetBundleDialog with auto-sync toggle, refresh, update notifications
  - Hyperlinked bundle names to cloud bundle pages

  Co-authored-by: Sabriel Koh <sabrielkcr@gmail.com>
  Co-authored-by: Derrick <derrick992110@gmail.com>
  Co-authored-by: Mykola Nahirnyi <mnahirnyi@amcbridge.com>
  Co-authored-by: Ian Chua <iancrb00@gmail.com>
  Co-authored-by: Draginraptor <draginraptor@gmail.com>
  Co-authored-by: ExPikaPaka <112851715+ExPikaPaka@users.noreply.github.com>
  Co-authored-by: Ian Bassi <ian.bassi@outlook.com>
  Co-authored-by: Ocraftyone <Ocraftyone@users.noreply.github.com>
  Co-authored-by: yw4z <ywsyildiz@gmail.com>
  Co-authored-by: peterm-m <101202951+peterm-m@users.noreply.github.com>

* Fixed an issue on Windows it failed to login Orca Cloud with Google account
This commit is contained in:
SoftFever
2026-05-01 18:01:29 +08:00
committed by GitHub
parent 6d8488c513
commit c86ca771de
113 changed files with 8691 additions and 3467 deletions

View File

@@ -2,9 +2,85 @@
#include "BBLNetworkPlugin.hpp"
#include <boost/log/trivial.hpp>
#include "Http.hpp"
#include "slic3r/GUI/GUI_App.hpp"
#include <sstream>
#include <boost/algorithm/string/replace.hpp>
#include <nlohmann/json.hpp>
namespace Slic3r {
namespace {
std::string convert_studio_language_to_api(std::string lang_code)
{
boost::replace_all(lang_code, "_", "-");
return lang_code;
}
std::string normalize_homepage_auth_command(std::string payload)
{
if (payload.empty()) {
return payload;
}
try {
auto json = nlohmann::json::parse(payload);
if (json.contains("command") && json["command"].is_string()) {
std::string command = json["command"].get<std::string>();
if (command == "studio_userlogin") {
json["command"] = "studio_bambu_userlogin";
} else if (command == "studio_useroffline") {
json["command"] = "studio_bambu_useroffline";
}
return json.dump();
}
} catch (...) {
boost::replace_first(payload, "\"command\":\"studio_userlogin\"", "\"command\":\"studio_bambu_userlogin\"");
boost::replace_first(payload, "\"command\":\"studio_useroffline\"", "\"command\":\"studio_bambu_useroffline\"");
}
return payload;
}
} // namespace
std::map<std::string, std::string> BBLCloudServiceAgent::get_extra_header()
{
std::map<std::string, std::string> extra_headers;
extra_headers.emplace("X-BBL-Client-Type", "slicer");
extra_headers.emplace("X-BBL-Client-Name", SLIC3R_APP_NAME);
extra_headers.emplace("X-BBL-Client-Version", GUI::wxGetApp().get_bbl_client_version());
#if defined(__WINDOWS__)
#ifdef _M_X64
extra_headers.emplace("X-BBL-OS-Type", "windows");
#else
extra_headers.emplace("X-BBL-OS-Type", "windows_arm");
#endif
#elif defined(__APPLE__)
extra_headers.emplace("X-BBL-OS-Type", "macos");
#elif defined(__LINUX__)
extra_headers.emplace("X-BBL-OS-Type", "linux");
#endif
int major = 0, minor = 0, micro = 0;
wxGetOsVersion(&major, &minor, &micro);
std::ostringstream os_version;
os_version << major << "." << minor << "." << micro;
extra_headers.emplace("X-BBL-OS-Version", os_version.str());
auto& app = GUI::wxGetApp();
if (app.app_config) {
extra_headers.emplace("X-BBL-Device-ID", app.app_config->get("slicer_uuid"));
}
extra_headers.emplace("X-BBL-Language",
convert_studio_language_to_api(app.current_language_code_safe().ToStdString()));
return extra_headers;
}
BBLCloudServiceAgent::BBLCloudServiceAgent() = default;
BBLCloudServiceAgent::~BBLCloudServiceAgent() = default;
@@ -59,6 +135,8 @@ int BBLCloudServiceAgent::set_country_code(std::string country_code)
int BBLCloudServiceAgent::start()
{
set_extra_http_header();
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_start();
@@ -159,7 +237,7 @@ std::string BBLCloudServiceAgent::build_login_cmd()
auto agent = plugin.get_agent();
auto func = plugin.get_build_login_cmd();
if (func && agent) {
return func(agent);
return normalize_homepage_auth_command(func(agent));
}
return "";
}
@@ -170,7 +248,7 @@ std::string BBLCloudServiceAgent::build_logout_cmd()
auto agent = plugin.get_agent();
auto func = plugin.get_build_logout_cmd();
if (func && agent) {
return func(agent);
return normalize_homepage_auth_command(func(agent));
}
return "";
}
@@ -181,7 +259,7 @@ std::string BBLCloudServiceAgent::build_login_info()
auto agent = plugin.get_agent();
auto func = plugin.get_build_login_info();
if (func && agent) {
return func(agent);
return normalize_homepage_auth_command(func(agent));
}
return "";
}
@@ -211,21 +289,6 @@ bool BBLCloudServiceAgent::ensure_token_fresh(const std::string& reason)
return true;
}
// ============================================================================
// Auth Callbacks (merged from BBLAuthAgent)
// ============================================================================
int BBLCloudServiceAgent::set_on_user_login_fn(OnUserLoginFn fn)
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_set_on_user_login_fn();
if (func && agent) {
return func(agent, fn);
}
return -1;
}
// ============================================================================
// Server Connectivity
// ============================================================================
@@ -767,28 +830,22 @@ int BBLCloudServiceAgent::get_model_mall_rating_result(int job_id, std::string&
// Extra Features
// ============================================================================
int BBLCloudServiceAgent::set_extra_http_header(std::map<std::string, std::string> extra_headers)
int BBLCloudServiceAgent::set_extra_http_header()
{
// Orca: not sure if this required to login into bbl cloud
// Slic3r::Http::set_extra_headers(extra_headers);
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto agent = plugin.get_agent();
auto func = plugin.get_set_extra_http_header();
if (func && agent) {
auto extra_headers = get_extra_header();
return func(agent, extra_headers);
}
return -1;
}
std::string BBLCloudServiceAgent::get_studio_info_url()
{
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_get_studio_info_url();
if (func && agent) {
return func(agent);
}
return "";
}
int BBLCloudServiceAgent::get_mw_user_preference(std::function<void(std::string)> callback)
{
auto& plugin = BBLNetworkPlugin::instance();
@@ -825,24 +882,36 @@ std::string BBLCloudServiceAgent::get_version()
// Cloud Callbacks
// ============================================================================
int BBLCloudServiceAgent::set_on_server_connected_fn(OnServerConnectedFn fn)
int BBLCloudServiceAgent::set_on_server_connected_fn(AppOnServerConnectedFn fn)
{
m_app_on_server_connected_fn = fn;
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_set_on_server_connected_fn();
if (func && agent) {
return func(agent, fn);
// Register raw callback with DLL, wrap to inject CloudEvent
return func(agent, [this](int return_code, int reason_code) {
if (m_app_on_server_connected_fn) {
m_app_on_server_connected_fn(CloudEvent{BBL_CLOUD_PROVIDER}, return_code, reason_code);
}
});
}
return -1;
}
int BBLCloudServiceAgent::set_on_http_error_fn(OnHttpErrorFn fn)
int BBLCloudServiceAgent::set_on_http_error_fn(AppOnHttpErrorFn fn)
{
m_app_on_http_error_fn = fn;
auto& plugin = BBLNetworkPlugin::instance();
auto agent = plugin.get_agent();
auto func = plugin.get_set_on_http_error_fn();
if (func && agent) {
return func(agent, fn);
// Register raw callback with DLL, wrap to inject CloudEvent
return func(agent, [this](unsigned http_code, std::string http_body) {
if (m_app_on_http_error_fn) {
m_app_on_http_error_fn(CloudEvent{BBL_CLOUD_PROVIDER}, http_code, http_body);
}
});
}
return -1;
}