OrcaSlicer latest
OrcaSlicer
Loading...
Searching...
No Matches
Slic3r::OrcaCloudServiceAgent Class Reference

#include </home/runner/work/OrcaSlicer/OrcaSlicer/src/slic3r/Utils/OrcaCloudServiceAgent.hpp>

Inheritance diagram for Slic3r::OrcaCloudServiceAgent:
Collaboration diagram for Slic3r::OrcaCloudServiceAgent:

Classes

struct  SessionInfo
struct  PkceBundle
struct  HttpResult

Public Types

using SessionHandler = std::function<bool(const std::string&)>
using OnLoginCompleteHandler = std::function<void(bool success, const std::string& user_id)>

Public Member Functions

 OrcaCloudServiceAgent (std::string log_dir)
 ~OrcaCloudServiceAgent () override
std::string get_id () const override
void configure_urls (AppConfig *app_config)
void set_api_base_url (const std::string &url)
void set_auth_base_url (const std::string &url)
void set_cloud_base_url (const std::string &url)
std::string get_cloud_base_url () const
void set_use_encrypted_token_file (bool use)
bool get_use_encrypted_token_file () const
int init_log () override
int set_config_dir (std::string config_dir) override
int set_cert_file (std::string folder, std::string filename) override
int set_country_code (std::string country_code) override
int start () override
int change_user (std::string user_info) override
bool is_user_login () override
int user_logout (bool request=false) override
std::string get_user_id () override
std::string get_user_name () override
std::string get_user_avatar () override
std::string get_user_nickname () override
std::string build_login_cmd () override
std::string build_logout_cmd () override
std::string build_login_info () override
std::string get_access_token () const override
std::string get_refresh_token () const override
bool ensure_token_fresh (const std::string &reason) override
std::string get_cloud_service_host () override
std::string get_cloud_login_url (const std::string &language="") override
std::string build_web_session_start_url (const std::string &next=std::string()) const
std::string build_web_session_complete_url (const std::string &token_hash, const std::string &state) const
std::string build_web_session_signout_url () const
int request_web_session_handoff (std::string &token_hash, std::string &error_code, unsigned int &http_code)
int connect_server () override
bool is_server_connected () override
int refresh_connection () override
void stop_health_check ()
bool is_refresh_running () const
int start_subscribe (std::string module) override
int stop_subscribe (std::string module) override
int add_subscribe (std::vector< std::string > dev_list) override
int del_subscribe (std::vector< std::string > dev_list) override
void enable_multi_machine (bool enable) override
int get_user_presets (std::map< std::string, std::map< std::string, std::string > > *user_presets) override
std::string request_setting_id (std::string name, std::map< std::string, std::string > *values_map, unsigned int *http_code) override
int put_setting (std::string setting_id, std::string name, std::map< std::string, std::string > *values_map, unsigned int *http_code, bool force=false) override
SyncPushResult sync_push (const std::string &profile_id, const std::string &name, const nlohmann::json &content, const std::string &original_updated_time="", bool force=false)
int get_setting_list (std::string bundle_version, ProgressFn pro_fn=nullptr, WasCancelledFn cancel_fn=nullptr) override
int get_setting_list2 (std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn=nullptr, WasCancelledFn cancel_fn=nullptr) override
int delete_setting (std::string setting_id) override
int get_my_message (int type, int after, int limit, unsigned int *http_code, std::string *http_body) override
int check_user_task_report (int *task_id, bool *printable) override
int get_user_print_info (unsigned int *http_code, std::string *http_body) override
int get_user_tasks (TaskQueryParams params, std::string *http_body) override
int get_printer_firmware (std::string dev_id, unsigned *http_code, std::string *http_body) override
int get_task_plate_index (std::string task_id, int *plate_index) override
int get_user_info (int *identifier) override
int get_subtask_info (std::string subtask_id, std::string *task_json, unsigned int *http_code, std::string *http_body) override
int get_slice_info (std::string project_id, std::string profile_id, int plate_index, std::string *slice_json) override
int query_bind_status (std::vector< std::string > query_list, unsigned int *http_code, std::string *http_body) override
int modify_printer_name (std::string dev_id, std::string dev_name) override
int get_camera_url (std::string dev_id, std::function< void(std::string)> callback) override
int get_design_staffpick (int offset, int limit, std::function< void(std::string)> callback) override
int start_publish (PublishParams params, OnUpdateStatusFn update_fn, WasCancelledFn cancel_fn, std::string *out) override
int get_model_publish_url (std::string *url) override
int get_subtask (BBLModelTask *task, OnGetSubTaskFn getsub_fn) override
int get_model_mall_home_url (std::string *url) override
int get_model_mall_detail_url (std::string *url, std::string id) override
int get_my_profile (std::string token, unsigned int *http_code, std::string *http_body) override
int get_my_token (std::string ticket, unsigned int *http_code, std::string *http_body) override
int track_enable (bool enable) override
int track_remove_files () override
int track_event (std::string evt_key, std::string content) override
int track_header (std::string header) override
int track_update_property (std::string name, std::string value, std::string type="string") override
int track_get_property (std::string name, std::string &value, std::string type="string") override
bool get_track_enable () override
int 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) override
int get_oss_config (std::string &config, std::string country_code, unsigned int &http_code, std::string &http_error) override
int 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) override
int get_model_mall_rating_result (int job_id, std::string &rating_result, unsigned int &http_code, std::string &http_error) override
int get_mw_user_preference (std::function< void(std::string)> callback) override
int get_mw_user_4ulist (int seed, int limit, std::function< void(std::string)> callback) override
std::string get_version () override
int set_on_server_connected_fn (AppOnServerConnectedFn fn) override
int set_on_http_error_fn (AppOnHttpErrorFn fn) override
int set_get_country_code_fn (GetCountryCodeFn fn) override
int set_queue_on_main_fn (QueueOnMainFn fn) override
void load_sync_state ()
void save_sync_state ()
void clear_sync_state ()
const SyncState & get_sync_state () const
bool unsubscribe_bundle (const std::string &bundle_id)
std::string get_bundle_url (const std::string &bundle_id) const
int get_subscribed_bundles (std::vector< std::pair< std::string, std::string > > *bundles, std::vector< std::string > &notfound, std::vector< std::string > &unauthorized)
int get_shared_bundle (const std::string &bundle_id, std::map< std::string, std::map< std::string, std::string > > *presets, BundleMetadata *bundle_metadata)
int fetch_subscribed_manifests_into_descriptors (std::vector< PluginDescriptor > &descriptors, std::vector< std::string > &not_found, std::vector< std::string > &unauthorized)
int fetch_mine_manifests_into_descriptors (std::vector< PluginDescriptor > &descriptors)
int get_plugin_download_url (const std::string &uuid, const std::string &requested_version, std::vector< PluginDownloadData > &data, std::vector< PluginDownloadNotFound > &not_found, std::vector< std::string > &unauthorized)
std::string get_plugin_url (const std::string &sharing_token) const
int subscribe_plugin (const std::string &plugin_uuid)
int unsubscribe_plugins (const std::vector< std::string > &plugin_uuids)
int fetch_plugin_changelogs (const std::vector< std::string > &uuids, std::unordered_map< std::string, std::vector< PluginChangelog > > &changelog)
void set_session_handler (SessionHandler handler)
void set_on_login_complete_handler (OnLoginCompleteHandler handler)
const PkceBundle & pkce ()
void regenerate_pkce ()
void persist_user_secret (const std::string &secret)
bool load_user_secret (std::string &out_secret)
void clear_user_secret (bool all_backends=false)
bool refresh_if_expiring (std::chrono::seconds skew, const std::string &reason)
RefreshResult refresh_from_storage (const std::string &reason, bool async=false)
RefreshResult refresh_now (const std::string &refresh_token, const std::string &reason, bool async=false)
RefreshResult refresh_session_with_token (const std::string &refresh_token, const std::string &reason="")
bool set_user_session (const std::string &token, const std::string &user_id, const std::string &username, const std::string &nickname, const std::string &avatar, const std::string &refresh_token="", bool persist=true)
bool set_user_session (const nlohmann::json &session_json, bool notify_login=true)
void clear_session (bool all_backends=false)
virtual std::unique_ptr< ICameraSignalingChannel > create_camera_signaling_channel (const std::string &dev_id)

Static Public Member Functions

static std::string generate_uuid_for_setting_id (const std::string &name, const std::string &user_id="")

Private Member Functions

int sync_pull (std::function< void(const SyncPullResponse &)> on_success, std::function< void(int http_code, const std::string &error)> on_error)
bool resolve_unauthorized (HttpResult &res, const std::function< HttpResult()> &perform, const std::string &reason)
int http_get (const std::string &path, std::string *response_body, unsigned int *http_code)
int http_post (const std::string &path, const std::string &body, std::string *response_body, unsigned int *http_code)
int http_put (const std::string &path, const std::string &body, std::string *response_body, unsigned int *http_code)
int http_delete (const std::string &path, std::string *response_body, unsigned int *http_code)
std::map< std::string, std::string > data_headers ()
RefreshResult attempt_refresh_after_unauthorized (const std::string &reason)
bool http_post_token (const std::string &body, std::string *response_body, unsigned int *http_code, const std::string &url="")
bool http_post_auth (const std::string &path, const std::string &body, std::string *response_body, unsigned int *http_code)
bool exchange_auth_code (const std::string &auth_code, const std::string &state, std::string &session_payload)
void update_redirect_uri ()
void compute_fallback_path ()
bool decode_jwt_expiry (const std::string &token, std::chrono::system_clock::time_point &out_tp)
bool should_refresh_locked (std::chrono::seconds skew) const
bool run_health_check (const std::atomic_bool *cancel)
void invoke_server_connected_callback (int return_code, int reason_code)
void invoke_http_error_callback (unsigned http_code, const std::string &http_body)
std::string map_to_json (const std::map< std::string, std::string > &map)
void json_to_map (const std::string &json, std::map< std::string, std::string > &map)
std::string token_lock_path () const

Private Attributes

std::string log_dir
std::string config_dir
std::string api_base_url
std::string auth_base_url
std::string cloud_base_url
std::string country_code
std::map< std::string, std::string > extra_headers
std::map< std::string, std::string > auth_headers
std::mutex headers_mutex
bool m_use_encrypted_token_file {false}
PkceBundle pkce_bundle
std::string secret_fallback_path
std::atomic_bool secret_stored {false}
SessionHandler session_handler
OnLoginCompleteHandler on_login_complete_handler
SessionInfo session
std::mutex session_mutex
std::atomic< long long > last_refresh_success_epoch {0}
const long long agent_start_epoch
bool is_connected {false}
bool enable_track {false}
bool multi_machine_enabled {false}
SyncState sync_state
std::string sync_state_path
AppOnServerConnectedFn on_server_connected_fn
AppOnHttpErrorFn on_http_error_fn
GetCountryCodeFn get_country_code_fn
QueueOnMainFn queue_on_main_fn
std::mutex callback_mutex
std::recursive_mutex state_mutex
std::thread refresh_thread
std::atomic_bool refresh_running {false}
std::thread health_check_thread
std::atomic_bool health_check_running {false}
std::atomic_bool health_check_stopped {false}

Detailed Description

OrcaCloudServiceAgent - Native cloud service and authentication implementation for Orca Cloud.

Implements the ICloudServiceAgent interface with:

  • Full OAuth 2.0 PKCE authentication support
  • Token storage via wxSecretStore with AES-256-GCM encrypted file fallback
  • JWT expiry decoding and proactive token refresh
  • Session management with thread-safe state access
  • Settings synchronization (sync_pull, sync_push)
  • Server connectivity management
  • HTTP helpers with automatic token injection

This class combines the functionality of the former OrcaAuthAgent and OrcaCloudServiceAgent.


Class Documentation

◆ Slic3r::OrcaCloudServiceAgent::SessionInfo

struct Slic3r::OrcaCloudServiceAgent::SessionInfo
Collaboration diagram for Slic3r::OrcaCloudServiceAgent::SessionInfo:
Class Members
string access_token
time_point expires_at {}
bool logged_in = false
string refresh_token
string user_avatar
string user_id
string user_name
string user_nickname

◆ Slic3r::OrcaCloudServiceAgent::PkceBundle

struct Slic3r::OrcaCloudServiceAgent::PkceBundle
Collaboration diagram for Slic3r::OrcaCloudServiceAgent::PkceBundle:
Class Members
string challenge
int loopback_port = auth_constants::LOOPBACK_PORT
string redirect
string state
string verifier

◆ Slic3r::OrcaCloudServiceAgent::HttpResult

struct Slic3r::OrcaCloudServiceAgent::HttpResult
Collaboration diagram for Slic3r::OrcaCloudServiceAgent::HttpResult:
Class Members
string body
unsigned int status {0}
bool success {false}

Member Typedef Documentation

◆ OnLoginCompleteHandler

using Slic3r::OrcaCloudServiceAgent::OnLoginCompleteHandler = std::function<void(bool success, const std::string& user_id)>

◆ SessionHandler

using Slic3r::OrcaCloudServiceAgent::SessionHandler = std::function<bool(const std::string&)>

Constructor & Destructor Documentation

◆ OrcaCloudServiceAgent()

Slic3r::OrcaCloudServiceAgent::OrcaCloudServiceAgent ( std::string log_dir)
explicit
517 : log_dir(std::move(log_dir))
518 , api_base_url(ORCA_DEFAULT_API_URL)
519 , auth_base_url(ORCA_DEFAULT_AUTH_URL)
520 , cloud_base_url(ORCA_DEFAULT_CLOUD_URL)
521{
523 pkce_bundle.loopback_port = choose_loopback_port();
527}
PkceBundle pkce_bundle
Definition OrcaCloudServiceAgent.hpp:449
std::string api_base_url
Definition OrcaCloudServiceAgent.hpp:439
void regenerate_pkce()
Definition OrcaCloudServiceAgent.cpp:1634
std::string auth_base_url
Definition OrcaCloudServiceAgent.hpp:440
void compute_fallback_path()
Definition OrcaCloudServiceAgent.cpp:2610
std::map< std::string, std::string > auth_headers
Definition OrcaCloudServiceAgent.hpp:444
std::string log_dir
Definition OrcaCloudServiceAgent.hpp:437
void update_redirect_uri()
Definition OrcaCloudServiceAgent.cpp:1644
std::string cloud_base_url
Definition OrcaCloudServiceAgent.hpp:441
int choose_loopback_port()
Definition OrcaCloudServiceAgent.cpp:394
constexpr const char * ORCA_DEFAULT_PUB_KEY
Definition OrcaCloudServiceAgent.cpp:91

References api_base_url, auth_base_url, auth_headers, cloud_base_url, compute_fallback_path(), log_dir, pkce_bundle, regenerate_pkce(), and update_redirect_uri().

Here is the call graph for this function:

◆ ~OrcaCloudServiceAgent()

Slic3r::OrcaCloudServiceAgent::~OrcaCloudServiceAgent ( )
override
530{
532 if (refresh_thread.joinable()) {
533 refresh_thread.join();
534 }
535}
void stop_health_check()
Definition OrcaCloudServiceAgent.cpp:1061
std::thread refresh_thread
Definition OrcaCloudServiceAgent.hpp:485

References refresh_thread, and stop_health_check().

Here is the call graph for this function:

Member Function Documentation

◆ add_subscribe()

int Slic3r::OrcaCloudServiceAgent::add_subscribe ( std::vector< std::string > dev_list)
overridevirtual

Subscribe to push streams for specific device identifiers.

Implements Slic3r::ICloudServiceAgent.

1081{
1082 (void) dev_list;
1083 return BAMBU_NETWORK_SUCCESS;
1084}
#define BAMBU_NETWORK_SUCCESS
Definition bambu_networking.hpp:16

References BAMBU_NETWORK_SUCCESS.

◆ attempt_refresh_after_unauthorized()

RefreshResult Slic3r::OrcaCloudServiceAgent::attempt_refresh_after_unauthorized ( const std::string & reason)
private
2168{
2169 RefreshResult r = refresh_from_storage(reason, false);
2170 if (r != RefreshResult::Transient) return r; // Success or AuthRejected: decided, no retry
2171
2172 // Only a transient (network/server) failure is worth retrying.
2173 std::this_thread::sleep_for(std::chrono::milliseconds(500));
2174 r = refresh_from_storage(reason + "_retry", false);
2175
2176 if (r == RefreshResult::Transient)
2177 BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh result=transient source=" << reason << " action=keep_session";
2178 else if (r == RefreshResult::AuthRejected)
2179 BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh result=rejected source=" << reason << " action=logout";
2180 return r;
2181}
RefreshResult refresh_from_storage(const std::string &reason, bool async=false)
Definition OrcaCloudServiceAgent.cpp:1881
RefreshResult
Definition OrcaCloudServiceAgent.hpp:49
@ AuthRejected
Definition OrcaCloudServiceAgent.hpp:51
@ Transient
Definition OrcaCloudServiceAgent.hpp:52

References Slic3r::AuthRejected, refresh_from_storage(), and Slic3r::Transient.

Referenced by resolve_unauthorized().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ build_login_cmd()

std::string Slic3r::OrcaCloudServiceAgent::build_login_cmd ( )
overridevirtual

Build a JSON command for the WebView login flow. Contains backend URL, API key, and PKCE parameters.

Implements Slic3r::ICloudServiceAgent.

884{
885 // When already signed in, emit the homepage payload so the web UI
886 // can flip to the logged-in state without re-opening the login flow.
887 if (is_user_login()) {
888 std::string display_name = get_user_nickname();
889 if (display_name.empty()) {
890 display_name = _u8L("unknown name");
891 }
892 json cmd;
893 cmd["command"] = "orca_userlogin";
894 cmd["data"]["name"] = display_name;
895 cmd["data"]["avatar"] = get_user_avatar();
896 // The unique handle, shown under the display name in the homepage account menu.
897 cmd["data"]["account"] = get_user_name();
898 return cmd.dump();
899 }
900
903 const auto bundle = pkce();
904
905 json tree;
906 tree["action"] = "login_config";
907 tree["backend_url"] = auth_base_url;
908
909 // Include API key for direct Supabase calls from JavaScript
910 {
911 std::lock_guard<std::recursive_mutex> lock(state_mutex);
912 auto it = auth_headers.find("apikey");
913 if (it != auth_headers.end()) {
914 tree["apikey"] = it->second;
915 }
916 }
917
918 tree["pkce"]["code_challenge"] = bundle.challenge;
919 tree["pkce"]["code_challenge_method"] = "S256";
920 tree["pkce"]["state"] = bundle.state;
921 tree["pkce"]["redirect_uri"] = bundle.redirect;
922 tree["pkce"]["code_verifier"] = bundle.verifier;
923 tree["pkce"]["loopback_port"] = bundle.loopback_port;
924
925 return tree.dump();
926}
std::recursive_mutex state_mutex
Definition OrcaCloudServiceAgent.hpp:484
const PkceBundle & pkce()
Definition OrcaCloudServiceAgent.cpp:1626
std::string get_user_avatar() override
Definition OrcaCloudServiceAgent.cpp:867
std::string get_user_nickname() override
Definition OrcaCloudServiceAgent.cpp:873
bool is_user_login() override
Definition OrcaCloudServiceAgent.cpp:814
std::string get_user_name() override
Definition OrcaCloudServiceAgent.cpp:861
nlohmann::json json
Definition AssembleList.cpp:42
@ lock
Definition GLGizmoEmboss.cpp:209
#define _u8L(s)
Definition I18N.hpp:11

References _u8L, auth_base_url, auth_headers, get_user_avatar(), get_user_name(), get_user_nickname(), is_user_login(), pkce(), regenerate_pkce(), state_mutex, and update_redirect_uri().

Here is the call graph for this function:

◆ build_login_info()

std::string Slic3r::OrcaCloudServiceAgent::build_login_info ( )
overridevirtual

Return a JSON snapshot of the active session (user info, no tokens). Used by WebView to display current user state.

Implements Slic3r::ICloudServiceAgent.

931{
932 json tree;
933 {
934 std::lock_guard<std::mutex> lock(session_mutex);
935 tree["user_id"] = session.user_id;
936 tree["user_name"] = session.user_name;
937 tree["nickname"] = session.user_nickname;
938 tree["avatar"] = session.user_avatar;
939 tree["logged_in"] = session.logged_in;
940 }
941 // Do not expose tokens to the WebView
942 tree["access_token"] = "";
943 tree["refresh_token"] = "";
944 tree["backend_url"] = api_base_url;
945 tree["auth_url"] = auth_base_url;
946
947 return tree.dump();
948}
SessionInfo session
Definition OrcaCloudServiceAgent.hpp:458
std::mutex session_mutex
Definition OrcaCloudServiceAgent.hpp:459

References api_base_url, auth_base_url, session, and session_mutex.

◆ build_logout_cmd()

std::string Slic3r::OrcaCloudServiceAgent::build_logout_cmd ( )
overridevirtual

Build a JSON command for WebView logout.

Implements Slic3r::ICloudServiceAgent.

928{ return json{{"command", "orca_useroffline"}}.dump(); }

◆ build_web_session_complete_url()

std::string Slic3r::OrcaCloudServiceAgent::build_web_session_complete_url ( const std::string & token_hash,
const std::string & state ) const
3073{
3074 if (cloud_base_url.empty() || token_hash.empty() || state.empty())
3075 return "";
3076
3077 // Fragment (not query) so the one-time token never reaches a server or a log.
3078 return cloud_base_url + "/auth/handoff#token_hash=" + percent_encode_unreserved(token_hash) +
3079 "&state=" + percent_encode_unreserved(state);
3080}
static std::string percent_encode_unreserved(const std::string &s)
Definition OrcaCloudServiceAgent.cpp:3037

References cloud_base_url, and Slic3r::percent_encode_unreserved().

Here is the call graph for this function:

◆ build_web_session_signout_url()

std::string Slic3r::OrcaCloudServiceAgent::build_web_session_signout_url ( ) const
3083{
3084 if (cloud_base_url.empty())
3085 return "";
3086 return cloud_base_url + "/auth/handoff?signout=1";
3087}

References cloud_base_url.

◆ build_web_session_start_url()

std::string Slic3r::OrcaCloudServiceAgent::build_web_session_start_url ( const std::string & next = std::string()) const
3056{
3057 if (cloud_base_url.empty())
3058 return "";
3059
3060 // The page replaces a `next` that is not a same-site path with /app/profiles, so pass a
3061 // same-site path (the guide's default) and let it validate.
3062 std::string url = cloud_base_url + "/auth/handoff?next=" +
3063 percent_encode_unreserved(next.empty() ? "/app/profiles" : next);
3064
3065 std::lock_guard<std::mutex> lock(session_mutex);
3066 if (session.logged_in && !session.user_id.empty())
3067 url += "&user_id=" + percent_encode_unreserved(session.user_id);
3068 return url;
3069}

References cloud_base_url, Slic3r::percent_encode_unreserved(), session, and session_mutex.

Here is the call graph for this function:

◆ change_user()

int Slic3r::OrcaCloudServiceAgent::change_user ( std::string user_info)
overridevirtual

Authenticate the user with the provided JSON payload.

Supported formats:

  1. Traditional: {"username": "...", "password": "..."}
  2. WebView/OAuth: {"command": "user_login", "data": {...}}
  3. Token format: {"data": {"token": "...", "refresh_token": "...", "user": {...}}}

Implements Slic3r::ICloudServiceAgent.

739{
740 try {
741 auto tree = json::parse(user_info);
742
743 auto safe_str = [](const json& j, const std::string& key) -> std::string {
744 if (j.contains(key) && j[key].is_string())
745 return j[key].get<std::string>();
746 return "";
747 };
748
749 // Check if this is a WebView login message (PKCE flow completion)
750 std::string command = safe_str(tree, "command");
751 if (command == "user_login") {
752 if (!tree.contains("data") || !tree["data"].is_object()) {
753 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: WebView login payload missing data field";
755 }
756 const auto& data = tree["data"];
757 std::string state = safe_str(data, "state");
758
759 // Check for auth code (PKCE authorization code flow)
760 std::string auth_code = safe_str(data, "code");
761 if (!auth_code.empty()) {
762 std::string session_payload;
763 if (!exchange_auth_code(auth_code, state, session_payload)) {
765 }
766 // Recursively process the session payload (contains access_token, user, etc.)
767 return change_user(session_payload);
768 }
769
770 // Validate PKCE state
771 const auto expected_state = pkce_bundle.state;
772 if (!expected_state.empty() && state != expected_state) {
773 BOOST_LOG_TRIVIAL(warning) << "[auth] event=login result=failure reason=state_mismatch";
775 }
776
777 // Direct token flow (tokens already obtained by WebView)
778 bool success = set_user_session(data);
779 BOOST_LOG_TRIVIAL(info) << "[auth] event=login result=" << (success ? "success" : "failure") << " source=webview";
781 }
782
783 // Orca cloud session payload (default flow)
784 const json* session_node = nullptr;
785 if (tree.contains("data") && tree["data"].is_object()) {
786 const auto& data = tree["data"];
787 if (data.contains("session") && data["session"].is_object()) {
788 session_node = &data["session"];
789 } else if (data.contains("access_token") || data.contains("token")) {
790 session_node = &data;
791 }
792 }
793 if (!session_node) {
794 if (tree.contains("session") && tree["session"].is_object()) {
795 session_node = &tree["session"];
796 } else if (tree.contains("access_token") || tree.contains("token")) {
797 session_node = &tree;
798 }
799 }
800
801 if (session_node) {
803 }
804
805 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: Username/password login is disabled. Use the Orca cloud PKCE flow.";
807
808 } catch (const std::exception& e) {
809 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: change_user exception - " << e.what();
811 }
812}
#define BAMBU_NETWORK_ERR_INVALID_HANDLE
Definition bambu_networking.hpp:17
#define BAMBU_NETWORK_ERR_INVALID_RESULT
Definition bambu_networking.hpp:35
int change_user(std::string user_info) override
Definition OrcaCloudServiceAgent.cpp:738
bool exchange_auth_code(const std::string &auth_code, const std::string &state, std::string &session_payload)
Definition OrcaCloudServiceAgent.cpp:697
bool set_user_session(const std::string &token, const std::string &user_id, const std::string &username, const std::string &nickname, const std::string &avatar, const std::string &refresh_token="", bool persist=true)
Definition OrcaCloudServiceAgent.cpp:2058

References BAMBU_NETWORK_ERR_INVALID_HANDLE, BAMBU_NETWORK_ERR_INVALID_RESULT, BAMBU_NETWORK_SUCCESS, change_user(), exchange_auth_code(), pkce_bundle, and set_user_session().

Referenced by change_user().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ check_user_task_report()

int Slic3r::OrcaCloudServiceAgent::check_user_task_report ( int * task_id,
bool * printable )
overridevirtual

Check for pending task reports.

Implements Slic3r::ICloudServiceAgent.

2744{
2745 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: check_user_task_report (stub)";
2746 if (task_id)
2747 *task_id = 0;
2748 if (printable)
2749 *printable = false;
2750 return BAMBU_NETWORK_SUCCESS;
2751}

References BAMBU_NETWORK_SUCCESS.

◆ clear_session()

void Slic3r::OrcaCloudServiceAgent::clear_session ( bool all_backends = false)
2155{
2156 {
2157 std::lock_guard<std::mutex> lock(session_mutex);
2158 session = SessionInfo{};
2159 }
2160 clear_user_secret(all_backends);
2161}
void clear_user_secret(bool all_backends=false)
Definition OrcaCloudServiceAgent.cpp:1783
Definition OrcaCloudServiceAgent.hpp:129

References clear_user_secret(), session, and session_mutex.

Referenced by user_logout().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ clear_sync_state()

void Slic3r::OrcaCloudServiceAgent::clear_sync_state ( )
1609{
1610 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1611 sync_state = SyncState{};
1612 if (!sync_state_path.empty() && boost::filesystem::exists(sync_state_path)) {
1613 boost::filesystem::remove(sync_state_path);
1614 }
1615}
SyncState sync_state
Definition OrcaCloudServiceAgent.hpp:473
std::string sync_state_path
Definition OrcaCloudServiceAgent.hpp:474

References state_mutex, sync_state, and sync_state_path.

Referenced by sync_pull().

Here is the caller graph for this function:

◆ clear_user_secret()

void Slic3r::OrcaCloudServiceAgent::clear_user_secret ( bool all_backends = false)
1784{
1785 // Nothing this process loaded or saved: leave the store alone. Deleting would only cost a
1786 // keychain round trip (or a hang while the keychain is unresponsive) and could remove a
1787 // login another instance just saved.
1788 if (!secret_stored.exchange(false) && !all_backends) {
1789 return;
1790 }
1791
1792 if (all_backends || !m_use_encrypted_token_file) {
1793 wxSecretStore store = wxSecretStore::GetDefault();
1794 if (store.IsOk()) {
1795 store.Delete(SECRET_STORE_SERVICE);
1796 }
1797 }
1798
1800 if (!secret_fallback_path.empty() && wxFileExists(wxString::FromUTF8(secret_fallback_path.c_str()))) {
1801 wxRemoveFile(wxString::FromUTF8(secret_fallback_path.c_str()));
1802 }
1803}
bool m_use_encrypted_token_file
Definition OrcaCloudServiceAgent.hpp:446
std::string secret_fallback_path
Definition OrcaCloudServiceAgent.hpp:450
std::atomic_bool secret_stored
Definition OrcaCloudServiceAgent.hpp:455
bool store(const Facenames &facenames)
Definition GLGizmoEmboss.cpp:3560

References compute_fallback_path(), m_use_encrypted_token_file, secret_fallback_path, and secret_stored.

Referenced by clear_session(), and persist_user_secret().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ compute_fallback_path()

void Slic3r::OrcaCloudServiceAgent::compute_fallback_path ( )
private
2611{
2612 if (!secret_fallback_path.empty())
2613 return;
2614 // wxStandardPaths::GetUserDataDir() resolves the app data directory via
2615 // wxAppConsoleBase::GetAppName(), which dereferences wxTheApp. In headless
2616 // contexts (CLI, unit tests) there is no wxApp, so guard the call to avoid a
2617 // null dereference. The path can still be provided explicitly through
2618 // set_config_dir(); when it is left empty, file persistence is skipped.
2619 if (wxTheApp == nullptr)
2620 return;
2621 wxFileName fallback(wxStandardPaths::Get().GetUserDataDir(), "orca_refresh_token.sec");
2622 fallback.MakeAbsolute();
2623 secret_fallback_path = fallback.GetFullPath().ToStdString();
2624}

References secret_fallback_path.

Referenced by OrcaCloudServiceAgent(), clear_user_secret(), load_user_secret(), and persist_user_secret().

Here is the caller graph for this function:

◆ configure_urls()

void Slic3r::OrcaCloudServiceAgent::configure_urls ( AppConfig * app_config)
556{
557 if (!app_config)
558 return;
559
560 // Read token storage preference
562
563 std::string api_url = app_config->get(CONFIG_ORCA_API_URL);
564 if (!api_url.empty()) {
565 api_base_url = api_url;
566 }
567
568 std::string auth_url = app_config->get(CONFIG_ORCA_AUTH_URL);
569 if (!auth_url.empty()) {
570 auth_base_url = auth_url;
571 }
572
573 std::string cloud_url = app_config->get(CONFIG_ORCA_CLOUD_URL);
574 if (!cloud_url.empty()) {
575 cloud_base_url = cloud_url;
576 }
577
578 std::string pub_key = app_config->get(CONFIG_ORCA_PUB_KEY);
579 if (!pub_key.empty()) {
580 auth_headers["apikey"] = pub_key;
581 }
582}
#define SETTING_USE_ENCRYPTED_TOKEN_FILE
Definition AppConfig.hpp:32

References api_base_url, auth_base_url, auth_headers, cloud_base_url, Slic3r::AppConfig::get(), Slic3r::AppConfig::get_bool(), m_use_encrypted_token_file, and SETTING_USE_ENCRYPTED_TOKEN_FILE.

Here is the call graph for this function:

◆ connect_server()

int Slic3r::OrcaCloudServiceAgent::connect_server ( )
overridevirtual

Perform a health check against the configured backend. Updates is_server_connected() state and triggers OnServerConnectedFn.

Implements Slic3r::ICloudServiceAgent.

1019{
1021}
#define BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED
Definition bambu_networking.hpp:97
bool run_health_check(const std::atomic_bool *cancel)
Definition OrcaCloudServiceAgent.cpp:975

References BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED, BAMBU_NETWORK_SUCCESS, and run_health_check().

Here is the call graph for this function:

◆ create_camera_signaling_channel()

virtual std::unique_ptr< ICameraSignalingChannel > Slic3r::ICloudServiceAgent::create_camera_signaling_channel ( const std::string & dev_id)
inlinevirtualinherited

Create a camera signaling channel object for P2P camera streaming over WebRTC.

340 {
341 (void) dev_id;
342 return nullptr;
343 }

◆ data_headers()

std::map< std::string, std::string > Slic3r::OrcaCloudServiceAgent::data_headers ( )
private
2184{
2185 std::scoped_lock lock(state_mutex, headers_mutex);
2186 auto headers = auth_headers;
2187 for (const auto& pair : extra_headers) {
2188 headers[pair.first] = pair.second;
2189 }
2190 return headers;
2191}
std::map< std::string, std::string > extra_headers
Definition OrcaCloudServiceAgent.hpp:443
std::mutex headers_mutex
Definition OrcaCloudServiceAgent.hpp:445

References auth_headers, extra_headers, headers_mutex, and state_mutex.

Referenced by http_delete(), http_get(), http_post(), http_put(), request_web_session_handoff(), and run_health_check().

Here is the caller graph for this function:

◆ decode_jwt_expiry()

bool Slic3r::OrcaCloudServiceAgent::decode_jwt_expiry ( const std::string & token,
std::chrono::system_clock::time_point & out_tp )
private
1821{
1822 out_tp = {};
1823 if (token.empty())
1824 return false;
1825
1826 auto first = token.find('.');
1827 auto second = token.find('.', first == std::string::npos ? 0 : first + 1);
1828 if (first == std::string::npos || second == std::string::npos)
1829 return false;
1830
1831 std::string payload_b64 = token.substr(first + 1, second - first - 1);
1832 std::vector<unsigned char> payload_bytes;
1833 if (!base64url_decode(payload_b64, payload_bytes))
1834 return false;
1835
1836 std::string payload_str(payload_bytes.begin(), payload_bytes.end());
1837 try {
1838 auto payload = json::parse(payload_str);
1839 if (payload.contains("exp") && payload["exp"].is_number()) {
1840 out_tp = std::chrono::system_clock::time_point{std::chrono::seconds(payload["exp"].get<long long>())};
1841 return true;
1842 }
1843 } catch (const std::exception& e) {
1844 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: failed to decode JWT exp - " << e.what();
1845 }
1846 return false;
1847}
bool base64url_decode(const std::string &input, std::vector< unsigned char > &out)
Definition OrcaCloudServiceAgent.cpp:233

Referenced by set_user_session().

Here is the caller graph for this function:

◆ del_subscribe()

int Slic3r::OrcaCloudServiceAgent::del_subscribe ( std::vector< std::string > dev_list)
overridevirtual

Remove device-level subscriptions.

Implements Slic3r::ICloudServiceAgent.

1087{
1088 (void) dev_list;
1089 return BAMBU_NETWORK_SUCCESS;
1090}

References BAMBU_NETWORK_SUCCESS.

◆ delete_setting()

int Slic3r::OrcaCloudServiceAgent::delete_setting ( std::string setting_id)
overridevirtual

Delete a remote preset.

Implements Slic3r::ICloudServiceAgent.

1383{
1384 std::string path = std::string(ORCA_SYNC_DELETE_PATH) + "?resource=" + ORCA_SYNC_PROFILE_TABLE + "&id=" + setting_id;
1385 std::string response;
1386 unsigned int http_code = 0;
1387
1388 int result = http_delete(path, &response, &http_code);
1389
1390 // Treat 204 as success: the setting is already gone from cloud, which is our goal.
1391 if (http_code == 204) {
1392 return BAMBU_NETWORK_SUCCESS;
1393 }
1394
1395 if (result != BAMBU_NETWORK_SUCCESS || http_code >= 400) {
1397 }
1398
1399 return BAMBU_NETWORK_SUCCESS;
1400}
#define BAMBU_NETWORK_ERR_DEL_SETTING_FAILED
Definition bambu_networking.hpp:26
int http_delete(const std::string &path, std::string *response_body, unsigned int *http_code)
Definition OrcaCloudServiceAgent.cpp:2398
constexpr const char * ORCA_SYNC_PROFILE_TABLE
Definition OrcaCloudServiceAgent.cpp:101

References BAMBU_NETWORK_ERR_DEL_SETTING_FAILED, BAMBU_NETWORK_SUCCESS, and http_delete().

Here is the call graph for this function:

◆ enable_multi_machine()

void Slic3r::OrcaCloudServiceAgent::enable_multi_machine ( bool enable)
overridevirtual

Enable or disable multi-machine mode.

Implements Slic3r::ICloudServiceAgent.

1093{
1094 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1095 multi_machine_enabled = enable;
1096}
bool multi_machine_enabled
Definition OrcaCloudServiceAgent.hpp:470

References multi_machine_enabled, and state_mutex.

◆ ensure_token_fresh()

bool Slic3r::OrcaCloudServiceAgent::ensure_token_fresh ( const std::string & reason)
overridevirtual

Ensure the access token is fresh, refreshing if necessary. Call before making API requests to avoid 401 errors.

Parameters
reasonDescriptive string for logging (e.g., "connect_server")
Returns
true if the token is fresh or was successfully refreshed

Implements Slic3r::ICloudServiceAgent.

966{ return refresh_if_expiring(TOKEN_REFRESH_SKEW, reason); }
bool refresh_if_expiring(std::chrono::seconds skew, const std::string &reason)
Definition OrcaCloudServiceAgent.cpp:1918

References refresh_if_expiring().

Referenced by http_delete(), http_get(), http_post(), http_put(), and request_web_session_handoff().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ exchange_auth_code()

bool Slic3r::OrcaCloudServiceAgent::exchange_auth_code ( const std::string & auth_code,
const std::string & state,
std::string & session_payload )
private
698{
699 const auto expected_state = pkce_bundle.state;
700 if (expected_state.empty()) {
701 BOOST_LOG_TRIVIAL(warning) << "[auth] event=code_exchange result=failure reason=no_expected_state";
702 return false;
703 }
704 if (state != expected_state) {
705 BOOST_LOG_TRIVIAL(warning) << "[auth] event=code_exchange result=failure reason=state_mismatch";
706 return false;
707 }
708
709 std::string code_verifier;
710 {
711 std::lock_guard<std::recursive_mutex> lock(state_mutex);
712 code_verifier = pkce_bundle.verifier;
713 }
714
715 // Exchange the Supabase PKCE code with the token endpoint.
716 // With redirect_to, GoTrue handles Google OAuth internally and redirects to localhost
717 // with a Supabase-generated auth_code (not a raw Google code). The grant_type goes
718 // in the URL query string (same pattern as the refresh_token flow).
719 const std::string token_url = auth_base_url + auth_constants::TOKEN_PATH + "?grant_type=pkce";
720 json body_j;
721 body_j["auth_code"] = auth_code;
722 body_j["code_verifier"] = code_verifier;
723
724 std::string response;
725 unsigned int http_code = 0;
726 const bool success = http_post_token(body_j.dump(), &response, &http_code, token_url);
727
728 if (!success || http_code >= 400) {
729 BOOST_LOG_TRIVIAL(error) << "[auth] event=code_exchange result=failure http_code=" << http_code << " body=" << response;
730 return false;
731 }
732
733 session_payload = response;
734 BOOST_LOG_TRIVIAL(info) << "[auth] event=code_exchange result=success";
735 return true;
736}
bool http_post_token(const std::string &body, std::string *response_body, unsigned int *http_code, const std::string &url="")
Definition OrcaCloudServiceAgent.cpp:2457
constexpr const char * TOKEN_PATH
Definition OrcaCloudServiceAgent.hpp:59

References auth_base_url, http_post_token(), pkce_bundle, state_mutex, and Slic3r::auth_constants::TOKEN_PATH.

Referenced by change_user().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ fetch_mine_manifests_into_descriptors()

int Slic3r::OrcaCloudServiceAgent::fetch_mine_manifests_into_descriptors ( std::vector< PluginDescriptor > & descriptors)
3517{
3518 constexpr int PAGE_LIMIT = 100;
3519 constexpr int MAX_PAGES = 1000; // safety backstop against a server that never stops paginating
3520
3521 int current_offset = 0;
3522 int page_count = 0;
3523
3524 while (true) {
3525 if (++page_count > MAX_PAGES) {
3526 BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": reached max page count (" << MAX_PAGES
3527 << "), stopping pagination";
3528 break;
3529 }
3530
3531 const std::string path = std::string(ORCA_PLUGINS_MINE) + "?limit=" + std::to_string(PAGE_LIMIT) +
3532 "&offset=" + std::to_string(current_offset) + "&include=changelog";
3533
3534 std::string response_body;
3535 unsigned int http_code = 0;
3536
3537 int result = http_get(path, &response_body, &http_code);
3538
3539 if (result != 0 || http_code != 200) {
3540 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: http_code=" << http_code;
3541 return result != 0 ? result : http_code;
3542 }
3543
3544 try {
3545 auto root = json::parse(response_body);
3546
3547 if (!root.contains("data") || !root["data"].is_array()) {
3548 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid response format, missing data array";
3549 return -1;
3550 }
3551
3552 const auto& data = root["data"];
3553 for (const auto& item : data) {
3554 PluginDescriptor descriptor;
3555 std::string uuid = get_json_string_field(item, "uuid");
3556 if (uuid.empty())
3557 uuid = get_json_string_field(item, "id");
3558
3559 descriptor.name = sanitize_plugin_name(get_json_string_field(item, "name"));
3560 descriptor.plugin_key = uuid;
3561 // Cloud API "description" is intentionally not parsed: descriptions come only from the
3562 // plugin's Python header once installed. Cloud-only rows show a "View on OrcaCloud" link.
3563 descriptor.author = parse_cloud_author(item);
3564 descriptor.version = get_json_string_field(item, "version");
3565 descriptor.latest_version = descriptor.version;
3566 // Cloud "type" is cosmetic (see parse_cloud_display_types); real capability_types are
3567 // populated only when the plugin is actually loaded. Leave them empty here.
3568 descriptor.display_types = parse_cloud_display_types(item);
3569 descriptor.entry_package = get_json_string_field(item, "entry_package");
3570 descriptor.sharing_token = get_json_string_field(item, "sharing_token");
3571 // main_image.access_url is pre-signed, so the WebView <img> can load it without the
3572 // Bearer token (content_url, an API path, would 401). Images only; display-only.
3573 if (item.contains("main_image") && item["main_image"].is_object() && cloud_media_is_image(item["main_image"]))
3574 descriptor.thumbnail_url = get_json_string_field(item["main_image"], "access_url");
3575 if (item.contains("changelog") && item["changelog"].is_array()) {
3576 for (const auto& changelog_item : item["changelog"]) {
3577 if (!changelog_item.is_object())
3578 continue;
3579
3580 PluginChangelog entry;
3581 entry.changelog_id = get_json_string_field(changelog_item, "id");
3582 entry.plugin_uuid = get_json_string_field(changelog_item, "plugin_id");
3583 if (entry.plugin_uuid.empty())
3584 entry.plugin_uuid = uuid;
3585 entry.version = get_json_string_field(changelog_item, "version");
3586 entry.changelog = get_json_string_field(changelog_item, "changelog");
3587 entry.created_time = get_json_int64_field(changelog_item, "created_time");
3588
3589 descriptor.changelog.push_back(std::move(entry));
3590 }
3591 sort_plugin_changelog(descriptor.changelog);
3592 }
3593
3594 descriptor.cloud = std::make_optional<CloudPluginState>({
3595 uuid,
3596 false,
3597 false,
3598 false,
3599 true
3600 });
3601
3602 descriptors.push_back(std::move(descriptor));
3603 }
3604
3605 // A short (or empty) page means there is nothing left to page through. This is the
3606 // primary guard against a server that keeps echoing a non-null next_offset forever.
3607 if (data.size() < static_cast<size_t>(PAGE_LIMIT))
3608 break;
3609
3610 // Honor an explicit end-of-pagination signal from the server.
3611 if (!root.contains("next_offset") || root["next_offset"].is_null())
3612 break;
3613
3614 // Require the offset to strictly advance; otherwise a stuck/regressing offset
3615 // would loop forever.
3616 const int next_offset = root["next_offset"].get<int>();
3617 if (next_offset <= current_offset) {
3618 BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": next_offset (" << next_offset
3619 << ") did not advance past " << current_offset << ", stopping pagination";
3620 break;
3621 }
3622 current_offset = next_offset;
3623 } catch (const std::exception& e) {
3624 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": JSON parse error: " << e.what();
3625 return -1;
3626 }
3627 }
3628
3629 BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loaded " << descriptors.size() << " manifests";
3630 return 0;
3631}
int http_get(const std::string &path, std::string *response_body, unsigned int *http_code)
Definition OrcaCloudServiceAgent.cpp:2211
std::string get_json_string_field(const json &j, const std::string &key)
Definition OrcaCloudServiceAgent.cpp:180
long long get_json_int64_field(const json &j, const std::string &key)
Definition OrcaCloudServiceAgent.cpp:188
std::string sanitize_plugin_name(std::string name)
Definition PluginDescriptor.hpp:187
void sort_plugin_changelog(std::vector< PluginChangelog > &changelog)
Definition PluginDescriptor.hpp:42
static std::string parse_cloud_author(const nlohmann::json &item)
Definition OrcaCloudServiceAgent.cpp:3414
static bool cloud_media_is_image(const nlohmann::json &main_image)
Definition OrcaCloudServiceAgent.cpp:3399
static std::vector< std::string > parse_cloud_display_types(const nlohmann::json &item)
Definition OrcaCloudServiceAgent.cpp:3373

References Slic3r::PluginDescriptor::author, Slic3r::PluginChangelog::changelog, Slic3r::PluginDescriptor::changelog, Slic3r::PluginChangelog::changelog_id, Slic3r::PluginDescriptor::cloud, Slic3r::cloud_media_is_image(), Slic3r::PluginChangelog::created_time, Slic3r::PluginDescriptor::display_types, Slic3r::PluginDescriptor::entry_package, http_get(), Slic3r::PluginDescriptor::latest_version, Slic3r::PluginDescriptor::name, Slic3r::parse_cloud_author(), Slic3r::parse_cloud_display_types(), Slic3r::PluginDescriptor::plugin_key, Slic3r::PluginChangelog::plugin_uuid, Slic3r::sanitize_plugin_name(), Slic3r::PluginDescriptor::sharing_token, Slic3r::sort_plugin_changelog(), Slic3r::PluginDescriptor::thumbnail_url, Slic3r::PluginChangelog::version, and Slic3r::PluginDescriptor::version.

Here is the call graph for this function:

◆ fetch_plugin_changelogs()

int Slic3r::OrcaCloudServiceAgent::fetch_plugin_changelogs ( const std::vector< std::string > & uuids,
std::unordered_map< std::string, std::vector< PluginChangelog > > & changelog )
3824{
3825 changelog.clear();
3826
3827 if (uuids.empty())
3828 return 0;
3829
3830 int first_error = 0;
3831
3832 for (const std::string& uuid : uuids) {
3833 if (uuid.empty()) {
3834 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: empty plugin UUID";
3835 if (first_error == 0)
3836 first_error = -1;
3837 continue;
3838 }
3839
3840 const std::string path = std::string(ORCA_PLUGINS_BASE) + "/" + Http::url_encode(uuid) + "/changelog?limit=100";
3841 std::string response_body;
3842 unsigned int http_code = 0;
3843
3844 const int result = http_get(path, &response_body, &http_code);
3845
3846 if (result != 0 || http_code != 200) {
3847 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: plugin_uuid=" << uuid << ", http_code=" << http_code
3848 << ", response=" << response_body;
3849 if (first_error == 0)
3850 first_error = http_code != 0 ? static_cast<int>(http_code) : result;
3851 continue;
3852 }
3853
3854 try {
3855 auto root = json::parse(response_body);
3856
3857 if (!root.contains("data") || !root["data"].is_array()) {
3858 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid response format, missing data array for plugin_uuid=" << uuid;
3859 if (first_error == 0)
3860 first_error = -1;
3861 continue;
3862 }
3863
3864 auto& plugin_changelog = changelog[uuid];
3865 plugin_changelog.clear();
3866 for (const auto& item : root["data"]) {
3867 if (!item.is_object())
3868 continue;
3869
3870 PluginChangelog entry;
3871 entry.changelog_id = get_json_string_field(item, "id");
3872 entry.plugin_uuid = get_json_string_field(item, "plugin_id");
3873 if (entry.plugin_uuid.empty())
3874 entry.plugin_uuid = uuid;
3875 entry.version = get_json_string_field(item, "version");
3876 entry.changelog = get_json_string_field(item, "changelog");
3877 entry.created_time = get_json_int64_field(item, "created_time");
3878
3879 plugin_changelog.push_back(std::move(entry));
3880 }
3881 sort_plugin_changelog(plugin_changelog);
3882 } catch (const std::exception& e) {
3883 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": JSON parse error for plugin_uuid=" << uuid << ": " << e.what();
3884 if (first_error == 0)
3885 first_error = -1;
3886 continue;
3887 }
3888 }
3889
3890 return first_error;
3891}
static std::string url_encode(const std::string &str)
Definition Http.cpp:969

References Slic3r::PluginChangelog::changelog, Slic3r::PluginChangelog::changelog_id, Slic3r::PluginChangelog::created_time, http_get(), Slic3r::PluginChangelog::plugin_uuid, Slic3r::sort_plugin_changelog(), Slic3r::Http::url_encode(), and Slic3r::PluginChangelog::version.

Here is the call graph for this function:

◆ fetch_subscribed_manifests_into_descriptors()

int Slic3r::OrcaCloudServiceAgent::fetch_subscribed_manifests_into_descriptors ( std::vector< PluginDescriptor > & descriptors,
std::vector< std::string > & not_found,
std::vector< std::string > & unauthorized )
3425{
3426 std::string response_body;
3427 unsigned int http_code = 0;
3428
3429 const std::string path = std::string(ORCA_SUBSCRIBED_PLUGINS) + "?include=changelog";
3430 int result = http_get(path, &response_body, &http_code);
3431
3432 if (result != 0 || http_code != 200) {
3433 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: http_code=" << http_code;
3434 return result != 0 ? result : http_code;
3435 }
3436
3437 try {
3438 BOOST_LOG_TRIVIAL(info) << response_body;
3439 auto root = json::parse(response_body);
3440
3441 if (root.contains("not_found") && root["not_found"].is_array()) {
3442 for (const auto& item : root["not_found"])
3443 not_found.push_back(item.get<std::string>());
3444 }
3445 if (root.contains("unauthorised") && root["unauthorised"].is_array()) {
3446 for (const auto& item : root["unauthorised"])
3447 unauthorized.push_back(item.get<std::string>());
3448 }
3449
3450 if (!root.contains("data") || !root["data"].is_array()) {
3451 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid response format, missing data array";
3452 return -1;
3453 }
3454
3455 for (const auto& item : root["data"]) {
3456 PluginDescriptor descriptor;
3457 std::string uuid = get_json_string_field(item, "uuid");
3458 if (uuid.empty())
3459 uuid = get_json_string_field(item, "id");
3460
3461 descriptor.name = sanitize_plugin_name(get_json_string_field(item, "name"));
3462 descriptor.plugin_key = uuid;
3463 // Cloud API "description" is intentionally not parsed: descriptions come only from the
3464 // plugin's Python header once installed. Cloud-only rows show a "View on OrcaCloud" link.
3465 descriptor.author = parse_cloud_author(item);
3466 descriptor.version = get_json_string_field(item, "version");
3467 descriptor.latest_version = descriptor.version;
3468 // Cloud "type" is cosmetic (see parse_cloud_display_types); real capability_types are
3469 // populated only when the plugin is actually loaded. Leave them empty here.
3470 descriptor.display_types = parse_cloud_display_types(item);
3471 descriptor.entry_package = get_json_string_field(item, "entry_package");
3472 descriptor.sharing_token = get_json_string_field(item, "sharing_token");
3473 // main_image.access_url is pre-signed, so the WebView <img> can load it without the
3474 // Bearer token (content_url, an API path, would 401). Images only; display-only.
3475 if (item.contains("main_image") && item["main_image"].is_object() && cloud_media_is_image(item["main_image"]))
3476 descriptor.thumbnail_url = get_json_string_field(item["main_image"], "access_url");
3477 if (item.contains("changelog") && item["changelog"].is_array()) {
3478 for (const auto& changelog_item : item["changelog"]) {
3479 if (!changelog_item.is_object())
3480 continue;
3481
3482 PluginChangelog entry;
3483 entry.changelog_id = get_json_string_field(changelog_item, "id");
3484 entry.plugin_uuid = get_json_string_field(changelog_item, "plugin_id");
3485 if (entry.plugin_uuid.empty())
3486 entry.plugin_uuid = uuid;
3487 entry.version = get_json_string_field(changelog_item, "version");
3488 entry.changelog = get_json_string_field(changelog_item, "changelog");
3489 entry.created_time = get_json_int64_field(changelog_item, "created_time");
3490
3491 descriptor.changelog.push_back(std::move(entry));
3492 }
3493 sort_plugin_changelog(descriptor.changelog);
3494 }
3495
3496 descriptor.cloud = std::make_optional<CloudPluginState>({
3497 uuid,
3498 false,
3499 false,
3500 false,
3501 false
3502 });
3503
3504 descriptors.push_back(std::move(descriptor));
3505 }
3506
3507 BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": loaded " << descriptors.size() << " manifests";
3508 } catch (const std::exception& e) {
3509 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": JSON parse error: " << e.what();
3510 return -1;
3511 }
3512
3513 return 0;
3514}

References Slic3r::PluginDescriptor::author, Slic3r::PluginChangelog::changelog, Slic3r::PluginDescriptor::changelog, Slic3r::PluginChangelog::changelog_id, Slic3r::PluginDescriptor::cloud, Slic3r::cloud_media_is_image(), Slic3r::PluginChangelog::created_time, Slic3r::PluginDescriptor::display_types, Slic3r::PluginDescriptor::entry_package, http_get(), Slic3r::PluginDescriptor::latest_version, Slic3r::PluginDescriptor::name, Slic3r::parse_cloud_author(), Slic3r::parse_cloud_display_types(), Slic3r::PluginDescriptor::plugin_key, Slic3r::PluginChangelog::plugin_uuid, Slic3r::sanitize_plugin_name(), Slic3r::PluginDescriptor::sharing_token, Slic3r::sort_plugin_changelog(), Slic3r::PluginDescriptor::thumbnail_url, Slic3r::PluginChangelog::version, and Slic3r::PluginDescriptor::version.

Here is the call graph for this function:

◆ generate_uuid_for_setting_id()

std::string Slic3r::OrcaCloudServiceAgent::generate_uuid_for_setting_id ( const std::string & name,
const std::string & user_id = "" )
static
538{
539 if (name.empty()) {
540 return "";
541 }
542
543 // Mix user_id into the hashed input so two different users generating a setting_id
544 // for an identically-named preset get distinct UUIDs. Without this, the cloud's ID
545 // space collides across accounts and the second user's create gets HTTP 409 with
546 // server_profile=null on every sync (the foreign owner's record is not exposed).
547 static const boost::uuids::uuid orca_namespace =
548 boost::uuids::string_generator()("f47ac10b-58cc-4372-a567-0e02b2c3d479");
549
550 boost::uuids::name_generator_sha1 gen(orca_namespace);
551 boost::uuids::uuid id = user_id.empty() ? gen(name) : gen(user_id + "/" + name);
552 return boost::uuids::to_string(id);
553}

Referenced by Slic3r::GUI::GUI_App::force_push_conflicting_preset(), Slic3r::GUI::GUI_App::on_http_error(), and request_setting_id().

Here is the caller graph for this function:

◆ get_access_token()

std::string Slic3r::OrcaCloudServiceAgent::get_access_token ( ) const
overridevirtual

Return the current access token for API calls. Cloud and printer agents use this for Authorization headers.

Implements Slic3r::ICloudServiceAgent.

955{
956 std::lock_guard<std::mutex> lock(session_mutex);
957 return session.access_token;
958}

References session, and session_mutex.

Referenced by http_delete(), http_get(), http_post(), http_put(), and request_web_session_handoff().

Here is the caller graph for this function:

◆ get_bundle_url()

std::string Slic3r::OrcaCloudServiceAgent::get_bundle_url ( const std::string & bundle_id) const
3214{ return cloud_base_url + "/app/bundles/subscribed-bundles/" + bundle_id; }

References cloud_base_url.

◆ get_camera_url()

int Slic3r::OrcaCloudServiceAgent::get_camera_url ( std::string dev_id,
std::function< void(std::string)> callback )
overridevirtual

Request live camera streaming URL.

Implements Slic3r::ICloudServiceAgent.

2874{
2875 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_camera_url (stub)";
2876 if (callback)
2877 callback("");
2878 return BAMBU_NETWORK_SUCCESS;
2879}

References BAMBU_NETWORK_SUCCESS.

◆ get_cloud_base_url()

std::string Slic3r::OrcaCloudServiceAgent::get_cloud_base_url ( ) const
inline
163{ return cloud_base_url; }

References cloud_base_url.

◆ get_cloud_login_url()

std::string Slic3r::OrcaCloudServiceAgent::get_cloud_login_url ( const std::string & language = "")
overridevirtual

Return the login URL for the cloud service.

Parameters
languageOptional language code (e.g., "en-US", "zh-CN") for localized login page. If empty, returns the default (non-localized) login URL.
Returns
The full URL to the login page, or a local file:// URL for native implementations.

Implements Slic3r::ICloudServiceAgent.

3027{
3028 std::string url = cloud_base_url + ORCA_CLOUD_LOGIN_PATH;
3029 if (!language.empty()) {
3030 url += "?lang=" + language;
3031 }
3032 return url;
3033}
constexpr const char * ORCA_CLOUD_LOGIN_PATH
Definition OrcaCloudServiceAgent.cpp:111

References cloud_base_url.

◆ get_cloud_service_host()

std::string Slic3r::OrcaCloudServiceAgent::get_cloud_service_host ( )
overridevirtual

Return the base hostname for cloud API calls (varies by region). Helpful for diagnostics and when building browser URLs.

Implements Slic3r::ICloudServiceAgent.

3024{ return api_base_url; }

References api_base_url.

◆ get_design_staffpick()

int Slic3r::OrcaCloudServiceAgent::get_design_staffpick ( int offset,
int limit,
std::function< void(std::string)> callback )
overridevirtual

Fetch staff-picked designs from model mall.

Implements Slic3r::ICloudServiceAgent.

2882{
2883 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_design_staffpick (stub)";
2884 if (callback)
2885 callback("[]");
2886 return BAMBU_NETWORK_SUCCESS;
2887}

References BAMBU_NETWORK_SUCCESS, and Slic3r::offset().

Here is the call graph for this function:

◆ get_id()

std::string Slic3r::OrcaCloudServiceAgent::get_id ( ) const
inlineoverridevirtual

Implements Slic3r::ICloudServiceAgent.

156{ return ORCA_CLOUD_PROVIDER; }
static const std::string ORCA_CLOUD_PROVIDER("orca")

References Slic3r::ORCA_CLOUD_PROVIDER().

Here is the call graph for this function:

◆ get_model_mall_detail_url()

int Slic3r::OrcaCloudServiceAgent::get_model_mall_detail_url ( std::string * url,
std::string id )
overridevirtual

Build model detail page URL.

Implements Slic3r::ICloudServiceAgent.

2918{
2919 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_mall_detail_url (stub)";
2920 if (url)
2921 *url = "";
2922 return BAMBU_NETWORK_SUCCESS;
2923}

References BAMBU_NETWORK_SUCCESS.

◆ get_model_mall_home_url()

int Slic3r::OrcaCloudServiceAgent::get_model_mall_home_url ( std::string * url)
overridevirtual

Get model mall home URL.

Implements Slic3r::ICloudServiceAgent.

2910{
2911 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_mall_home_url (stub)";
2912 if (url)
2913 *url = "";
2914 return BAMBU_NETWORK_SUCCESS;
2915}

References BAMBU_NETWORK_SUCCESS.

◆ get_model_mall_rating_result()

int Slic3r::OrcaCloudServiceAgent::get_model_mall_rating_result ( int job_id,
std::string & rating_result,
unsigned int & http_code,
std::string & http_error )
overridevirtual

Poll for rating result.

Implements Slic3r::ICloudServiceAgent.

3017{
3018 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_mall_rating_result (stub)";
3019 rating_result = "{}";
3020 http_code = 200;
3021 return BAMBU_NETWORK_SUCCESS;
3022}

References BAMBU_NETWORK_SUCCESS.

◆ get_model_publish_url()

int Slic3r::OrcaCloudServiceAgent::get_model_publish_url ( std::string * url)
overridevirtual

Get model publish URL.

Implements Slic3r::ICloudServiceAgent.

2896{
2897 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_model_publish_url (stub)";
2898 if (url)
2899 *url = "";
2900 return BAMBU_NETWORK_SUCCESS;
2901}

References BAMBU_NETWORK_SUCCESS.

◆ get_mw_user_4ulist()

int Slic3r::OrcaCloudServiceAgent::get_mw_user_4ulist ( int seed,
int limit,
std::function< void(std::string)> callback )
overridevirtual

Retrieve MakerWorld "For You" list.

Implements Slic3r::ICloudServiceAgent.

3186{
3187 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_mw_user_4ulist (stub)";
3188 if (callback)
3189 callback("[]");
3190 return BAMBU_NETWORK_SUCCESS;
3191}

References BAMBU_NETWORK_SUCCESS.

◆ get_mw_user_preference()

int Slic3r::OrcaCloudServiceAgent::get_mw_user_preference ( std::function< void(std::string)> callback)
overridevirtual

Fetch MakerWorld user preferences.

Implements Slic3r::ICloudServiceAgent.

3178{
3179 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_mw_user_preference (stub)";
3180 if (callback)
3181 callback("{}");
3182 return BAMBU_NETWORK_SUCCESS;
3183}

References BAMBU_NETWORK_SUCCESS.

◆ get_my_message()

int Slic3r::OrcaCloudServiceAgent::get_my_message ( int type,
int after,
int limit,
unsigned int * http_code,
std::string * http_body )
overridevirtual

Retrieve inbox/notification messages.

Implements Slic3r::ICloudServiceAgent.

2734{
2735 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_my_message (stub)";
2736 if (http_code)
2737 *http_code = 200;
2738 if (http_body)
2739 *http_body = "[]";
2740 return BAMBU_NETWORK_SUCCESS;
2741}

References BAMBU_NETWORK_SUCCESS.

◆ get_my_profile()

int Slic3r::OrcaCloudServiceAgent::get_my_profile ( std::string token,
unsigned int * http_code,
std::string * http_body )
overridevirtual

Retrieve user's model mall profile.

Implements Slic3r::ICloudServiceAgent.

2926{
2927 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_my_profile (stub)";
2928 if (http_code)
2929 *http_code = 200;
2930 if (http_body)
2931 *http_body = "{}";
2932 return BAMBU_NETWORK_SUCCESS;
2933}

References BAMBU_NETWORK_SUCCESS.

◆ get_my_token()

int Slic3r::OrcaCloudServiceAgent::get_my_token ( std::string ticket,
unsigned int * http_code,
std::string * http_body )
overridevirtual

Exchange a one-time login ticket (from a third-party OAuth callback) for token + profile JSON. Used by the localhost callback handler when the auth server redirects with ?ticket=...&redirect_url=... instead of passing tokens directly in the URL.

Implements Slic3r::ICloudServiceAgent.

2936{
2937 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_my_token (stub) - Orca cloud uses code-based OAuth, not tickets";
2938 if (http_code)
2939 *http_code = 0;
2940 if (http_body)
2941 *http_body = "";
2942 return -1;
2943}

◆ get_oss_config()

int Slic3r::OrcaCloudServiceAgent::get_oss_config ( std::string & config,
std::string country_code,
unsigned int & http_code,
std::string & http_error )
overridevirtual

Get OSS configuration for image uploads.

Implements Slic3r::ICloudServiceAgent.

2998{
2999 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_oss_config (stub)";
3000 config = "{}";
3001 http_code = 200;
3002 return BAMBU_NETWORK_SUCCESS;
3003}

References BAMBU_NETWORK_SUCCESS, and country_code.

◆ get_plugin_download_url()

int Slic3r::OrcaCloudServiceAgent::get_plugin_download_url ( const std::string & uuid,
const std::string & requested_version,
std::vector< PluginDownloadData > & data,
std::vector< PluginDownloadNotFound > & not_found,
std::vector< std::string > & unauthorized )
3638{
3639 data.clear();
3640 not_found.clear();
3641 unauthorized.clear();
3642
3643 if (uuid.empty())
3644 return -1;
3645
3646 std::string response_body;
3647 unsigned int http_code = 0;
3648
3649 auto api_os_type = []() {
3650 std::string os = platform_os_type();
3651 if (os == "unknown")
3652 return std::string{};
3653 if (os == "macos")
3654 return std::string("macosx");
3655 return os;
3656 };
3657
3658 auto api_architecture = []() {
3659 const std::string arch = platform_architecture();
3660 if (arch == "unknown")
3661 return std::string{};
3662 return arch;
3663 };
3664
3665 const std::string os = api_os_type();
3666 const std::string arch = api_architecture();
3667 std::string path = ORCA_PLUGIN_DOWNLOAD_URL;
3668 if (!os.empty())
3669 path += "?os=" + Http::url_encode(os);
3670 if (!os.empty() && !arch.empty())
3671 path += "&arch=" + Http::url_encode(arch);
3672
3673 json request_item;
3674 request_item["plugin_id"] = uuid;
3675 if (!requested_version.empty())
3676 request_item["version"] = requested_version;
3677
3678 json request_body;
3679 request_body["data"] = json::array({request_item});
3680
3681 int result = http_post(path, request_body.dump(), &response_body, &http_code);
3682 BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << response_body;
3683 BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": " << "Status: " << http_code;
3684
3685 if (result != 0 || http_code != 200) {
3686 if (http_code == 404)
3687 BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << " 404, plugin package for " << os << "-" << arch << " not available.";
3688 else
3689 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: url=" << path << ", http_code=" << http_code;
3690 return result != 0 ? result : http_code;
3691 }
3692
3693 try {
3694 auto root = json::parse(response_body);
3695
3696 if (!root.contains("data") || !root["data"].is_array()) {
3697 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": invalid response format, missing data array";
3698 return -1;
3699 }
3700
3701 for (const auto& item : root["data"]) {
3702 if (!item.is_object())
3703 continue;
3704
3705 PluginDownloadData row;
3706 row.plugin_id = get_json_string_field(item, "plugin_id");
3707 row.download_link = get_json_string_field(item, "download_link");
3708 row.requested_os = get_json_string_field(item, "requested_os");
3709 row.returned_os = get_json_string_field(item, "returned_os");
3710
3711 if (!row.requested_os.empty() || !row.returned_os.empty())
3712 BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": artifact fallback requested_os=" << row.requested_os
3713 << ", returned_os=" << row.returned_os;
3714
3715 data.push_back(std::move(row));
3716 }
3717
3718 if (root.contains("not_found") && root["not_found"].is_array()) {
3719 for (const auto& item : root["not_found"]) {
3720 if (!item.is_object())
3721 continue;
3722
3723 PluginDownloadNotFound row;
3724 row.id = get_json_string_field(item, "id");
3725 row.reason = get_json_string_field(item, "reason");
3726 not_found.push_back(std::move(row));
3727 }
3728 }
3729
3730 if (root.contains("unauthorised") && root["unauthorised"].is_array()) {
3731 for (const auto& item : root["unauthorised"]) {
3732 if (item.is_string())
3733 unauthorized.push_back(item.get<std::string>());
3734 }
3735 }
3736
3737 return 0;
3738
3739 } catch (const std::exception& e) {
3740 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << ": JSON parse error: " << e.what();
3741 return -1;
3742 }
3743
3744 return -1;
3745}
int http_post(const std::string &path, const std::string &body, std::string *response_body, unsigned int *http_code)
Definition OrcaCloudServiceAgent.cpp:2271
constexpr const char * ORCA_PLUGIN_DOWNLOAD_URL
Definition OrcaCloudServiceAgent.cpp:108
std::string platform_os_type()
Definition Platform.cpp:113
std::string platform_architecture()
Definition Platform.cpp:126

References Slic3r::PluginDownloadData::download_link, http_post(), Slic3r::PluginDownloadNotFound::id, Slic3r::platform_architecture(), Slic3r::platform_os_type(), Slic3r::PluginDownloadData::plugin_id, Slic3r::PluginDownloadNotFound::reason, Slic3r::PluginDownloadData::requested_os, Slic3r::PluginDownloadData::returned_os, and Slic3r::Http::url_encode().

Here is the call graph for this function:

◆ get_plugin_url()

std::string Slic3r::OrcaCloudServiceAgent::get_plugin_url ( const std::string & sharing_token) const
3748{ return cloud_base_url + "/p/" + Http::url_encode(sharing_token); }

References cloud_base_url, and Slic3r::Http::url_encode().

Here is the call graph for this function:

◆ get_printer_firmware()

int Slic3r::OrcaCloudServiceAgent::get_printer_firmware ( std::string dev_id,
unsigned * http_code,
std::string * http_body )
overridevirtual

Fetch firmware information for a printer.

Implements Slic3r::ICloudServiceAgent.

2812{
2813 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_printer_firmware (stub)";
2814 if (http_code)
2815 *http_code = 200;
2816 if (http_body)
2817 *http_body = "{}";
2818 return BAMBU_NETWORK_SUCCESS;
2819}

References BAMBU_NETWORK_SUCCESS.

◆ get_refresh_token()

std::string Slic3r::OrcaCloudServiceAgent::get_refresh_token ( ) const
overridevirtual

Return the current refresh token (if available).

Implements Slic3r::ICloudServiceAgent.

961{
962 std::lock_guard<std::mutex> lock(session_mutex);
963 return session.refresh_token;
964}

References session, and session_mutex.

Referenced by refresh_from_storage().

Here is the caller graph for this function:

◆ get_setting_list()

int Slic3r::OrcaCloudServiceAgent::get_setting_list ( std::string bundle_version,
ProgressFn pro_fn = nullptr,
WasCancelledFn cancel_fn = nullptr )
overridevirtual

Trigger bulk download of user presets.

Implements Slic3r::ICloudServiceAgent.

1284{ return get_setting_list2(bundle_version, nullptr, pro_fn, cancel_fn); }
int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn=nullptr, WasCancelledFn cancel_fn=nullptr) override
Definition OrcaCloudServiceAgent.cpp:1286

References get_setting_list2().

Here is the call graph for this function:

◆ get_setting_list2()

int Slic3r::OrcaCloudServiceAgent::get_setting_list2 ( std::string bundle_version,
CheckFn chk_fn,
ProgressFn pro_fn = nullptr,
WasCancelledFn cancel_fn = nullptr )
overridevirtual

Enhanced preset sync with per-item validation.

Implements Slic3r::ICloudServiceAgent.

1287{
1288 (void) bundle_version;
1289 bool success = false;
1290 int error_code = 0;
1291
1292 auto on_success = [&](const SyncPullResponse& resp) {
1293 int total = static_cast<int>(resp.upserts.size() + resp.deletes.size());
1294 int processed = 0;
1295 bool cancelled = false;
1296
1297 for (const auto& upsert : resp.upserts) {
1298 if (cancel_fn && cancel_fn()) {
1299 cancelled = true;
1300 break;
1301 }
1302
1303 if (chk_fn) {
1304 std::map<std::string, std::string> info;
1305 info[IOT_JSON_KEY_SETTING_ID] = upsert.id;
1306 info[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(upsert.updated_time);
1307
1308 if (upsert.content.is_object()) {
1309 for (auto& [key, value] : upsert.content.items()) {
1310 if (value.is_string()) {
1311 info[key] = value.get<std::string>();
1312 } else {
1313 info[key] = value.dump();
1314 }
1315 }
1316 }
1317
1318 if (!info.count(IOT_JSON_KEY_NAME) && !upsert.name.empty()) {
1319 info[IOT_JSON_KEY_NAME] = upsert.name;
1320 }
1321 if (!info.count(IOT_JSON_KEY_TYPE)) {
1323 }
1324
1325 chk_fn(info);
1326 }
1327
1328 if (pro_fn) {
1329 int progress = total > 0 ? (processed * 100 / total) : 100;
1330 pro_fn(progress);
1331 }
1332
1333 processed++;
1334 }
1335
1336 if (!cancelled) {
1337 for (const auto& deleted_id : resp.deletes) {
1338 if (cancel_fn && cancel_fn()) {
1339 cancelled = true;
1340 break;
1341 }
1342
1343 if (chk_fn) {
1344 std::map<std::string, std::string> info;
1345 info[IOT_JSON_KEY_SETTING_ID] = deleted_id;
1346 info["deleted"] = "true";
1347 chk_fn(info);
1348 }
1349
1350 if (pro_fn) {
1351 int progress = total > 0 ? (processed * 100 / total) : 100;
1352 pro_fn(progress);
1353 }
1354
1355 processed++;
1356 }
1357 }
1358
1359 if (!cancelled && resp.next_cursor != 0) {
1360 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1361 sync_state.last_sync_timestamp = resp.next_cursor;
1363 }
1364
1365 if (pro_fn) {
1366 pro_fn(100);
1367 }
1368
1369 success = !cancelled;
1370 };
1371
1372 auto on_error = [&](int code, const std::string& err) {
1373 error_code = code;
1374 success = false;
1375 };
1376
1377 sync_pull(on_success, on_error);
1378
1380}
#define IOT_PRINT_TYPE_STRING
Definition bambu_networking.hpp:107
#define IOT_JSON_KEY_UPDATED_TIME
Definition bambu_networking.hpp:113
#define IOT_JSON_KEY_SETTING_ID
Definition bambu_networking.hpp:115
#define IOT_JSON_KEY_TYPE
Definition bambu_networking.hpp:111
#define BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED
Definition bambu_networking.hpp:25
#define IOT_JSON_KEY_NAME
Definition bambu_networking.hpp:110
int sync_pull(std::function< void(const SyncPullResponse &)> on_success, std::function< void(int http_code, const std::string &error)> on_error)
Definition OrcaCloudServiceAgent.cpp:1406
void save_sync_state()
Definition OrcaCloudServiceAgent.cpp:1597

References BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED, BAMBU_NETWORK_SUCCESS, IOT_JSON_KEY_NAME, IOT_JSON_KEY_SETTING_ID, IOT_JSON_KEY_TYPE, IOT_JSON_KEY_UPDATED_TIME, IOT_PRINT_TYPE_STRING, save_sync_state(), state_mutex, sync_pull(), and sync_state.

Referenced by get_setting_list().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ get_shared_bundle()

int Slic3r::OrcaCloudServiceAgent::get_shared_bundle ( const std::string & bundle_id,
std::map< std::string, std::map< std::string, std::string > > * presets,
BundleMetadata * bundle_metadata )
3279{
3280 if (!presets)
3281 return -1;
3282
3283 std::string response_body;
3284 unsigned int http_code = 0;
3285
3286 // GET /api/v1/bundles/{id}
3287 std::string path = "/api/v1/bundles/" + bundle_id;
3288 int result = http_get(path, &response_body, &http_code);
3289
3290 if (result != 0 || http_code != 200) {
3291 BOOST_LOG_TRIVIAL(error) << "get_shared_bundle failed: bundle_id=" << bundle_id << ", http_code=" << http_code;
3292 return result != 0 ? result : http_code;
3293 }
3294
3295 // Parse JSON response
3296 try {
3297 auto json = nlohmann::json::parse(response_body);
3298
3299 BOOST_LOG_TRIVIAL(info) << "get_shared_bundle: response: " << response_body;
3300 BOOST_LOG_TRIVIAL(info) << "get_shared_bundle: shared_profile: " << json["shared_profiles"];
3301
3302 // Parse the bundle metadata
3303 if (json.contains("id"))
3304 bundle_metadata->id = json["id"].get<std::string>();
3305 if (json.contains("name"))
3306 bundle_metadata->name = json["name"].get<std::string>();
3307 if (json.contains("version"))
3308 bundle_metadata->version = json["version"].get<std::string>();
3309 if (json.contains("description"))
3310 bundle_metadata->description = json["description"].get<std::string>();
3311 if (json.contains("author"))
3312 bundle_metadata->author = json["author"].get<std::string>();
3313 if (json.contains("updated_time"))
3314 bundle_metadata->updated_time = json["updated_time"].get<long long>();
3315
3316 if (!json.contains("shared_profiles") || !json["shared_profiles"].is_array()) {
3317 BOOST_LOG_TRIVIAL(error) << "get_shared_bundle: invalid response format";
3318 return -1;
3319 }
3320
3321 for (auto& preset_object : json["shared_profiles"]) {
3322 BOOST_LOG_TRIVIAL(info) << "shared profile object: " << preset_object;
3323
3324 // Extract preset name and content
3325 std::string preset_name = preset_object.value("name", "");
3326 if (preset_name.empty()) {
3327 BOOST_LOG_TRIVIAL(warning) << "get_shared_bundle: preset has no name, skipping";
3328 continue;
3329 }
3330
3331 // Parse content JSON into key-value pairs using helper (same as get_user_presets)
3332 std::map<std::string, std::string> value_map;
3333 if (preset_object.contains("content") && preset_object["content"].is_object()) {
3334 json_to_map(preset_object["content"].dump(), value_map);
3335 }
3336
3337 // Add metadata fields to match get_user_presets format
3338 // These are required by PresetCollection::load_user_preset
3339 if (preset_object.contains("id") && value_map.find(BBL_JSON_KEY_SETTING_ID) == value_map.end()) {
3340 value_map[BBL_JSON_KEY_SETTING_ID] = preset_object["id"];
3341 }
3342 if (value_map.find(BBL_JSON_KEY_USER_ID) == value_map.end()) {
3343 // Bundle presets don't have a user_id in the traditional sense
3344 // Use bundle_id as a placeholder to indicate source
3345 value_map[BBL_JSON_KEY_USER_ID] = "bundle:" + bundle_id;
3346 }
3347 if (preset_object.contains("updated_time") && value_map.find(ORCA_JSON_KEY_UPDATE_TIME) == value_map.end()) {
3348 value_map[ORCA_JSON_KEY_UPDATE_TIME] = preset_object["updated_time"].dump();
3349 }
3350 if (value_map.find(BBL_JSON_KEY_NAME) == value_map.end()) {
3351 value_map[BBL_JSON_KEY_NAME] = preset_name;
3352 }
3353
3354 // Store as: presets[preset_name][key] = value
3355 // This matches the format expected by PresetBundle::load_user_presets
3356 (*presets)[preset_name] = value_map;
3357 }
3358
3359 BOOST_LOG_TRIVIAL(info) << "get_shared_bundle: loaded " << presets->size() << " presets for bundle_id=" << bundle_id;
3360 return 0;
3361
3362 } catch (const std::exception& e) {
3363 BOOST_LOG_TRIVIAL(error) << "get_shared_bundle: JSON parse error: " << e.what();
3364 return -1;
3365 }
3366}
#define BBL_JSON_KEY_USER_ID
Definition Preset.hpp:70
#define BBL_JSON_KEY_NAME
Definition Preset.hpp:58
#define ORCA_JSON_KEY_UPDATE_TIME
Definition Preset.hpp:73
#define BBL_JSON_KEY_SETTING_ID
Definition Preset.hpp:68
void json_to_map(const std::string &json, std::map< std::string, std::string > &map)
Definition OrcaCloudServiceAgent.cpp:2639
size_t dump(const PresetBundle &bundle, const Sections &sections, std::ostream &out)
Definition profile_include_dump.cpp:96

References Slic3r::BundleMetadata::author, BBL_JSON_KEY_NAME, BBL_JSON_KEY_SETTING_ID, BBL_JSON_KEY_USER_ID, Slic3r::BundleMetadata::description, http_get(), Slic3r::BundleMetadata::id, json_to_map(), Slic3r::BundleMetadata::name, ORCA_JSON_KEY_UPDATE_TIME, Slic3r::BundleMetadata::updated_time, and Slic3r::BundleMetadata::version.

Here is the call graph for this function:

◆ get_slice_info()

int Slic3r::OrcaCloudServiceAgent::get_slice_info ( std::string project_id,
std::string profile_id,
int plate_index,
std::string * slice_json )
overridevirtual

Retrieve slicing job info.

Implements Slic3r::ICloudServiceAgent.

2850{
2851 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_slice_info (stub)";
2852 if (slice_json)
2853 *slice_json = "{}";
2854 return BAMBU_NETWORK_SUCCESS;
2855}

References BAMBU_NETWORK_SUCCESS.

◆ get_subscribed_bundles()

int Slic3r::OrcaCloudServiceAgent::get_subscribed_bundles ( std::vector< std::pair< std::string, std::string > > * bundles,
std::vector< std::string > & notfound,
std::vector< std::string > & unauthorized )
3219{
3220 if (!bundles)
3221 return -1;
3222
3223 std::string response_body;
3224 unsigned int http_code = 0;
3225
3226 // GET /api/v1/bundles
3227 int result = http_get("/api/v1/subscriptions", &response_body, &http_code);
3228
3229 if (result != 0 || http_code != 200) {
3230 BOOST_LOG_TRIVIAL(error) << "get_subscribed_bundles failed: http_code=" << http_code << ", response=" << response_body;
3231 return result != 0 ? result : http_code;
3232 }
3233
3234 // Parse JSON response
3235 try {
3236 auto json = nlohmann::json::parse(response_body);
3237
3238 if (!json.contains("data") || !json["data"].is_array()) {
3239 BOOST_LOG_TRIVIAL(error) << "get_subscribed_bundles: invalid response format";
3240 return -1;
3241 }
3242 if (json.contains("not_found")) {
3243 // not found warning
3244 for (const auto& not_found : json["not_found"]) {
3245 notfound.push_back(not_found);
3246 }
3247 }
3248 if (json.contains("unauthorised")) {
3249 // populate something to be iterated to show warning notifications
3250 for (const auto& u : json["unauthorised"]) {
3251 unauthorized.push_back(u);
3252 }
3253 }
3254 // if(json.contains("privated") && json["privated"].is_array() && !json["privated"].empty())
3255 // {
3256 // // populate something to be iterated to show warning notifications
3257 // for(const auto& not_found : json["privated"])
3258 // {
3259
3260 // }
3261 // }
3262
3263 for (const auto& bundle_json : json["data"]) {
3264 bundles->push_back(std::make_pair(bundle_json["id"].get<std::string>(), bundle_json["version"].get<std::string>()));
3265 }
3266
3267 BOOST_LOG_TRIVIAL(info) << "get_subscribed_bundles: loaded " << bundles->size() << " bundles";
3268 return 0;
3269
3270 } catch (const std::exception& e) {
3271 BOOST_LOG_TRIVIAL(error) << "get_subscribed_bundles: JSON parse error: " << e.what();
3272 return -1;
3273 }
3274}

References http_get().

Here is the call graph for this function:

◆ get_subtask()

int Slic3r::OrcaCloudServiceAgent::get_subtask ( BBLModelTask * task,
OnGetSubTaskFn getsub_fn )
overridevirtual

Fetch publishing subtask information.

Implements Slic3r::ICloudServiceAgent.

2904{
2905 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_subtask (stub)";
2906 return BAMBU_NETWORK_SUCCESS;
2907}

References BAMBU_NETWORK_SUCCESS.

◆ get_subtask_info()

int Slic3r::OrcaCloudServiceAgent::get_subtask_info ( std::string subtask_id,
std::string * task_json,
unsigned int * http_code,
std::string * http_body )
overridevirtual

Fetch subtask information.

Implements Slic3r::ICloudServiceAgent.

2838{
2839 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_subtask_info (stub)";
2840 if (task_json)
2841 *task_json = "{}";
2842 if (http_code)
2843 *http_code = 200;
2844 if (http_body)
2845 *http_body = "{}";
2846 return BAMBU_NETWORK_SUCCESS;
2847}

References BAMBU_NETWORK_SUCCESS.

◆ get_sync_state()

const SyncState & Slic3r::OrcaCloudServiceAgent::get_sync_state ( ) const
inline
324{ return sync_state; }

References sync_state.

◆ get_task_plate_index()

int Slic3r::OrcaCloudServiceAgent::get_task_plate_index ( std::string task_id,
int * plate_index )
overridevirtual

Get plate index for a cloud task.

Implements Slic3r::ICloudServiceAgent.

2822{
2823 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_task_plate_index (stub)";
2824 if (plate_index)
2825 *plate_index = 0;
2826 return BAMBU_NETWORK_SUCCESS;
2827}

References BAMBU_NETWORK_SUCCESS.

◆ get_track_enable()

bool Slic3r::OrcaCloudServiceAgent::get_track_enable ( )
overridevirtual

Check if tracking is enabled.

Implements Slic3r::ICloudServiceAgent.

2984{
2985 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2986 return enable_track;
2987}
bool enable_track
Definition OrcaCloudServiceAgent.hpp:469

References enable_track, and state_mutex.

◆ get_use_encrypted_token_file()

bool Slic3r::OrcaCloudServiceAgent::get_use_encrypted_token_file ( ) const

◆ get_user_avatar()

std::string Slic3r::OrcaCloudServiceAgent::get_user_avatar ( )
overridevirtual

Return the avatar URL/path for the current user.

Implements Slic3r::ICloudServiceAgent.

868{
869 std::lock_guard<std::mutex> lock(session_mutex);
870 return session.user_avatar;
871}

References session, and session_mutex.

Referenced by build_login_cmd().

Here is the caller graph for this function:

◆ get_user_id()

std::string Slic3r::OrcaCloudServiceAgent::get_user_id ( )
overridevirtual

Return the backend-generated user ID for the current session.

Implements Slic3r::ICloudServiceAgent.

856{
857 std::lock_guard<std::mutex> lock(session_mutex);
858 return session.user_id;
859}

References session, and session_mutex.

Referenced by get_user_presets(), and request_setting_id().

Here is the caller graph for this function:

◆ get_user_info()

int Slic3r::OrcaCloudServiceAgent::get_user_info ( int * identifier)
overridevirtual

Retrieve extended user profile info.

Implements Slic3r::ICloudServiceAgent.

2830{
2831 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_info (stub)";
2832 if (identifier)
2833 *identifier = 0;
2834 return BAMBU_NETWORK_SUCCESS;
2835}

References BAMBU_NETWORK_SUCCESS.

◆ get_user_name()

std::string Slic3r::OrcaCloudServiceAgent::get_user_name ( )
overridevirtual

Return the display name for the current user.

Implements Slic3r::ICloudServiceAgent.

862{
863 std::lock_guard<std::mutex> lock(session_mutex);
864 return session.user_name;
865}

References session, and session_mutex.

Referenced by build_login_cmd().

Here is the caller graph for this function:

◆ get_user_nickname()

std::string Slic3r::OrcaCloudServiceAgent::get_user_nickname ( )
overridevirtual

Return the nickname for the current user.

Implements Slic3r::ICloudServiceAgent.

874{
875 std::lock_guard<std::mutex> lock(session_mutex);
876 return session.user_nickname;
877}

References session, and session_mutex.

Referenced by build_login_cmd().

Here is the caller graph for this function:

◆ get_user_presets()

int Slic3r::OrcaCloudServiceAgent::get_user_presets ( std::map< std::string, std::map< std::string, std::string > > * user_presets)
overridevirtual

Fetch all presets owned by the logged-in user.

Parameters
user_presetsMap populated with [type][setting_id] = json

Implements Slic3r::ICloudServiceAgent.

1103{
1104 if (!user_presets)
1106
1107 if (!is_user_login()) {
1108 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: Not logged in";
1110 }
1111
1112 bool success = false;
1113 std::string error_msg;
1114 int http_code_out = 0;
1115 SyncState saved_state;
1116
1117 {
1118 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1119 saved_state = sync_state;
1120 sync_state = SyncState{};
1121 }
1122
1123 auto on_success = [&](const SyncPullResponse& resp) {
1124 // Get current user_id for all presets (required by load_user_preset)
1125 std::string current_user_id = get_user_id();
1126
1127 for (const auto& upsert : resp.upserts) {
1128 // Parse JSON content into key-value pairs using helper
1129 std::map<std::string, std::string> value_map;
1130 json_to_map(upsert.content.dump(), value_map);
1131
1132 // Add metadata from top-level sync response if not already in content
1133 // These are required by PresetCollection::load_user_preset
1134 if (value_map.find(BBL_JSON_KEY_SETTING_ID) == value_map.end()) {
1135 value_map[BBL_JSON_KEY_SETTING_ID] = upsert.id;
1136 }
1137 if (value_map.find(BBL_JSON_KEY_USER_ID) == value_map.end()) {
1138 value_map[BBL_JSON_KEY_USER_ID] = current_user_id;
1139 }
1140 if (value_map.find(ORCA_JSON_KEY_UPDATE_TIME) == value_map.end()) {
1141 value_map[ORCA_JSON_KEY_UPDATE_TIME] = std::to_string(upsert.updated_time);
1142 }
1143
1144 // Use preset name from content or fallback to upsert.name or upsert.id
1145 std::string preset_name = upsert.content.value(BBL_JSON_KEY_NAME, "");
1146 if (preset_name.empty()) {
1147 preset_name = upsert.name.empty() ? upsert.id : upsert.name;
1148 }
1149
1150 // Store as: user_presets[preset_name][key] = value
1151 // This matches the format expected by PresetBundle::load_user_presets
1152 (*user_presets)[preset_name] = value_map;
1153 }
1154
1155 if (resp.next_cursor != 0) {
1156 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1157 sync_state.last_sync_timestamp = resp.next_cursor;
1159 } else {
1160 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1161 sync_state = saved_state;
1162 }
1163 success = true;
1164 };
1165
1166 auto on_error = [&](int code, const std::string& err) {
1167 http_code_out = code;
1168 error_msg = err;
1169 success = false;
1170 };
1171
1172 sync_pull(on_success, on_error);
1173
1174 if (!success) {
1175 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1176 sync_state = saved_state;
1177 }
1178
1180}
std::string get_user_id() override
Definition OrcaCloudServiceAgent.cpp:855

References BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED, BAMBU_NETWORK_ERR_INVALID_HANDLE, BAMBU_NETWORK_SUCCESS, BBL_JSON_KEY_NAME, BBL_JSON_KEY_SETTING_ID, BBL_JSON_KEY_USER_ID, get_user_id(), is_user_login(), json_to_map(), ORCA_JSON_KEY_UPDATE_TIME, save_sync_state(), state_mutex, sync_pull(), and sync_state.

Here is the call graph for this function:

◆ get_user_print_info()

int Slic3r::OrcaCloudServiceAgent::get_user_print_info ( unsigned int * http_code,
std::string * http_body )
overridevirtual

Fetch aggregated print statistics.

Implements Slic3r::ICloudServiceAgent.

2754{
2755 std::string response;
2756 unsigned int code = 0;
2757 int result = http_get(ORCA_CLOUD_PRINTER, &response, &code);
2758
2759 if (http_code)
2760 *http_code = code;
2761
2762 if (result != 0 || code != 200)
2763 return result != 0 ? result : BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED;
2764
2765 try {
2766 auto resp_json = nlohmann::json::parse(response);
2767 nlohmann::json devices = nlohmann::json::array();
2768
2769 for (const auto& printer : resp_json.value("data", nlohmann::json::array())) {
2770 const std::string role = printer.value("access_role", "");
2771 if (role.empty() || role == "viewer")
2772 continue;
2773
2774 nlohmann::json device;
2775 device["dev_id"] = printer.value("id", "");
2776 device["dev_name"] = printer.value("name", "");
2777 if (printer.contains("model") && printer["model"].is_string())
2778 device["dev_model_name"] = printer["model"].get<std::string>();
2779
2780 bool online = false;
2781 if (printer.contains("status_snapshot") && printer["status_snapshot"].is_object()) {
2782 const auto& status = printer["status_snapshot"].value("status", nlohmann::json::object());
2783 online = status.value("connection", nlohmann::json::object()).value("state", "") == "online";
2784 if (status.contains("job") && status["job"].is_object())
2785 device["task_status"] = status["job"].value("state", "");
2786 }
2787 device["dev_online"] = online;
2788 devices.push_back(std::move(device));
2789 }
2790
2791 if (http_body) {
2792 nlohmann::json out;
2793 out["devices"] = std::move(devices);
2794 *http_body = out.dump();
2795 }
2796 } catch (const std::exception&) {
2798 }
2799
2800 return BAMBU_NETWORK_SUCCESS;
2801}

References BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED, BAMBU_NETWORK_SUCCESS, and http_get().

Here is the call graph for this function:

◆ get_user_tasks()

int Slic3r::OrcaCloudServiceAgent::get_user_tasks ( TaskQueryParams params,
std::string * http_body )
overridevirtual

Query user's tasks/prints.

Implements Slic3r::ICloudServiceAgent.

2804{
2805 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: get_user_tasks (stub)";
2806 if (http_body)
2807 *http_body = "[]";
2808 return BAMBU_NETWORK_SUCCESS;
2809}

References BAMBU_NETWORK_SUCCESS.

◆ get_version()

std::string Slic3r::OrcaCloudServiceAgent::get_version ( )
overridevirtual

Return the version of the cloud service implementation.

Implements Slic3r::ICloudServiceAgent.

3193{ return "OrcaCloudServiceAgent 1.0.0"; }

◆ http_delete()

int Slic3r::OrcaCloudServiceAgent::http_delete ( const std::string & path,
std::string * response_body,
unsigned int * http_code )
private
2399{
2400 std::string url = api_base_url + path;
2401 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: DELETE " << url;
2402
2403 ensure_token_fresh("http_delete_" + path);
2404
2405 auto perform = [&]() {
2406 HttpResult result;
2407 try {
2408 auto http = Http::del(url);
2409 http.tls_verify(true);
2410
2411 std::string token = get_access_token();
2412
2413 auto headers = data_headers();
2414 for (const auto& pair : headers) {
2415 http.header(pair.first, pair.second);
2416 }
2417
2418 if (!token.empty()) {
2419 http.header("Authorization", "Bearer " + token);
2420 }
2421
2422 http.on_complete([&](std::string resp_body, unsigned resp_status) {
2423 result.success = true;
2424 result.status = resp_status;
2425 result.body = resp_body;
2426 })
2427 .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
2428 result.success = false;
2429 result.status = resp_status == 0 ? 404 : resp_status;
2430 result.body = resp_body;
2431 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
2432 })
2433 .timeout_max(30)
2434 .perform_sync();
2435
2436 } catch (const std::exception& e) {
2437 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_delete exception - " << e.what();
2438 }
2439 return result;
2440 };
2441
2442 HttpResult res = perform();
2443 bool suppress = resolve_unauthorized(res, perform, "http_delete_" + path);
2444
2445 if (response_body)
2446 *response_body = res.body;
2447 if (http_code)
2448 *http_code = res.status;
2449
2450 if (!suppress && (!res.success || res.status >= 400)) {
2451 invoke_http_error_callback(res.status, res.body);
2452 }
2453
2454 return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
2455}
#define BAMBU_NETWORK_ERR_CONNECT_FAILED
Definition bambu_networking.hpp:18
static Http del(std::string url)
Definition Http.cpp:923
bool ensure_token_fresh(const std::string &reason) override
Definition OrcaCloudServiceAgent.cpp:966
std::string get_access_token() const override
Definition OrcaCloudServiceAgent.cpp:954
bool resolve_unauthorized(HttpResult &res, const std::function< HttpResult()> &perform, const std::string &reason)
Definition OrcaCloudServiceAgent.cpp:2193
std::map< std::string, std::string > data_headers()
Definition OrcaCloudServiceAgent.cpp:2183
void invoke_http_error_callback(unsigned http_code, const std::string &http_body)
Definition OrcaCloudServiceAgent.cpp:2677
Definition OrcaCloudServiceAgent.hpp:390
void trace(unsigned int level, const char *message)
Definition utils.cpp:247
@ http
Definition IPrinterAgent.hpp:64

References api_base_url, BAMBU_NETWORK_ERR_CONNECT_FAILED, BAMBU_NETWORK_SUCCESS, Slic3r::OrcaCloudServiceAgent::HttpResult::body, data_headers(), Slic3r::Http::del(), ensure_token_fresh(), get_access_token(), Slic3r::http, invoke_http_error_callback(), resolve_unauthorized(), Slic3r::OrcaCloudServiceAgent::HttpResult::status, Slic3r::OrcaCloudServiceAgent::HttpResult::success, and Slic3r::trace().

Referenced by delete_setting(), unsubscribe_bundle(), and unsubscribe_plugins().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ http_get()

int Slic3r::OrcaCloudServiceAgent::http_get ( const std::string & path,
std::string * response_body,
unsigned int * http_code )
private
2212{
2213 std::string url = api_base_url + path;
2214 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: GET " << url;
2215
2216 if (!ensure_token_fresh("http_get_" + path))
2217 BOOST_LOG_TRIVIAL(warning) << "ensure_token_fresh returned false";
2218
2219 auto perform = [&]() {
2220 HttpResult result;
2221 try {
2222 auto http = Http::get(url);
2223 http.tls_verify(true);
2224
2225 std::string token = get_access_token();
2226
2227 auto headers = data_headers();
2228 for (const auto& pair : headers) {
2229 http.header(pair.first, pair.second);
2230 }
2231
2232 if (!token.empty()) {
2233 http.header("Authorization", "Bearer " + token);
2234 }
2235
2236 http.on_complete([&](std::string body, unsigned resp_status) {
2237 result.success = true;
2238 result.status = resp_status;
2239 result.body = body;
2240 })
2241 .on_error([&](std::string body, std::string error, unsigned resp_status) {
2242 result.success = false;
2243 result.status = resp_status == 0 ? 404 : resp_status;
2244 result.body = body;
2245 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
2246 })
2247 .timeout_max(30)
2248 .perform_sync();
2249
2250 } catch (const std::exception& e) {
2251 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_get exception - " << e.what();
2252 }
2253 return result;
2254 };
2255
2256 HttpResult res = perform();
2257 bool suppress = resolve_unauthorized(res, perform, "http_get_" + path);
2258
2259 if (response_body)
2260 *response_body = res.body;
2261 if (http_code)
2262 *http_code = res.status;
2263
2264 if (!suppress && (!res.success || res.status >= 400)) {
2265 invoke_http_error_callback(res.status, res.body);
2266 }
2267
2268 return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
2269}
static Http get(std::string url)
Definition Http.cpp:886

References api_base_url, BAMBU_NETWORK_ERR_CONNECT_FAILED, BAMBU_NETWORK_SUCCESS, Slic3r::OrcaCloudServiceAgent::HttpResult::body, data_headers(), ensure_token_fresh(), Slic3r::Http::get(), get_access_token(), Slic3r::http, invoke_http_error_callback(), resolve_unauthorized(), Slic3r::OrcaCloudServiceAgent::HttpResult::status, Slic3r::OrcaCloudServiceAgent::HttpResult::success, and Slic3r::trace().

Referenced by fetch_mine_manifests_into_descriptors(), fetch_plugin_changelogs(), fetch_subscribed_manifests_into_descriptors(), get_shared_bundle(), get_subscribed_bundles(), get_user_print_info(), and sync_pull().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ http_post()

int Slic3r::OrcaCloudServiceAgent::http_post ( const std::string & path,
const std::string & body,
std::string * response_body,
unsigned int * http_code )
private
2272{
2273 std::string url = api_base_url + path;
2274 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url;
2275
2276 ensure_token_fresh("http_post_" + path);
2277
2278 auto perform = [&]() {
2279 HttpResult result;
2280 try {
2281 auto http = Http::post(url);
2282 http.tls_verify(true);
2283
2284 std::string token = get_access_token();
2285
2286 auto headers = data_headers();
2287 for (const auto& pair : headers) {
2288 http.header(pair.first, pair.second);
2289 }
2290
2291 if (!token.empty()) {
2292 http.header("Authorization", "Bearer " + token);
2293 }
2294
2295 http.header("Content-Type", "application/json");
2296 http.set_post_body(body);
2297
2298 http.on_complete([&](std::string resp_body, unsigned resp_status) {
2299 result.success = true;
2300 result.status = resp_status;
2301 result.body = resp_body;
2302 })
2303 .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
2304 result.success = false;
2305 result.status = resp_status == 0 ? 404 : resp_status;
2306 result.body = resp_body;
2307 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
2308 })
2309 .timeout_max(30)
2310 .perform_sync();
2311
2312 } catch (const std::exception& e) {
2313 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_post exception - " << e.what();
2314 }
2315 return result;
2316 };
2317
2318 HttpResult res = perform();
2319 bool suppress = resolve_unauthorized(res, perform, "http_post_" + path);
2320
2321 if (response_body)
2322 *response_body = res.body;
2323 if (http_code)
2324 *http_code = res.status;
2325
2326 // 409 is a push-only domain conflict; sync_push re-fires the error callback with the
2327 // local preset name injected (the raw server body omits it for tombstone conflicts),
2328 // so skip the generic nameless auto-fire here to avoid a duplicate, nameless event.
2329 if (!suppress && (!res.success || res.status >= 400) && res.status != 409) {
2330 invoke_http_error_callback(res.status, res.body);
2331 }
2332
2333 return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
2334}
static Http post(std::string url)
Definition Http.cpp:891

References api_base_url, BAMBU_NETWORK_ERR_CONNECT_FAILED, BAMBU_NETWORK_SUCCESS, Slic3r::OrcaCloudServiceAgent::HttpResult::body, data_headers(), ensure_token_fresh(), get_access_token(), Slic3r::http, invoke_http_error_callback(), Slic3r::Http::post(), resolve_unauthorized(), Slic3r::OrcaCloudServiceAgent::HttpResult::status, Slic3r::OrcaCloudServiceAgent::HttpResult::success, and Slic3r::trace().

Referenced by get_plugin_download_url(), subscribe_plugin(), and sync_push().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ http_post_auth()

bool Slic3r::OrcaCloudServiceAgent::http_post_auth ( const std::string & path,
const std::string & body,
std::string * response_body,
unsigned int * http_code )
private
2541{
2542 std::string url = auth_base_url + path + "?scope=local";
2543 std::string token;
2544 std::map<std::string, std::string> headers_copy;
2545 {
2546 std::lock_guard<std::mutex> lock(session_mutex);
2547 token = session.access_token;
2548 }
2549 {
2550 std::lock_guard<std::mutex> lock(headers_mutex);
2551 headers_copy = extra_headers;
2552 }
2553 {
2554 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2555 for (const auto& pair : auth_headers) {
2556 headers_copy[pair.first] = pair.second;
2557 }
2558 }
2559
2560 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST (auth) " << url;
2561
2562 try {
2563 auto http = Http::post(url);
2564 http.tls_verify(true);
2565
2566 for (const auto& pair : headers_copy) {
2567 http.header(pair.first, pair.second);
2568 }
2569
2570 if (!token.empty()) {
2571 http.header("Authorization", "Bearer " + token);
2572 }
2573
2574 http.remove_header("Content-Type");
2575 http.header("Content-Type", "application/json");
2576 http.set_post_body(body);
2577
2578 bool success = false;
2579 unsigned int status = 0;
2580 std::string resp_body;
2581
2582 http.on_complete([&](std::string body, unsigned resp_status) {
2583 success = true;
2584 status = resp_status;
2585 resp_body = body;
2586 })
2587 .on_error([&](std::string body, std::string error, unsigned resp_status) {
2588 success = false;
2589 status = resp_status == 0 ? 404 : resp_status;
2590 resp_body = body;
2591 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP (auth) error - " << error;
2592 })
2593 .timeout_max(30)
2594 .perform_sync();
2595
2596 if (response_body)
2597 *response_body = resp_body;
2598 if (http_code)
2599 *http_code = status;
2600 return success;
2601
2602 } catch (const std::exception& e) {
2603 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_post_auth exception - " << e.what();
2604 if (http_code)
2605 *http_code = 0;
2606 return false;
2607 }
2608}

References auth_base_url, auth_headers, extra_headers, headers_mutex, Slic3r::http, Slic3r::Http::post(), session, session_mutex, state_mutex, and Slic3r::trace().

Referenced by user_logout().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ http_post_token()

bool Slic3r::OrcaCloudServiceAgent::http_post_token ( const std::string & body,
std::string * response_body,
unsigned int * http_code,
const std::string & url = "" )
private
2461{
2462 std::map<std::string, std::string> headers_copy;
2463 std::string url;
2464 {
2465 std::lock_guard<std::mutex> lock(headers_mutex);
2466 url = custom_url.empty() ? (auth_base_url + auth_constants::TOKEN_PATH) : custom_url;
2467 headers_copy = extra_headers;
2468 }
2469
2470 // Add auth headers
2471 {
2472 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2473 for (const auto& pair : auth_headers) {
2474 headers_copy[pair.first] = pair.second;
2475 }
2476 }
2477
2478 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << url;
2479
2480 bool has_apikey = false;
2481 for (const auto& pair : headers_copy) {
2482 if (pair.first == "apikey")
2483 has_apikey = true;
2484 }
2485 if (!has_apikey) {
2486 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: http_post_token - apikey header MISSING! Token request will likely fail.";
2487 }
2488
2489 try {
2490 auto http = Http::post(url);
2491 http.tls_verify(true);
2492
2493 for (const auto& pair : headers_copy) {
2494 http.header(pair.first, pair.second);
2495 }
2496
2497 http.remove_header("Authorization");
2498 http.remove_header("Content-Type");
2499 http.header("Content-Type", "application/json");
2500 http.set_post_body(body);
2501
2502 bool success = false;
2503 unsigned int status = 0;
2504 std::string resp_body;
2505
2506 http.on_complete([&](std::string body, unsigned resp_status) {
2507 success = true;
2508 status = resp_status;
2509 resp_body = body;
2510 })
2511 .on_error([&](std::string body, std::string error, unsigned resp_status) {
2512 success = false;
2513 status = resp_status; // keep 0 for "no response" so the refresh classifier sees a transport failure
2514 resp_body = body;
2515 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
2516 })
2517 // Keep this timeout finite: refresh_from_storage holds a cross-process lock across
2518 // this call, so an unbounded refresh POST would let one instance wedge token refresh
2519 // for every other Orca instance on the machine.
2520 .timeout_max(30)
2521 .perform_sync();
2522
2523 if (response_body)
2524 *response_body = resp_body;
2525 if (http_code)
2526 *http_code = status;
2527 return success;
2528
2529 } catch (const std::exception& e) {
2530 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_post_token exception - " << e.what();
2531 if (http_code)
2532 *http_code = 0;
2533 return false;
2534 }
2535}

References auth_base_url, auth_headers, extra_headers, headers_mutex, Slic3r::http, Slic3r::Http::post(), state_mutex, Slic3r::auth_constants::TOKEN_PATH, and Slic3r::trace().

Referenced by exchange_auth_code(), and refresh_session_with_token().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ http_put()

int Slic3r::OrcaCloudServiceAgent::http_put ( const std::string & path,
const std::string & body,
std::string * response_body,
unsigned int * http_code )
private
2337{
2338 std::string url = api_base_url + path;
2339 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: PUT " << url;
2340
2341 ensure_token_fresh("http_put_" + path);
2342
2343 auto perform = [&]() {
2344 HttpResult result;
2345 try {
2346 auto http = Http::put(url);
2347 http.tls_verify(true);
2348
2349 std::string token = get_access_token();
2350
2351 auto headers = data_headers();
2352 for (const auto& pair : headers) {
2353 http.header(pair.first, pair.second);
2354 }
2355
2356 if (!token.empty()) {
2357 http.header("Authorization", "Bearer " + token);
2358 }
2359
2360 http.header("Content-Type", "application/json");
2361 http.set_post_body(body);
2362
2363 http.on_complete([&](std::string resp_body, unsigned resp_status) {
2364 result.success = true;
2365 result.status = resp_status;
2366 result.body = resp_body;
2367 })
2368 .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
2369 result.success = false;
2370 result.status = resp_status == 0 ? 404 : resp_status;
2371 result.body = resp_body;
2372 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error;
2373 })
2374 .timeout_max(30)
2375 .perform_sync();
2376
2377 } catch (const std::exception& e) {
2378 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: http_put exception - " << e.what();
2379 }
2380 return result;
2381 };
2382
2383 HttpResult res = perform();
2384 bool suppress = resolve_unauthorized(res, perform, "http_put_" + path);
2385
2386 if (response_body)
2387 *response_body = res.body;
2388 if (http_code)
2389 *http_code = res.status;
2390
2391 if (!suppress && (!res.success || res.status >= 400)) {
2392 invoke_http_error_callback(res.status, res.body);
2393 }
2394
2395 return (res.success && !suppress) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
2396}
static Http put(std::string url)
Definition Http.cpp:899

References api_base_url, BAMBU_NETWORK_ERR_CONNECT_FAILED, BAMBU_NETWORK_SUCCESS, Slic3r::OrcaCloudServiceAgent::HttpResult::body, data_headers(), ensure_token_fresh(), get_access_token(), Slic3r::http, invoke_http_error_callback(), Slic3r::Http::put(), resolve_unauthorized(), Slic3r::OrcaCloudServiceAgent::HttpResult::status, Slic3r::OrcaCloudServiceAgent::HttpResult::success, and Slic3r::trace().

Here is the call graph for this function:

◆ init_log()

int Slic3r::OrcaCloudServiceAgent::init_log ( )
overridevirtual

Initialize the logging backend for the agent. Call after set_config_dir() so logs have a destination.

Implements Slic3r::ICloudServiceAgent.

598{ return BAMBU_NETWORK_SUCCESS; }

References BAMBU_NETWORK_SUCCESS.

◆ invoke_http_error_callback()

void Slic3r::OrcaCloudServiceAgent::invoke_http_error_callback ( unsigned http_code,
const std::string & http_body )
private
2678{
2679 AppOnHttpErrorFn callback;
2680 QueueOnMainFn queue_fn;
2681 {
2682 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2683 callback = on_http_error_fn;
2684 queue_fn = queue_on_main_fn;
2685 }
2686
2687 if (callback) {
2688 CloudEvent event{ORCA_CLOUD_PROVIDER};
2689 if (queue_fn) {
2690 queue_fn([callback, event, http_code, http_body]() { callback(event, http_code, http_body); });
2691 } else {
2692 callback(event, http_code, http_body);
2693 }
2694 }
2695}
AppOnHttpErrorFn on_http_error_fn
Definition OrcaCloudServiceAgent.hpp:478
QueueOnMainFn queue_on_main_fn
Definition OrcaCloudServiceAgent.hpp:480
std::function< void(CloudEvent event, unsigned http_code, std::string http_body)> AppOnHttpErrorFn
Definition ICloudServiceAgent.hpp:46
std::function< void(std::function< void()>)> QueueOnMainFn
Definition bambu_networking.hpp:136

References on_http_error_fn, Slic3r::ORCA_CLOUD_PROVIDER(), queue_on_main_fn, and state_mutex.

Referenced by http_delete(), http_get(), http_post(), http_put(), request_web_session_handoff(), run_health_check(), and sync_push().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ invoke_server_connected_callback()

void Slic3r::OrcaCloudServiceAgent::invoke_server_connected_callback ( int return_code,
int reason_code )
private
2658{
2659 AppOnServerConnectedFn callback;
2660 QueueOnMainFn queue_fn;
2661 {
2662 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2663 callback = on_server_connected_fn;
2664 queue_fn = queue_on_main_fn;
2665 }
2666
2667 if (callback) {
2668 CloudEvent event{ORCA_CLOUD_PROVIDER};
2669 if (queue_fn) {
2670 queue_fn([callback, event, return_code, reason_code]() { callback(event, return_code, reason_code); });
2671 } else {
2672 callback(event, return_code, reason_code);
2673 }
2674 }
2675}
AppOnServerConnectedFn on_server_connected_fn
Definition OrcaCloudServiceAgent.hpp:477
std::function< void(CloudEvent event, int return_code, int reason_code)> AppOnServerConnectedFn
Definition ICloudServiceAgent.hpp:45

References on_server_connected_fn, Slic3r::ORCA_CLOUD_PROVIDER(), queue_on_main_fn, and state_mutex.

Referenced by run_health_check().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ is_refresh_running()

bool Slic3r::OrcaCloudServiceAgent::is_refresh_running ( ) const
inline
235{ return refresh_running.load(); }
std::atomic_bool refresh_running
Definition OrcaCloudServiceAgent.hpp:486

References refresh_running.

◆ is_server_connected()

bool Slic3r::OrcaCloudServiceAgent::is_server_connected ( )
overridevirtual

Return whether the server is currently reachable.

Implements Slic3r::ICloudServiceAgent.

1024{
1025 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1026 return is_connected;
1027}
bool is_connected
Definition OrcaCloudServiceAgent.hpp:468

References is_connected, and state_mutex.

◆ is_user_login()

bool Slic3r::OrcaCloudServiceAgent::is_user_login ( )
overridevirtual

Check whether a valid authenticated session exists.

Implements Slic3r::ICloudServiceAgent.

815{
816 std::lock_guard<std::mutex> lock(session_mutex);
817 return session.logged_in;
818}

References session, and session_mutex.

Referenced by build_login_cmd(), and get_user_presets().

Here is the caller graph for this function:

◆ json_to_map()

void Slic3r::OrcaCloudServiceAgent::json_to_map ( const std::string & json,
std::map< std::string, std::string > & map )
private
2640{
2641 try {
2642 auto j = nlohmann::json::parse(json);
2643 for (auto it = j.begin(); it != j.end(); ++it) {
2644 if (it.value().is_string()) {
2645 map[it.key()] = it.value().get<std::string>();
2646 } else {
2647 map[it.key()] = it.value().dump();
2648 }
2649 }
2650 } catch (...) {}
2651}

Referenced by get_shared_bundle(), and get_user_presets().

Here is the caller graph for this function:

◆ load_sync_state()

void Slic3r::OrcaCloudServiceAgent::load_sync_state ( )
1578{
1579 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1580
1581 if (sync_state_path.empty())
1582 return;
1583
1584 try {
1585 std::ifstream ifs(sync_state_path);
1586 if (ifs.good()) {
1587 std::string line;
1588 if (std::getline(ifs, line)) {
1589 sync_state.last_sync_timestamp = std::stoll(line);
1590 }
1591 }
1592 } catch (...) {
1593 sync_state.last_sync_timestamp = 0;
1594 }
1595}

References state_mutex, sync_state, and sync_state_path.

Referenced by set_user_session().

Here is the caller graph for this function:

◆ load_user_secret()

bool Slic3r::OrcaCloudServiceAgent::load_user_secret ( std::string & out_secret)
1719{
1720 out_secret.clear();
1721
1723 // Load from encrypted file only
1725 if (wxFileExists(wxString::FromUTF8(secret_fallback_path.c_str()))) {
1726 std::ifstream ifs(secret_fallback_path, std::ios::binary);
1727 std::string payload((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
1728 auto key = sha256_bytes(get_encryption_key());
1729 std::string plain;
1730 if (!key.empty()) {
1731 std::string encoded_payload = payload;
1732 bool integrity_ok = true;
1733
1734 if (payload.rfind("v2:", 0) == 0) {
1735 auto delim = payload.find(':', 3);
1736 if (delim == std::string::npos) {
1737 BOOST_LOG_TRIVIAL(warning) << "payload missing delim ':'.";
1738 integrity_ok = false;
1739 } else {
1740 std::string stored_hmac = payload.substr(3, delim - 3);
1741 std::string lower_stored = stored_hmac;
1742 std::transform(lower_stored.begin(), lower_stored.end(), lower_stored.begin(), ::tolower);
1743 encoded_payload = payload.substr(delim + 1);
1744
1745 std::string computed_hmac = hmac_sha256_hex(encoded_payload, key);
1746 std::transform(computed_hmac.begin(), computed_hmac.end(), computed_hmac.begin(), ::tolower);
1747 if (computed_hmac.empty() || computed_hmac != lower_stored) {
1748 integrity_ok = false;
1749 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: user secret integrity check failed (HMAC mismatch)";
1750 }
1751 }
1752 }
1753
1754 if (integrity_ok && aes256gcm_decrypt(encoded_payload, key, plain) && !plain.empty()) {
1755 secret_stored = true;
1756 out_secret = plain;
1757 // Upgrade legacy payloads to signed format
1758 if (payload.rfind("v2:", 0) != 0) {
1759 persist_user_secret(out_secret);
1760 }
1761 return true;
1762 }
1763 }
1764 }
1765 } else {
1766 wxSecretStore store = wxSecretStore::GetDefault();
1767 if (store.IsOk()) {
1768 wxString username;
1769 wxSecretValue secret;
1770 if (store.Load(SECRET_STORE_SERVICE, username, secret) && secret.IsOk()) {
1771 out_secret.assign(static_cast<const char*>(secret.GetData()), secret.GetSize());
1772 if (!out_secret.empty()) {
1773 secret_stored = true;
1774 return true;
1775 }
1776 }
1777 }
1778 }
1779
1780 return false;
1781}
void persist_user_secret(const std::string &secret)
Definition OrcaCloudServiceAgent.cpp:1655
bool aes256gcm_decrypt(const std::string &b64_payload, const std::vector< unsigned char > &key, std::string &plaintext)
Definition OrcaCloudServiceAgent.cpp:464
std::string get_encryption_key()
Definition OrcaCloudServiceAgent.cpp:336
std::string hmac_sha256_hex(const std::string &data, const std::vector< unsigned char > &key)
Definition OrcaCloudServiceAgent.cpp:352
std::vector< unsigned char > sha256_bytes(const std::string &input)
Definition OrcaCloudServiceAgent.cpp:345

References compute_fallback_path(), m_use_encrypted_token_file, persist_user_secret(), secret_fallback_path, and secret_stored.

Referenced by refresh_from_storage(), refresh_session_with_token(), and start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ map_to_json()

std::string Slic3r::OrcaCloudServiceAgent::map_to_json ( const std::map< std::string, std::string > & map)
private
2631{
2632 nlohmann::json j;
2633 for (const auto& pair : map) {
2634 j[pair.first] = pair.second;
2635 }
2636 return j.dump();
2637}

◆ modify_printer_name()

int Slic3r::OrcaCloudServiceAgent::modify_printer_name ( std::string dev_id,
std::string dev_name )
overridevirtual

Update printer name in cloud profile.

Implements Slic3r::ICloudServiceAgent.

2868{
2869 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: modify_printer_name (stub)";
2870 return BAMBU_NETWORK_SUCCESS;
2871}

References BAMBU_NETWORK_SUCCESS.

◆ persist_user_secret()

void Slic3r::OrcaCloudServiceAgent::persist_user_secret ( const std::string & secret)
1656{
1657 if (secret.empty()) {
1659 return;
1660 }
1661
1662 bool stored = false;
1663
1665 // Use encrypted file only
1666 auto key = sha256_bytes(get_encryption_key());
1667 if (key.empty()) {
1668 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: cannot derive key for user secret file storage";
1669 return;
1670 }
1671
1672 std::string payload;
1673 if (!aes256gcm_encrypt(secret, key, payload)) {
1674 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: failed to encrypt user secret for file storage";
1675 return;
1676 }
1677
1678 std::string signed_payload = payload;
1679 if (auto mac = hmac_sha256_hex(payload, key); !mac.empty()) {
1680 signed_payload = "v2:" + mac + ":" + payload;
1681 }
1682
1684 if (secret_fallback_path.empty()) {
1685 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: no user secret storage path available; skipping file persistence";
1686 return;
1687 }
1688 wxFileName path(wxString::FromUTF8(secret_fallback_path.c_str()));
1689 path.MakeAbsolute();
1690 if (!wxFileName::DirExists(path.GetPath())) {
1691 wxFileName::Mkdir(path.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
1692 }
1693
1694 if (const std::error_code ec = write_file_atomically(secret_fallback_path, signed_payload, /*binary=*/true))
1695 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: cannot write user secret file " << secret_fallback_path << ": " << ec.message();
1696 else
1697 stored = true;
1698 } else {
1699 // Use wxSecretStore only
1700 wxSecretStore store = wxSecretStore::GetDefault();
1701 if (store.IsOk()) {
1702 wxSecretValue secret_value(wxString::FromUTF8(secret.c_str()));
1703 if (store.Save(SECRET_STORE_SERVICE, SECRET_STORE_USER, secret_value)) {
1704 stored = true;
1705 } else {
1706 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: System Keychain save failed";
1707 }
1708 } else {
1709 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: System Keychain not available";
1710 }
1711 }
1712
1713 if (stored) {
1714 secret_stored = true;
1715 }
1716}
bool aes256gcm_encrypt(const std::string &plaintext, const std::vector< unsigned char > &key, std::string &out_b64)
Definition OrcaCloudServiceAgent.cpp:418
std::error_code write_file_atomically(const std::string &path, std::initializer_list< std::string_view > chunks, bool binary)
Definition utils.cpp:796

References clear_user_secret(), compute_fallback_path(), m_use_encrypted_token_file, secret_fallback_path, secret_stored, and Slic3r::write_file_atomically().

Referenced by load_user_secret(), and set_user_session().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ pkce()

const OrcaCloudServiceAgent::PkceBundle & Slic3r::OrcaCloudServiceAgent::pkce ( )
1627{
1628 if (pkce_bundle.verifier.empty() || pkce_bundle.challenge.empty() || pkce_bundle.state.empty()) {
1630 }
1631 return pkce_bundle;
1632}

References pkce_bundle, and regenerate_pkce().

Referenced by build_login_cmd().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ put_model_mall_rating()

int Slic3r::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 )
overridevirtual

Submit a review for a marketplace design.

Implements Slic3r::ICloudServiceAgent.

2991{
2992 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: put_model_mall_rating (stub)";
2993 http_code = 200;
2994 return BAMBU_NETWORK_SUCCESS;
2995}

References BAMBU_NETWORK_SUCCESS.

◆ put_rating_picture_oss()

int Slic3r::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 )
overridevirtual

Upload rating images to OSS.

Implements Slic3r::ICloudServiceAgent.

3007{
3008 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: put_rating_picture_oss (stub)";
3009 http_code = 200;
3010 return BAMBU_NETWORK_SUCCESS;
3011}

References BAMBU_NETWORK_SUCCESS.

◆ put_setting()

int Slic3r::OrcaCloudServiceAgent::put_setting ( std::string setting_id,
std::string name,
std::map< std::string, std::string > * values_map,
unsigned int * http_code,
bool force = false )
overridevirtual

Update or create a preset with a known setting_id.

Implements Slic3r::ICloudServiceAgent.

1236{
1237 // Extract original_updated_time for Optimistic Concurrency Control
1238 // If present, server will verify version before update. If absent, treated as insert.
1239 std::string original_updated_time;
1240 if (values_map) {
1241 auto it = values_map->find(IOT_JSON_KEY_UPDATED_TIME);
1242 if (it != values_map->end()) {
1243 original_updated_time = it->second;
1244 }
1245 }
1246
1247 // Build content JSON
1248 nlohmann::json content;
1249 content["name"] = name;
1250
1251 if (values_map && !values_map->empty()) {
1252 for (const auto& pair : *values_map) {
1253 // Skip updated_time - it's used for OCC, not as content
1254 if (pair.first == IOT_JSON_KEY_UPDATED_TIME)
1255 continue;
1256 content[pair.first] = pair.second;
1257 }
1258 }
1259
1260 auto result = sync_push(setting_id, name, content, original_updated_time, force);
1261 if (http_code) *http_code = result.http_code;
1262
1263 if (result.success) {
1264 if (values_map && result.new_updated_time != 0) {
1265 (*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.new_updated_time);
1266 }
1267 return BAMBU_NETWORK_SUCCESS;
1268 }
1269
1270 if (result.http_code == 409) {
1271 // Conflict - update values_map with server version
1272 if (values_map && result.server_version.updated_time != 0) {
1273 (*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.server_version.updated_time);
1274 }
1275 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: put_setting conflict - server_updated_time="
1276 << result.server_version.updated_time;
1277 }
1278
1279 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: put_setting failed - " << result.error_message;
1281}
#define BAMBU_NETWORK_ERR_PUT_SETTING_FAILED
Definition bambu_networking.hpp:24
SyncPushResult sync_push(const std::string &profile_id, const std::string &name, const nlohmann::json &content, const std::string &original_updated_time="", bool force=false)
Definition OrcaCloudServiceAgent.cpp:1479

References BAMBU_NETWORK_ERR_PUT_SETTING_FAILED, BAMBU_NETWORK_SUCCESS, IOT_JSON_KEY_UPDATED_TIME, and sync_push().

Here is the call graph for this function:

◆ query_bind_status()

int Slic3r::OrcaCloudServiceAgent::query_bind_status ( std::vector< std::string > query_list,
unsigned int * http_code,
std::string * http_body )
overridevirtual

Query binding status for multiple devices.

Implements Slic3r::ICloudServiceAgent.

2858{
2859 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: query_bind_status (stub)";
2860 if (http_code)
2861 *http_code = 200;
2862 if (http_body)
2863 *http_body = "{}";
2864 return BAMBU_NETWORK_SUCCESS;
2865}

References BAMBU_NETWORK_SUCCESS.

◆ refresh_connection()

int Slic3r::OrcaCloudServiceAgent::refresh_connection ( )
overridevirtual

Force a server state recheck, clearing any cached state. The result arrives through is_server_connected() and OnServerConnectedFn. Implementations may return before the check finishes, fold the call into a check already in flight, and ignore calls while the agent is shutting down.

Implements Slic3r::ICloudServiceAgent.

1034{
1035 if (health_check_stopped.load())
1037 bool expected = false;
1038 if (!health_check_running.compare_exchange_strong(expected, true))
1039 return BAMBU_NETWORK_SUCCESS;
1040 if (health_check_thread.joinable())
1041 health_check_thread.join(); // the previous check is done; only its thread exit is left
1042 try {
1043 health_check_thread = std::thread([this] {
1044 try {
1046 } catch (...) {
1047 // Callback dispatch (std::function copies, CallAfter) must not terminate the app.
1048 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: health check worker exception";
1049 }
1050 health_check_running.store(false);
1051 });
1052 } catch (...) {
1053 // std::system_error, or std::bad_alloc for the callable: leave the next tick free to retry.
1054 health_check_running.store(false);
1055 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: cannot start health check";
1057 }
1058 return BAMBU_NETWORK_SUCCESS;
1059}
#define BAMBU_NETWORK_ERR_CANCELED
Definition bambu_networking.hpp:34
std::atomic_bool health_check_running
Definition OrcaCloudServiceAgent.hpp:488
std::atomic_bool health_check_stopped
Definition OrcaCloudServiceAgent.hpp:489
std::thread health_check_thread
Definition OrcaCloudServiceAgent.hpp:487

References BAMBU_NETWORK_ERR_CANCELED, BAMBU_NETWORK_ERR_CONNECTION_TO_SERVER_FAILED, BAMBU_NETWORK_SUCCESS, health_check_running, health_check_stopped, health_check_thread, and run_health_check().

Here is the call graph for this function:

◆ refresh_from_storage()

RefreshResult Slic3r::OrcaCloudServiceAgent::refresh_from_storage ( const std::string & reason,
bool async = false )
1882{
1883 (void) async; // currently all callers are calling this function synchronous anyway
1884
1885 // Blocks until we own the lock (kernel sleep, no spin). Cannot deadlock permanently:
1886 // the refresh POST is bounded (http_post_token timeout_max) and the lock auto-releases
1887 // on process death, so no holder can keep it longer than that bound.
1888 InterProcessRefreshTokenLock lock(token_lock_path());
1889 if (!lock.locked()) {
1890 // Lock syscall genuinely failed (rare). Proceed best-effort rather than give up.
1891 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: token refresh lock unavailable, "
1892 "proceeding unsynchronized (reason=" << reason << ")";
1893 }
1894
1895 std::string refresh_token;
1896 std::string user_secret;
1897
1898 // We read the refresh token from the OS keychain as the source of truth
1899 if (load_user_secret(user_secret) && !user_secret.empty()) {
1900 SessionInfo stored_session;
1901 parse_stored_secret(user_secret, refresh_token, stored_session);
1902 }
1903
1904 // We only read from memory if the read from OS keychain fails
1905 if (refresh_token.empty())
1906 refresh_token = get_refresh_token();
1907
1908 if (refresh_token.empty()) {
1909 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: no refresh token available for refresh (reason=" << reason << ")";
1910 return RefreshResult::AuthRejected; // no persisted token: nothing to preserve
1911 }
1912
1913 // Synchronous: hold the lock across the network round-trip AND the write-back that
1914 // set_user_session performs, so the rotation is atomic w.r.t. other instances.
1915 return refresh_now(refresh_token, reason, /*async=*/false);
1916}
std::string get_refresh_token() const override
Definition OrcaCloudServiceAgent.cpp:960
std::string token_lock_path() const
Definition OrcaCloudServiceAgent.cpp:3893
RefreshResult refresh_now(const std::string &refresh_token, const std::string &reason, bool async=false)
Definition OrcaCloudServiceAgent.cpp:1849
bool load_user_secret(std::string &out_secret)
Definition OrcaCloudServiceAgent.cpp:1718
static bool parse_stored_secret(const std::string &secret, std::string &out_refresh_token, OrcaCloudServiceAgent::SessionInfo &out_session)
Decode a saved user session or a refresh token.
Definition OrcaCloudServiceAgent.cpp:632

References Slic3r::AuthRejected, get_refresh_token(), load_user_secret(), Slic3r::parse_stored_secret(), refresh_now(), and token_lock_path().

Referenced by attempt_refresh_after_unauthorized(), and refresh_if_expiring().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ refresh_if_expiring()

bool Slic3r::OrcaCloudServiceAgent::refresh_if_expiring ( std::chrono::seconds skew,
const std::string & reason )
1919{
1920 const auto needs_refresh = [&] {
1921 std::lock_guard<std::mutex> lock(session_mutex);
1922 return should_refresh_locked(skew);
1923 };
1924 if (!needs_refresh())
1925 return true;
1926
1927 // A refresh may already be running on another thread, e.g. the one start() launches. Wait for
1928 // it and check again instead of spending the refresh token it is rotating. Bounded: the
1929 // refresh POST times out after 30 s.
1930 const auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(60);
1931 while (refresh_running.load() && std::chrono::steady_clock::now() < deadline)
1932 std::this_thread::sleep_for(std::chrono::milliseconds(10));
1933 if (!needs_refresh())
1934 return true;
1935
1936 // First attempt. refresh_from_storage blocks on the cross-process lock and reads the
1937 // freshest token from the store, so cross-instance contention is already resolved here.
1938 RefreshResult r = refresh_from_storage(reason, false);
1939 if (r == RefreshResult::Success) return true;
1940 if (r == RefreshResult::AuthRejected) return false; // definitive: retrying the same token can't help
1941
1942 // One retry, only for a transient network failure of the refresh POST (lost response,
1943 // timeout, 429/5xx -> Transient). The retry re-acquires the lock and re-reads the store.
1944 std::this_thread::sleep_for(std::chrono::milliseconds(750));
1945 if (!needs_refresh())
1946 return true;
1947 return refresh_from_storage(reason + "_retry", false) == RefreshResult::Success;
1948}
bool should_refresh_locked(std::chrono::seconds skew) const
Definition OrcaCloudServiceAgent.cpp:1809
@ Success
Definition OrcaCloudServiceAgent.hpp:50

References Slic3r::AuthRejected, refresh_from_storage(), refresh_running, session_mutex, should_refresh_locked(), and Slic3r::Success.

Referenced by ensure_token_fresh().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ refresh_now()

RefreshResult Slic3r::OrcaCloudServiceAgent::refresh_now ( const std::string & refresh_token,
const std::string & reason,
bool async = false )
1850{
1851 if (refresh_token.empty()) return RefreshResult::AuthRejected; // nothing to refresh
1852
1853 bool expected = false;
1854 if (!refresh_running.compare_exchange_strong(expected, true)) {
1855 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: refresh already running, skip (reason=" << reason << ")";
1856 // Another refresh is already in flight. Treat as transient so we keep the session
1857 // instead of logging out: that in-flight refresh surfaces its own Success/AuthRejected,
1858 // so a genuine rejection is only deferred to the next request, never lost.
1860 }
1861
1862 auto worker = [this, refresh_token, reason]() {
1863 RefreshResult r = refresh_session_with_token(refresh_token, reason);
1864 refresh_running.store(false);
1865 return r;
1866 };
1867
1868 if (async) {
1869 if (refresh_thread.joinable()) {
1870 refresh_thread.join();
1871 }
1872 refresh_thread = std::thread([worker]() { worker(); });
1873 // Fire-and-forget: the outcome isn't known yet and no current caller consumes it.
1874 // Return Transient (indeterminate) rather than implying a completed, successful refresh.
1876 }
1877
1878 return worker();
1879}
RefreshResult refresh_session_with_token(const std::string &refresh_token, const std::string &reason="")
Definition OrcaCloudServiceAgent.cpp:1980

References Slic3r::AuthRejected, refresh_running, refresh_session_with_token(), refresh_thread, and Slic3r::Transient.

Referenced by refresh_from_storage(), and start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ refresh_session_with_token()

RefreshResult Slic3r::OrcaCloudServiceAgent::refresh_session_with_token ( const std::string & refresh_token,
const std::string & reason = "" )
1981{
1982 std::string body = "{\"refresh_token\":\"" + refresh_token + "\"}";
1983 std::string url = auth_base_url + auth_constants::TOKEN_PATH + "?grant_type=refresh_token";
1984 std::string response;
1985 unsigned int http_code = 0;
1986 // http_post_token sets http_code to 0 when the server could not be reached.
1987 http_post_token(body, &response, &http_code, url);
1988
1989 const long long now_s = now_epoch_seconds();
1990 const long long last_ok = last_refresh_success_epoch.load(std::memory_order_relaxed);
1991 const long long since_last_ok = (last_ok == 0) ? -1 : (now_s - last_ok); // -1 = no success yet this process
1992 const long long since_start = now_s - agent_start_epoch;
1993 const std::string log_reason = reason.empty() ? "-" : reason;
1994
1995 bool established = false;
1996 if (http_code >= 200 && http_code < 300) {
1997 if (session_handler) {
1998 established = session_handler(response);
1999 } else {
2000 // No session handler set - parse the token response directly and establish the
2001 // session, so OrcaCloudServiceAgent is self-contained without external setup.
2002 try {
2003 established = set_user_session(json::parse(response));
2004 } catch (const std::exception& e) {
2005 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: token refresh parse exception - " << e.what();
2006 }
2007 }
2008 if (established) {
2009 last_refresh_success_epoch.store(now_s, std::memory_order_relaxed);
2010 BOOST_LOG_TRIVIAL(info) << "[auth] event=refresh_ok reason=" << log_reason
2011 << " secs_since_last_success=" << since_last_ok;
2012 }
2013 } else {
2014 // Diagnostics to classify a future refresh_token_already_used incident from logs alone
2015 // - secs_since_last_success large (or -1 = none this process) => stale token beyond the
2016 // reuse-grace window (the ">1h gap" residual).
2017 // - stored_differs=true => another instance/device already rotated the token in the
2018 // shared secret store (the multi-session residual).
2019 const std::string error_code = extract_error_code(response);
2020
2021 // Only meaningful when the server actually rejected us (not a bare transport failure).
2022 bool stored_present = false, stored_differs = false;
2023 if (http_code >= 400) {
2024 std::string stored_secret, stored_token;
2025 SessionInfo ignored;
2026 if (load_user_secret(stored_secret) && !stored_secret.empty()
2027 && parse_stored_secret(stored_secret, stored_token, ignored) && !stored_token.empty()) {
2028 stored_present = true;
2029 stored_differs = (stored_token != refresh_token);
2030 }
2031 }
2032
2033 std::string user_id_copy;
2034 {
2035 std::lock_guard<std::mutex> lock(session_mutex);
2036 user_id_copy = session.user_id;
2037 }
2038
2039 std::string truncated_response = response.size() > 200 ? response.substr(0, 200) + "..." : response;
2040 BOOST_LOG_TRIVIAL(warning) << "[auth] event=refresh_rejected http_code=" << http_code
2041 << " error_code=" << (error_code.empty() ? "-" : error_code)
2042 << " reason=" << log_reason
2043 << " stored_present=" << (stored_present ? "true" : "false")
2044 << " stored_differs=" << (stored_differs ? "true" : "false")
2045 << " secs_since_last_success=" << since_last_ok
2046 << " secs_since_start=" << since_start
2047 << " user_id=" << (user_id_copy.empty() ? "-" : user_id_copy)
2048 << " response_body=" << truncated_response;
2049 }
2050
2051 return classify_refresh_result(http_code, established);
2052}
SessionHandler session_handler
Definition OrcaCloudServiceAgent.hpp:456
std::atomic< long long > last_refresh_success_epoch
Definition OrcaCloudServiceAgent.hpp:463
const long long agent_start_epoch
Definition OrcaCloudServiceAgent.hpp:464
static std::string extract_error_code(const std::string &body)
Definition OrcaCloudServiceAgent.cpp:1966
static long long now_epoch_seconds()
Definition OrcaCloudServiceAgent.cpp:1974
static RefreshResult classify_refresh_result(unsigned http_code, bool session_established)
Definition OrcaCloudServiceAgent.cpp:1952

References agent_start_epoch, auth_base_url, Slic3r::classify_refresh_result(), Slic3r::extract_error_code(), http_post_token(), last_refresh_success_epoch, load_user_secret(), Slic3r::now_epoch_seconds(), Slic3r::parse_stored_secret(), session, session_handler, session_mutex, set_user_session(), and Slic3r::auth_constants::TOKEN_PATH.

Referenced by refresh_now().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ regenerate_pkce()

void Slic3r::OrcaCloudServiceAgent::regenerate_pkce ( )
1635{
1637 pkce_bundle.challenge = sha256_base64url(pkce_bundle.verifier);
1639 if (pkce_bundle.redirect.empty()) {
1640 pkce_bundle.redirect = "http://localhost:" + std::to_string(pkce_bundle.loopback_port) + auth_constants::LOOPBACK_PATH;
1641 }
1642}
std::string generate_state_token()
Definition OrcaCloudServiceAgent.cpp:270
std::string generate_code_verifier()
Definition OrcaCloudServiceAgent.cpp:263
std::string sha256_base64url(const std::string &input)
Definition OrcaCloudServiceAgent.cpp:280
constexpr const char * LOOPBACK_PATH
Definition OrcaCloudServiceAgent.hpp:58

References Slic3r::auth_constants::LOOPBACK_PATH, and pkce_bundle.

Referenced by OrcaCloudServiceAgent(), build_login_cmd(), pkce(), and start().

Here is the caller graph for this function:

◆ request_setting_id()

std::string Slic3r::OrcaCloudServiceAgent::request_setting_id ( std::string name,
std::map< std::string, std::string > * values_map,
unsigned int * http_code )
overridevirtual

Request a new preset identifier from the server.

Implements Slic3r::ICloudServiceAgent.

1185{
1186 std::string new_id = generate_uuid_for_setting_id(name, get_user_id());
1187 if (new_id.empty()) {
1188 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: request_setting_id failed - name is empty";
1189 return "";
1190 }
1191
1192 nlohmann::json content;
1193 content["name"] = name;
1194 content["type"] = IOT_PRINT_TYPE_STRING; // Default type
1195
1196 if (values_map && !values_map->empty()) {
1197 for (const auto& pair : *values_map) {
1198 // Skip updated_time - it's metadata, not content
1199 if (pair.first == IOT_JSON_KEY_UPDATED_TIME)
1200 continue;
1201 content[pair.first] = pair.second;
1202 }
1203 }
1204
1205 // Use sync_push to create the profile (no original_updated_time for new profiles per spec)
1206 auto result = sync_push(new_id, name, content, "");
1207 if (http_code)
1208 *http_code = result.http_code;
1209
1210 // 409 duplicate_profile_uuid in the create path means the deterministic id we
1211 // just generated already exists in this account: the earlier create succeeded.
1212 // Adopt it instead of failing, so sync_preset persists the id and stops retrying.
1213 if (result.http_code == 409 && result.conflict_code == -2
1214 && !result.server_version.id.empty() && result.server_version.id == new_id) {
1215 if (values_map && result.server_version.updated_time != 0)
1216 (*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.server_version.updated_time);
1217 if (http_code)
1218 *http_code = 200;
1219 BOOST_LOG_TRIVIAL(info) << "OrcaCloudServiceAgent: request_setting_id adopted existing profile id " << new_id << " (409 duplicate_profile_uuid)";
1220 return new_id;
1221 }
1222
1223 if (result.success) {
1224 if (values_map && result.new_updated_time != 0) {
1225 (*values_map)[IOT_JSON_KEY_UPDATED_TIME] = std::to_string(result.new_updated_time);
1226 }
1227 return new_id;
1228 }
1229
1230 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: request_setting_id failed - " << result.error_message
1231 << " - http code: " << result.http_code;
1232 return "";
1233}
static std::string generate_uuid_for_setting_id(const std::string &name, const std::string &user_id="")
Definition OrcaCloudServiceAgent.cpp:537

References generate_uuid_for_setting_id(), get_user_id(), IOT_JSON_KEY_UPDATED_TIME, IOT_PRINT_TYPE_STRING, and sync_push().

Here is the call graph for this function:

◆ request_web_session_handoff()

int Slic3r::OrcaCloudServiceAgent::request_web_session_handoff ( std::string & token_hash,
std::string & error_code,
unsigned int & http_code )
3106{
3107 token_hash.clear();
3108 error_code.clear();
3109 http_code = 0;
3110
3111 const std::string url = api_base_url + ORCA_WEB_SESSION_HANDOFF_PATH;
3112 BOOST_LOG_TRIVIAL(trace) << "OrcaCloudServiceAgent: POST " << ORCA_WEB_SESSION_HANDOFF_PATH;
3113
3114 // Refresh the desktop token the usual way before minting (newest access token).
3115 ensure_token_fresh("web_session_handoff");
3116
3117 auto perform = [&]() {
3118 HttpResult result;
3119 try {
3120 auto http = Http::post(url);
3121 http.tls_verify(true);
3122
3123 const std::string token = get_access_token();
3124
3125 auto headers = data_headers();
3126 for (const auto& pair : headers) {
3127 http.header(pair.first, pair.second);
3128 }
3129 if (!token.empty()) {
3130 http.header("Authorization", "Bearer " + token);
3131 }
3132
3133 // No request body: the endpoint identifies the user by the bearer token alone.
3134 http.on_complete([&](std::string resp_body, unsigned resp_status) {
3135 result.success = true;
3136 result.status = resp_status;
3137 result.body = resp_body;
3138 })
3139 .on_error([&](std::string resp_body, std::string error, unsigned resp_status) {
3140 result.success = false;
3141 result.status = resp_status == 0 ? 404 : resp_status;
3142 result.body = resp_body;
3143 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: web-session-handoff HTTP error - " << error;
3144 })
3145 .timeout_max(30)
3146 .perform_sync();
3147
3148 } catch (const std::exception& e) {
3149 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: web-session-handoff exception - " << e.what();
3150 }
3151 return result;
3152 };
3153
3154 HttpResult res = perform();
3155 // Reuse the shared 401 policy (refresh once, then retry), exactly like http_get/http_post:
3156 // `suppress` is true for a transient refresh failure (keep the session and token, no logout)
3157 // and false when the refresh token was rejected — so a 401 that remains after the refresh
3158 // surfaces through the generic HTTP-error callback and drives the normal desktop logout.
3159 // Every other handoff status (409/429/503/transport) stays silent here, so the page can
3160 // offer "Try again" instead of the desktop showing a generic cloud error.
3161 const bool suppress = resolve_unauthorized(res, perform, "web_session_handoff");
3162 if (!suppress && res.status == 401)
3163 invoke_http_error_callback(res.status, res.body);
3164
3165 http_code = res.status;
3166 if (res.status == 200) {
3167 const json j = json::parse(res.body, nullptr, false);
3168 if (!j.is_discarded() && j.is_object() && j.contains("token_hash") && j["token_hash"].is_string())
3169 token_hash = j["token_hash"].get<std::string>();
3170 } else {
3171 error_code = handoff_error_code(res.body);
3172 }
3173
3174 return (res.success && res.status == 200) ? BAMBU_NETWORK_SUCCESS : BAMBU_NETWORK_ERR_CONNECT_FAILED;
3175}
constexpr const char * ORCA_WEB_SESSION_HANDOFF_PATH
Definition OrcaCloudServiceAgent.cpp:115
static std::string handoff_error_code(const std::string &body)
Definition OrcaCloudServiceAgent.cpp:3091

References api_base_url, BAMBU_NETWORK_ERR_CONNECT_FAILED, BAMBU_NETWORK_SUCCESS, Slic3r::OrcaCloudServiceAgent::HttpResult::body, data_headers(), ensure_token_fresh(), get_access_token(), Slic3r::handoff_error_code(), Slic3r::http, invoke_http_error_callback(), Slic3r::Http::post(), resolve_unauthorized(), Slic3r::OrcaCloudServiceAgent::HttpResult::status, Slic3r::OrcaCloudServiceAgent::HttpResult::success, and Slic3r::trace().

Here is the call graph for this function:

◆ resolve_unauthorized()

bool Slic3r::OrcaCloudServiceAgent::resolve_unauthorized ( HttpResult & res,
const std::function< HttpResult()> & perform,
const std::string & reason )
private
2195{
2196 if (res.status != 401)
2197 return false;
2198
2200 if (rr == RefreshResult::Success) {
2201 res = perform(); // refreshed: retry the original request with the new token
2202 return false;
2203 }
2204
2205 // Transient (no connection / 5xx / 429 / ambiguous): keep the session and token,
2206 // suppress the auth error so the GUI does not log the user out.
2207 // AuthRejected (refresh token genuinely rejected): let the 401 surface -> logout.
2208 return rr == RefreshResult::Transient;
2209}
RefreshResult attempt_refresh_after_unauthorized(const std::string &reason)
Definition OrcaCloudServiceAgent.cpp:2167

References attempt_refresh_after_unauthorized(), Slic3r::OrcaCloudServiceAgent::HttpResult::status, Slic3r::Success, and Slic3r::Transient.

Referenced by http_delete(), http_get(), http_post(), http_put(), and request_web_session_handoff().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ run_health_check()

bool Slic3r::OrcaCloudServiceAgent::run_health_check ( const std::atomic_bool * cancel)
private
976{
977 if (cancel && cancel->load())
978 return false;
979
980 HttpResult res;
981 try {
982 auto http = Http::get(api_base_url + ORCA_HEALTH_PATH);
983 http.tls_verify(true);
984 for (const auto& [key, value] : data_headers())
985 http.header(key, value);
986 if (cancel)
987 http.on_progress([cancel](Http::Progress, bool& cancel_request) { cancel_request = cancel->load(); });
988 http.on_complete([&](std::string body, unsigned status) {
989 res.success = true;
990 res.status = status;
991 res.body = std::move(body);
992 })
993 .on_error([&](std::string body, std::string error, unsigned status) {
994 res.status = status == 0 ? 404 : status; // same mapping as http_get
995 res.body = std::move(body);
996 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: health check failed - " << error;
997 })
998 .timeout_max(30)
999 .perform_sync();
1000 } catch (const std::exception& e) {
1001 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: health check exception - " << e.what();
1002 }
1003 // Stopped mid-request: report nothing, the receivers may be going away.
1004 if (cancel && cancel->load())
1005 return false;
1006
1007 const bool connected = res.success;
1008 {
1009 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1010 is_connected = connected;
1011 }
1012 if (!connected)
1013 invoke_http_error_callback(res.status, res.body);
1014 invoke_server_connected_callback(connected ? 0 : -1, res.status);
1015 return connected;
1016}
bool success
Definition OrcaCloudServiceAgent.hpp:391
void invoke_server_connected_callback(int return_code, int reason_code)
Definition OrcaCloudServiceAgent.cpp:2657

References api_base_url, Slic3r::OrcaCloudServiceAgent::HttpResult::body, data_headers(), Slic3r::Http::get(), Slic3r::http, invoke_http_error_callback(), invoke_server_connected_callback(), is_connected, state_mutex, Slic3r::OrcaCloudServiceAgent::HttpResult::status, and Slic3r::OrcaCloudServiceAgent::HttpResult::success.

Referenced by connect_server(), and refresh_connection().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ save_sync_state()

void Slic3r::OrcaCloudServiceAgent::save_sync_state ( )
1598{
1599 std::lock_guard<std::recursive_mutex> lock(state_mutex);
1600
1601 if (sync_state_path.empty())
1602 return;
1603
1604 if (const std::error_code ec = write_file_atomically(sync_state_path, std::to_string(sync_state.last_sync_timestamp)))
1605 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: failed to save the sync state: " << ec.message();
1606}

References state_mutex, sync_state, sync_state_path, and Slic3r::write_file_atomically().

Referenced by get_setting_list2(), and get_user_presets().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ set_api_base_url()

void Slic3r::OrcaCloudServiceAgent::set_api_base_url ( const std::string & url)
584{ api_base_url = url; }

References api_base_url.

◆ set_auth_base_url()

void Slic3r::OrcaCloudServiceAgent::set_auth_base_url ( const std::string & url)
586{ auth_base_url = url; }

References auth_base_url.

◆ set_cert_file()

int Slic3r::OrcaCloudServiceAgent::set_cert_file ( std::string folder,
std::string filename )
overridevirtual

Register the client certificate file for TLS authentication. May be unused by some implementations (e.g., OrcaCloudServiceAgent).

Implements Slic3r::ICloudServiceAgent.

610{
611 // Not used by OrcaCloudServiceAgent (OAuth doesn't need client certs)
612 (void) folder;
613 (void) filename;
615}

References BAMBU_NETWORK_SUCCESS.

◆ set_cloud_base_url()

void Slic3r::OrcaCloudServiceAgent::set_cloud_base_url ( const std::string & url)
588{ cloud_base_url = url; }

References cloud_base_url.

◆ set_config_dir()

int Slic3r::OrcaCloudServiceAgent::set_config_dir ( std::string config_dir)
overridevirtual

Provide the writable configuration directory for storing auth state. Must be called before start().

Implements Slic3r::ICloudServiceAgent.

601{
602 config_dir = cfg_dir;
603 wxFileName fallback(wxString::FromUTF8(cfg_dir.c_str()), secret_constants::USER_SECRET_FILENAME);
604 fallback.MakeAbsolute();
605 secret_fallback_path = fallback.GetFullPath().ToStdString();
607}
std::string config_dir
Definition OrcaCloudServiceAgent.hpp:438
constexpr const char * USER_SECRET_FILENAME
Definition OrcaCloudServiceAgent.hpp:69

References BAMBU_NETWORK_SUCCESS, config_dir, secret_fallback_path, and Slic3r::secret_constants::USER_SECRET_FILENAME.

◆ set_country_code()

int Slic3r::OrcaCloudServiceAgent::set_country_code ( std::string country_code)
overridevirtual

Set the country code for region-specific backend selection.

Implements Slic3r::ICloudServiceAgent.

618{
619 country_code = code;
621}
std::string country_code
Definition OrcaCloudServiceAgent.hpp:442

References BAMBU_NETWORK_SUCCESS, and country_code.

◆ set_get_country_code_fn()

int Slic3r::OrcaCloudServiceAgent::set_get_country_code_fn ( GetCountryCodeFn fn)
overridevirtual

Provide country code getter callback.

Implements Slic3r::ICloudServiceAgent.

2716{
2717 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2719 return BAMBU_NETWORK_SUCCESS;
2720}
GetCountryCodeFn get_country_code_fn
Definition OrcaCloudServiceAgent.hpp:479

References BAMBU_NETWORK_SUCCESS, get_country_code_fn, and state_mutex.

◆ set_on_http_error_fn()

int Slic3r::OrcaCloudServiceAgent::set_on_http_error_fn ( AppOnHttpErrorFn fn)
overridevirtual

Register HTTP error callback.

Implements Slic3r::ICloudServiceAgent.

2709{
2710 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2711 on_http_error_fn = fn;
2712 return BAMBU_NETWORK_SUCCESS;
2713}

References BAMBU_NETWORK_SUCCESS, on_http_error_fn, and state_mutex.

◆ set_on_login_complete_handler()

void Slic3r::OrcaCloudServiceAgent::set_on_login_complete_handler ( OnLoginCompleteHandler handler)
1624{ on_login_complete_handler = std::move(handler); }
OnLoginCompleteHandler on_login_complete_handler
Definition OrcaCloudServiceAgent.hpp:457

References on_login_complete_handler.

◆ set_on_server_connected_fn()

int Slic3r::OrcaCloudServiceAgent::set_on_server_connected_fn ( AppOnServerConnectedFn fn)
overridevirtual

Register server connection status callback.

Implements Slic3r::ICloudServiceAgent.

2702{
2703 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2705 return BAMBU_NETWORK_SUCCESS;
2706}

References BAMBU_NETWORK_SUCCESS, on_server_connected_fn, and state_mutex.

◆ set_queue_on_main_fn()

int Slic3r::OrcaCloudServiceAgent::set_queue_on_main_fn ( QueueOnMainFn fn)
overridevirtual

Provide main thread queue callback.

Implements Slic3r::ICloudServiceAgent.

2723{
2724 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2725 queue_on_main_fn = fn;
2726 return BAMBU_NETWORK_SUCCESS;
2727}

References BAMBU_NETWORK_SUCCESS, queue_on_main_fn, and state_mutex.

◆ set_session_handler()

void Slic3r::OrcaCloudServiceAgent::set_session_handler ( SessionHandler handler)
1621{ session_handler = std::move(handler); }

References session_handler.

◆ set_use_encrypted_token_file()

void Slic3r::OrcaCloudServiceAgent::set_use_encrypted_token_file ( bool use)

◆ set_user_session() [1/2]

bool Slic3r::OrcaCloudServiceAgent::set_user_session ( const nlohmann::json & session_json,
bool notify_login = true )
2109{
2110 std::string access_token = get_json_string_field(session_json, "access_token");
2111 if (access_token.empty()) {
2112 access_token = get_json_string_field(session_json, "token");
2113 }
2114 std::string refresh_token = get_json_string_field(session_json, "refresh_token");
2115
2116 std::string user_id, username, nickname, avatar;
2117 if (session_json.contains("user") && session_json["user"].is_object()) {
2118 // Nested format (Orca cloud / GoTrue response)
2119 const auto& user = session_json["user"];
2120 user_id = get_json_string_field(user, "id");
2121
2122 if (user.contains("user_metadata") && user["user_metadata"].is_object()) {
2123 const auto& meta = user["user_metadata"];
2124 username = get_json_string_field(meta, "username"); // Orca Cloud's unique username
2125
2126 // Orca Cloud's primary display name field is display_name.
2127 // Fallback to different names from different providers if display_name is not set.
2128 nickname = resolve_display_name(get_json_string_field(meta, "display_name"), get_json_string_field(meta, "nickname"),
2129 get_json_string_field(meta, "full_name"), get_json_string_field(meta, "name"), username);
2130 avatar = get_json_string_field(meta, "avatar_url");
2131 }
2132 } else {
2133 // Flat format (WebView direct token flow)
2134 user_id = get_json_string_field(session_json, "user_id");
2135 username = get_json_string_field(session_json, "username");
2136 nickname = resolve_display_name(get_json_string_field(session_json, "display_name"),
2137 get_json_string_field(session_json, "nickname"), get_json_string_field(session_json, "full_name"),
2138 get_json_string_field(session_json, "name"), username);
2139 avatar = get_json_string_field(session_json, "avatar");
2140 }
2141
2142 if (access_token.empty() || user_id.empty()) {
2143 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: session payload missing access_token or user id";
2144 return false;
2145 }
2146
2147 bool success = set_user_session(access_token, user_id, username, nickname, avatar, refresh_token);
2148 if (success && notify_login && on_login_complete_handler) {
2149 on_login_complete_handler(true, user_id);
2150 }
2151 return success;
2152}
std::string resolve_display_name(const std::string &display_name, const std::string &nickname, const std::string &full_name, const std::string &name, const std::string &username)
Definition OrcaCloudServiceAgent.cpp:202

References on_login_complete_handler, and set_user_session().

Here is the call graph for this function:

◆ set_user_session() [2/2]

bool Slic3r::OrcaCloudServiceAgent::set_user_session ( const std::string & token,
const std::string & user_id,
const std::string & username,
const std::string & nickname,
const std::string & avatar,
const std::string & refresh_token = "",
bool persist = true )
2065{
2066 std::chrono::system_clock::time_point exp_tp{};
2067 decode_jwt_expiry(token, exp_tp);
2068
2069 {
2070 std::lock_guard<std::mutex> lock(session_mutex);
2071 session.access_token = token;
2072 session.refresh_token = refresh_token;
2073 session.user_id = user_id;
2074 session.user_name = username;
2075 session.user_nickname = nickname;
2076 session.user_avatar = avatar;
2077 session.expires_at = exp_tp;
2078 session.logged_in = true;
2079 }
2080
2081 if (persist) {
2082 // Store user session on disk to not block use from using
2083 // an already logged in account if internet is not available.
2084 // Don't store access token though, we should always refresh it
2085 // once user is back online.
2086 json sec = json::object();
2087 sec["refresh_token"] = refresh_token;
2088 sec["user_id"] = user_id;
2089 sec["username"] = username;
2090 sec["nickname"] = nickname;
2091 persist_user_secret(sec.dump());
2092 }
2093
2094 // Set per-user sync state path
2095 if (!config_dir.empty() && !user_id.empty()) {
2096 boost::filesystem::path user_dir = boost::filesystem::path(config_dir) / "user" / user_id;
2097 if (!boost::filesystem::exists(user_dir)) {
2098 boost::filesystem::create_directories(user_dir);
2099 }
2100 sync_state_path = (user_dir / ORCA_SYNC_STATE_FILE).string();
2102 }
2103
2104 BOOST_LOG_TRIVIAL(info) << "OrcaCloudServiceAgent: set_user_session - user_id=" << user_id << ", username=" << username;
2105 return true;
2106}
void load_sync_state()
Definition OrcaCloudServiceAgent.cpp:1577
bool decode_jwt_expiry(const std::string &token, std::chrono::system_clock::time_point &out_tp)
Definition OrcaCloudServiceAgent.cpp:1820
constexpr const char * ORCA_SYNC_STATE_FILE
Definition OrcaCloudServiceAgent.cpp:100

References config_dir, decode_jwt_expiry(), load_sync_state(), persist_user_secret(), session, session_mutex, and sync_state_path.

Referenced by change_user(), refresh_session_with_token(), set_user_session(), and start().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ should_refresh_locked()

bool Slic3r::OrcaCloudServiceAgent::should_refresh_locked ( std::chrono::seconds skew) const
private
1810{
1811 if (!session.logged_in)
1812 return false;
1813 if (session.expires_at.time_since_epoch().count() == 0)
1814 return true;
1815
1816 auto now = std::chrono::system_clock::now();
1817 return (session.expires_at - now) <= skew;
1818}

References session.

Referenced by refresh_if_expiring().

Here is the caller graph for this function:

◆ start()

int Slic3r::OrcaCloudServiceAgent::start ( )
overridevirtual

Start the agent, performing any expensive initialization. Typically regenerates PKCE bundles and attempts silent sign-in.

Implements Slic3r::ICloudServiceAgent.

667{
669
670 // Attempt silent sign-in from stored refresh token
671 std::string stored_secret;
672 if (load_user_secret(stored_secret) && !stored_secret.empty()) {
673 // Backward compatibility: if secret it a json, then read it as use session,
674 // which allows us to refresh it in a background thread to speed up the app startup;
675 // otherwise it's a plain refresh token, then we force a sync refresh
676 std::string refresh_token;
677 SessionInfo stored_session;
678 if (parse_stored_secret(stored_secret, refresh_token, stored_session)) {
679 if (stored_session.logged_in) {
680 // We have a previously saved user session, use it. Skip re-persisting: the secret was
681 // just loaded from disk, so writing the identical bytes back is wasted startup I/O.
682 set_user_session(stored_session.access_token, stored_session.user_id, stored_session.user_name,
683 stored_session.user_nickname, stored_session.user_avatar, stored_session.refresh_token,
684 /*persist=*/false);
685 }
686 refresh_now(refresh_token, "refresh token", stored_session.logged_in);
687 }
688 }
689
691}

References Slic3r::OrcaCloudServiceAgent::SessionInfo::access_token, BAMBU_NETWORK_SUCCESS, load_user_secret(), Slic3r::OrcaCloudServiceAgent::SessionInfo::logged_in, Slic3r::parse_stored_secret(), refresh_now(), Slic3r::OrcaCloudServiceAgent::SessionInfo::refresh_token, regenerate_pkce(), set_user_session(), Slic3r::OrcaCloudServiceAgent::SessionInfo::user_avatar, Slic3r::OrcaCloudServiceAgent::SessionInfo::user_id, Slic3r::OrcaCloudServiceAgent::SessionInfo::user_name, and Slic3r::OrcaCloudServiceAgent::SessionInfo::user_nickname.

Here is the call graph for this function:

◆ start_publish()

int Slic3r::OrcaCloudServiceAgent::start_publish ( PublishParams params,
OnUpdateStatusFn update_fn,
WasCancelledFn cancel_fn,
std::string * out )
overridevirtual

Run multi-stage publishing workflow.

Implements Slic3r::ICloudServiceAgent.

2890{
2891 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: start_publish (stub)";
2892 return BAMBU_NETWORK_SUCCESS;
2893}

References BAMBU_NETWORK_SUCCESS.

◆ start_subscribe()

int Slic3r::OrcaCloudServiceAgent::start_subscribe ( std::string module)
overridevirtual

Subscribe to a logical module (e.g., "printer", "user").

Implements Slic3r::ICloudServiceAgent.

1069{
1070 (void) module;
1071 return BAMBU_NETWORK_SUCCESS;
1072}
FileTransferModule & module()
Definition FileTransferUtils.hpp:248

References BAMBU_NETWORK_SUCCESS, and Slic3r::module().

Here is the call graph for this function:

◆ stop_health_check()

void Slic3r::OrcaCloudServiceAgent::stop_health_check ( )
1062{
1063 health_check_stopped.store(true);
1064 if (health_check_thread.joinable())
1065 health_check_thread.join();
1066}

References health_check_stopped, and health_check_thread.

Referenced by ~OrcaCloudServiceAgent().

Here is the caller graph for this function:

◆ stop_subscribe()

int Slic3r::OrcaCloudServiceAgent::stop_subscribe ( std::string module)
overridevirtual

Stop listening to a formerly subscribed module.

Implements Slic3r::ICloudServiceAgent.

1075{
1076 (void) module;
1077 return BAMBU_NETWORK_SUCCESS;
1078}

References BAMBU_NETWORK_SUCCESS, and Slic3r::module().

Here is the call graph for this function:

◆ subscribe_plugin()

int Slic3r::OrcaCloudServiceAgent::subscribe_plugin ( const std::string & plugin_uuid)
3751{
3752 if (plugin_uuid.empty())
3753 return -1;
3754
3755 const std::string request_body = json{{"plugin_id", plugin_uuid}}.dump();
3756 std::string response_body;
3757 unsigned int http_code = 0;
3758
3759 const int result = http_post(ORCA_SUBSCRIBED_PLUGINS, request_body, &response_body, &http_code);
3760 // Treat an existing subscription as success: the requested cloud state already exists.
3761 if (http_code == 409)
3762 return 0;
3763 if (result != 0 || (http_code != 200 && http_code != 201)) {
3764 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: http_code=" << http_code << ", response=" << response_body;
3765 return http_code != 0 ? static_cast<int>(http_code) : result;
3766 }
3767
3768 return 0;
3769}

References http_post().

Here is the call graph for this function:

◆ sync_pull()

int Slic3r::OrcaCloudServiceAgent::sync_pull ( std::function< void(const SyncPullResponse &)> on_success,
std::function< void(int http_code, const std::string &error)> on_error )
private
1408{
1409 std::string path = ORCA_SYNC_PULL_PATH;
1410 if (sync_state.last_sync_timestamp != 0) {
1411 path += "?cursor=" + std::to_string(sync_state.last_sync_timestamp);
1412 }
1413
1414 std::string response;
1415 unsigned int http_code = 0;
1416 int result = http_get(path, &response, &http_code);
1417
1418 // Handle 410 Gone - cursor too old, need full resync
1419 if (http_code == 410) {
1420 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: sync_pull returned 410 Gone - cursor too old, triggering full resync";
1422 // Retry without cursor
1423 path = ORCA_SYNC_PULL_PATH;
1424 result = http_get(path, &response, &http_code);
1425 }
1426
1427 if (result != BAMBU_NETWORK_SUCCESS || (http_code != 200 && http_code != 304)) {
1428 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: sync_pull failed - http_code=" << http_code << " - path=" << path;
1429 if (on_error)
1430 on_error(http_code, response);
1432 }
1433
1434 if (http_code == 304) {
1435 if (on_success) {
1436 SyncPullResponse empty_response;
1437 on_success(empty_response);
1438 }
1439 return BAMBU_NETWORK_SUCCESS;
1440 }
1441
1442 try {
1443 auto json = nlohmann::json::parse(response);
1444 SyncPullResponse resp;
1445 resp.next_cursor = json.value("next_cursor", 0);
1446
1447 if (json.contains("upserts") && json["upserts"].is_array()) {
1448 for (const auto& item : json["upserts"]) {
1449 ProfileUpsert upsert;
1450 upsert.id = item.value("id", "");
1451 upsert.name = item.value("name", "");
1452 upsert.updated_time = item.value(ORCA_JSON_KEY_UPDATE_TIME, 0);
1453 upsert.created_time = item.value(ORCA_JSON_KEY_CREATED_TIME, 0);
1454 if (item.contains("content")) {
1455 upsert.content = item["content"];
1456 }
1457 resp.upserts.push_back(upsert);
1458 }
1459 }
1460
1461 if (json.contains("deletes") && json["deletes"].is_array()) {
1462 for (const auto& item : json["deletes"]) {
1463 resp.deletes.push_back(item.get<std::string>());
1464 }
1465 }
1466
1467 if (on_success)
1468 on_success(resp);
1469 return BAMBU_NETWORK_SUCCESS;
1470
1471 } catch (const std::exception& e) {
1472 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: sync_pull parse error - " << e.what();
1473 if (on_error)
1474 on_error(http_code, e.what());
1476 }
1477}
#define ORCA_JSON_KEY_CREATED_TIME
Definition Preset.hpp:74
void clear_sync_state()
Definition OrcaCloudServiceAgent.cpp:1608
constexpr const char * ORCA_SYNC_PULL_PATH
Definition OrcaCloudServiceAgent.cpp:94

References BAMBU_NETWORK_ERR_GET_SETTING_LIST_FAILED, BAMBU_NETWORK_ERR_INVALID_RESULT, BAMBU_NETWORK_SUCCESS, clear_sync_state(), Slic3r::ProfileUpsert::content, Slic3r::ProfileUpsert::created_time, Slic3r::SyncPullResponse::deletes, http_get(), Slic3r::ProfileUpsert::id, Slic3r::ProfileUpsert::name, Slic3r::SyncPullResponse::next_cursor, ORCA_JSON_KEY_CREATED_TIME, ORCA_JSON_KEY_UPDATE_TIME, sync_state, Slic3r::ProfileUpsert::updated_time, and Slic3r::SyncPullResponse::upserts.

Referenced by get_setting_list2(), and get_user_presets().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ sync_push()

SyncPushResult Slic3r::OrcaCloudServiceAgent::sync_push ( const std::string & profile_id,
const std::string & name,
const nlohmann::json & content,
const std::string & original_updated_time = "",
bool force = false )
1484{
1485 SyncPushResult result;
1486 result.success = false;
1487 result.http_code = 0;
1488 result.conflict_code = 0;
1489 result.server_deleted = false;
1490
1491 nlohmann::json body;
1492 body["id"] = profile_id;
1493 body["name"] = name;
1494 body["content"] = content;
1495 if (!original_updated_time.empty()) {
1496 body["original_updated_time"] = original_updated_time;
1497 }
1498
1499 // Validate payload size before upload
1500 std::string body_str = body.dump();
1501 if (body_str.size() > ORCA_SYNC_MAX_PAYLOAD_SIZE) {
1502 result.http_code = 413; // HTTP 413 Payload Too Large
1503 result.success = false;
1504 result.error_message = "Preset content exceeds 1MB size limit (actual: " + std::to_string(body_str.size()) + " bytes)";
1505 BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: sync_push payload too large - " << "size=" << body_str.size() << " bytes, "
1506 << "limit=" << ORCA_SYNC_MAX_PAYLOAD_SIZE << " bytes, " << "profile_id=" << profile_id;
1507 return result;
1508 }
1509
1510 std::string response;
1511 unsigned int http_code = 0;
1512 int http_result = http_post(force ? ORCA_SYNC_FORCE_PUSH_PATH : ORCA_SYNC_PUSH_PATH, body_str, &response, &http_code);
1513
1514 result.http_code = http_code;
1515
1516 if (http_code == 409) {
1517 // Conflict - parse server version
1518 nlohmann::json err_body;
1519 try {
1520 auto json = nlohmann::json::parse(response);
1521 err_body = json;
1522 if (json.is_null()) {
1523 result.server_deleted = true;
1524 } else if (json.is_object()) {
1525 result.conflict_code = json.value("code", 0);
1526 if (json.contains("server_profile") && !json["server_profile"].is_null()) {
1527 auto& profile_data = json["server_profile"];
1528 result.server_version.id = profile_data.value("id", "");
1529 result.server_version.name = profile_data.value("name", "");
1530 result.server_version.updated_time = profile_data.value(ORCA_JSON_KEY_UPDATE_TIME, 0);
1531 }
1532 }
1533 } catch (...) {}
1534 // Create-path duplicate_profile_uuid (-2) is an idempotent success: the deterministic id
1535 // already exists, so the caller adopts the returned id. Skip the conflict notification,
1536 // otherwise every already-imported preset would raise a Pull/Force-push prompt on each launch.
1537 const bool is_create = original_updated_time.empty();
1538 const bool auto_resolved_duplicate = (is_create && result.conflict_code == -2);
1539 if (!auto_resolved_duplicate) {
1540 // Surface the conflict via the http-error callback with the local preset name injected.
1541 // The raw server body omits the name for tombstone (-3) conflicts (server_profile is null),
1542 // but the GUI needs it to regenerate the deterministic setting_id for a force push.
1543 if (!err_body.is_object())
1544 err_body = nlohmann::json::object();
1545 err_body["name"] = name;
1546 invoke_http_error_callback(409, err_body.dump());
1547 }
1548 result.error_message = response;
1549 return result;
1550 }
1551
1552 if (http_code != 200) {
1553 result.error_message = response;
1554 return result;
1555 }
1556
1557 // Success
1558 try {
1559 auto json = nlohmann::json::parse(response);
1560 result.new_updated_time = json.value(ORCA_JSON_KEY_UPDATE_TIME, 0);
1561 if (result.new_updated_time != 0) {
1562 result.success = true;
1563 } else {
1564 result.error_message = "Server response missing required updated_time timestamp";
1565 }
1566 } catch (const std::exception& e) {
1567 result.error_message = e.what();
1568 }
1569
1570 return result;
1571}
constexpr size_t ORCA_SYNC_MAX_PAYLOAD_SIZE
Definition OrcaCloudServiceAgent.cpp:102

References Slic3r::SyncPushResult::conflict_code, Slic3r::SyncPushResult::error_message, Slic3r::SyncPushResult::http_code, http_post(), Slic3r::ProfileUpsert::id, invoke_http_error_callback(), Slic3r::ProfileUpsert::name, Slic3r::SyncPushResult::new_updated_time, ORCA_JSON_KEY_UPDATE_TIME, Slic3r::SyncPushResult::server_deleted, Slic3r::SyncPushResult::server_version, Slic3r::SyncPushResult::success, and Slic3r::ProfileUpsert::updated_time.

Referenced by put_setting(), and request_setting_id().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ token_lock_path()

std::string Slic3r::OrcaCloudServiceAgent::token_lock_path ( ) const
private
3894{
3895 if (config_dir.empty())
3896 return {};
3897 wxFileName lock(wxString::FromUTF8(config_dir.c_str()), "orca_refresh_token.lock");
3898 lock.MakeAbsolute();
3899 return lock.GetFullPath().ToStdString();
3900}

References config_dir.

Referenced by refresh_from_storage().

Here is the caller graph for this function:

◆ track_enable()

int Slic3r::OrcaCloudServiceAgent::track_enable ( bool enable)
overridevirtual

Enable/disable telemetry.

Implements Slic3r::ICloudServiceAgent.

2946{
2947 std::lock_guard<std::recursive_mutex> lock(state_mutex);
2948 enable_track = enable;
2949 return BAMBU_NETWORK_SUCCESS;
2950}

References BAMBU_NETWORK_SUCCESS, enable_track, and state_mutex.

◆ track_event()

int Slic3r::OrcaCloudServiceAgent::track_event ( std::string evt_key,
std::string content )
overridevirtual

Report a custom analytics event.

Implements Slic3r::ICloudServiceAgent.

2959{
2960 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_event (stub) - " << evt_key;
2961 return BAMBU_NETWORK_SUCCESS;
2962}

References BAMBU_NETWORK_SUCCESS.

◆ track_get_property()

int Slic3r::OrcaCloudServiceAgent::track_get_property ( std::string name,
std::string & value,
std::string type = "string" )
overridevirtual

Read a tracked user property.

Implements Slic3r::ICloudServiceAgent.

2977{
2978 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_get_property (stub) - " << name;
2979 value = "";
2980 return BAMBU_NETWORK_SUCCESS;
2981}

References BAMBU_NETWORK_SUCCESS.

◆ track_header()

int Slic3r::OrcaCloudServiceAgent::track_header ( std::string header)
overridevirtual

Set telemetry headers.

Implements Slic3r::ICloudServiceAgent.

2965{
2966 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_header (stub)";
2967 return BAMBU_NETWORK_SUCCESS;
2968}

References BAMBU_NETWORK_SUCCESS.

◆ track_remove_files()

int Slic3r::OrcaCloudServiceAgent::track_remove_files ( )
overridevirtual

Delete telemetry files.

Implements Slic3r::ICloudServiceAgent.

2953{
2954 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_remove_files (stub)";
2955 return BAMBU_NETWORK_SUCCESS;
2956}

References BAMBU_NETWORK_SUCCESS.

◆ track_update_property()

int Slic3r::OrcaCloudServiceAgent::track_update_property ( std::string name,
std::string value,
std::string type = "string" )
overridevirtual

Update a tracked user property.

Implements Slic3r::ICloudServiceAgent.

2971{
2972 BOOST_LOG_TRIVIAL(debug) << "OrcaCloudServiceAgent: track_update_property (stub) - " << name;
2973 return BAMBU_NETWORK_SUCCESS;
2974}

References BAMBU_NETWORK_SUCCESS.

◆ unsubscribe_bundle()

bool Slic3r::OrcaCloudServiceAgent::unsubscribe_bundle ( const std::string & bundle_id)
3200{
3201 std::string path = std::string(ORCA_SUBSCRIPTIONS_PATH) + "/" + bundle_id;
3202 std::string response;
3203 unsigned int http_code = 0;
3204
3205 int result = http_delete(path, &response, &http_code);
3206 if (http_code >= 400) {
3207 return false;
3208 }
3209
3210 return true;
3211}

References http_delete().

Here is the call graph for this function:

◆ unsubscribe_plugins()

int Slic3r::OrcaCloudServiceAgent::unsubscribe_plugins ( const std::vector< std::string > & plugin_uuids)
3772{
3773 if (plugin_uuids.empty())
3774 return 0;
3775
3776 std::string response_body;
3777 unsigned int http_code = 0;
3778
3779 std::string query;
3780 for (const std::string& plugin_uuid : plugin_uuids) {
3781 if (plugin_uuid.empty()) {
3782 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: empty plugin UUID";
3783 return -1;
3784 }
3785
3786 if (!query.empty())
3787 query += ",";
3788 query += Http::url_encode(plugin_uuid);
3789 }
3790
3791 const std::string path = std::string(ORCA_UNSUBSCRIBE_PLUGINS) + "?ids=" + query;
3792 int result = http_delete(path, &response_body, &http_code);
3793
3794 if (result != 0 || (http_code != 200 && http_code != 204)) {
3795 BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " failed: http_code=" << http_code << ", response=" << response_body;
3796 return http_code != 0 ? static_cast<int>(http_code) : result;
3797 }
3798
3799 if (!response_body.empty()) {
3800 try {
3801 auto root = json::parse(response_body);
3802 if (root.contains("not_found") && root["not_found"].is_array() && !root["not_found"].empty()) {
3803 std::string missing;
3804 for (const auto& item : root["not_found"]) {
3805 if (!item.is_string())
3806 continue;
3807 if (!missing.empty())
3808 missing += ",";
3809 missing += item.get<std::string>();
3810 }
3811 if (!missing.empty())
3812 BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": plugin UUIDs not found: " << missing;
3813 }
3814 } catch (const std::exception& e) {
3815 BOOST_LOG_TRIVIAL(warning) << __FUNCTION__ << ": response parse error: " << e.what();
3816 }
3817 }
3818
3819 return 0;
3820}

References http_delete(), and Slic3r::Http::url_encode().

Here is the call graph for this function:

◆ update_redirect_uri()

void Slic3r::OrcaCloudServiceAgent::update_redirect_uri ( )
private
1645{
1646 int selected_port = choose_loopback_port();
1647 pkce_bundle.loopback_port = selected_port;
1648 pkce_bundle.redirect = "http://localhost:" + std::to_string(selected_port) + auth_constants::LOOPBACK_PATH;
1649}

References Slic3r::auth_constants::LOOPBACK_PATH, and pkce_bundle.

Referenced by OrcaCloudServiceAgent(), and build_login_cmd().

Here is the caller graph for this function:

◆ user_logout()

int Slic3r::OrcaCloudServiceAgent::user_logout ( bool request = false)
overridevirtual

Terminate the current session.

Parameters
requestIf true, also notify the backend to invalidate the session.

Implements Slic3r::ICloudServiceAgent.

821{
822 // Send logout request to backend if requested
823 if (request) {
824 std::string token;
825 std::string refresh_copy;
826 {
827 std::lock_guard<std::mutex> lock(session_mutex);
828 token = session.access_token;
829 refresh_copy = session.refresh_token;
830 }
831
832 if (!token.empty()) {
833 std::string response;
834 unsigned int http_code = 0;
835 json logout_req = json::object();
836 if (!refresh_copy.empty()) {
837 logout_req["refresh_token"] = refresh_copy;
838 }
839
840 int result = http_post_auth(auth_constants::LOGOUT_PATH, logout_req.dump(), &response, &http_code) ?
843 if (result != BAMBU_NETWORK_SUCCESS || http_code >= 400) {
844 BOOST_LOG_TRIVIAL(warning) << "OrcaCloudServiceAgent: Orca cloud logout request failed - http_code=" << http_code;
845 }
846 }
847 }
848
849 // An explicit logout also wipes the backend the token storage option is not using, so a token
850 // stranded by switching that option cannot sign the account back in later.
851 clear_session(/*all_backends=*/request);
853}
bool http_post_auth(const std::string &path, const std::string &body, std::string *response_body, unsigned int *http_code)
Definition OrcaCloudServiceAgent.cpp:2537
void clear_session(bool all_backends=false)
Definition OrcaCloudServiceAgent.cpp:2154
constexpr const char * LOGOUT_PATH
Definition OrcaCloudServiceAgent.hpp:60

References BAMBU_NETWORK_ERR_INVALID_HANDLE, BAMBU_NETWORK_SUCCESS, clear_session(), http_post_auth(), Slic3r::auth_constants::LOGOUT_PATH, session, and session_mutex.

Here is the call graph for this function:

Member Data Documentation

◆ agent_start_epoch

const long long Slic3r::OrcaCloudServiceAgent::agent_start_epoch
private
Initial value:
{std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch()).count()}
464 {std::chrono::duration_cast<std::chrono::seconds>(
465 std::chrono::system_clock::now().time_since_epoch()).count()};

Referenced by refresh_session_with_token().

◆ api_base_url

◆ auth_base_url

◆ auth_headers

std::map<std::string, std::string> Slic3r::OrcaCloudServiceAgent::auth_headers
private

◆ callback_mutex

std::mutex Slic3r::OrcaCloudServiceAgent::callback_mutex
mutableprivate

◆ cloud_base_url

◆ config_dir

std::string Slic3r::OrcaCloudServiceAgent::config_dir
private

◆ country_code

std::string Slic3r::OrcaCloudServiceAgent::country_code
private

Referenced by get_oss_config(), and set_country_code().

◆ enable_track

bool Slic3r::OrcaCloudServiceAgent::enable_track {false}
private
469{false};

Referenced by get_track_enable(), and track_enable().

◆ extra_headers

std::map<std::string, std::string> Slic3r::OrcaCloudServiceAgent::extra_headers
private

◆ get_country_code_fn

GetCountryCodeFn Slic3r::OrcaCloudServiceAgent::get_country_code_fn
private

Referenced by set_get_country_code_fn().

◆ headers_mutex

std::mutex Slic3r::OrcaCloudServiceAgent::headers_mutex
mutableprivate

◆ health_check_running

std::atomic_bool Slic3r::OrcaCloudServiceAgent::health_check_running {false}
private
488{false};

Referenced by refresh_connection().

◆ health_check_stopped

std::atomic_bool Slic3r::OrcaCloudServiceAgent::health_check_stopped {false}
private
489{false};

Referenced by refresh_connection(), and stop_health_check().

◆ health_check_thread

std::thread Slic3r::OrcaCloudServiceAgent::health_check_thread
private

◆ is_connected

bool Slic3r::OrcaCloudServiceAgent::is_connected {false}
private
468{false};

Referenced by is_server_connected(), and run_health_check().

◆ last_refresh_success_epoch

std::atomic<long long> Slic3r::OrcaCloudServiceAgent::last_refresh_success_epoch {0}
private
463{0}; // 0 = no success yet this process

Referenced by refresh_session_with_token().

◆ log_dir

std::string Slic3r::OrcaCloudServiceAgent::log_dir
private

Referenced by OrcaCloudServiceAgent().

◆ m_use_encrypted_token_file

bool Slic3r::OrcaCloudServiceAgent::m_use_encrypted_token_file {false}
private

◆ multi_machine_enabled

bool Slic3r::OrcaCloudServiceAgent::multi_machine_enabled {false}
private
470{false};

Referenced by enable_multi_machine().

◆ on_http_error_fn

AppOnHttpErrorFn Slic3r::OrcaCloudServiceAgent::on_http_error_fn
private

◆ on_login_complete_handler

OnLoginCompleteHandler Slic3r::OrcaCloudServiceAgent::on_login_complete_handler
private

◆ on_server_connected_fn

AppOnServerConnectedFn Slic3r::OrcaCloudServiceAgent::on_server_connected_fn
private

◆ pkce_bundle

PkceBundle Slic3r::OrcaCloudServiceAgent::pkce_bundle
private

◆ queue_on_main_fn

QueueOnMainFn Slic3r::OrcaCloudServiceAgent::queue_on_main_fn
private

◆ refresh_running

std::atomic_bool Slic3r::OrcaCloudServiceAgent::refresh_running {false}
private
486{false};

Referenced by is_refresh_running(), refresh_if_expiring(), and refresh_now().

◆ refresh_thread

std::thread Slic3r::OrcaCloudServiceAgent::refresh_thread
private

◆ secret_fallback_path

std::string Slic3r::OrcaCloudServiceAgent::secret_fallback_path
private

◆ secret_stored

std::atomic_bool Slic3r::OrcaCloudServiceAgent::secret_stored {false}
private

◆ session

◆ session_handler

SessionHandler Slic3r::OrcaCloudServiceAgent::session_handler
private

◆ session_mutex

◆ state_mutex

◆ sync_state

SyncState Slic3r::OrcaCloudServiceAgent::sync_state
private

◆ sync_state_path

std::string Slic3r::OrcaCloudServiceAgent::sync_state_path
private

The documentation for this class was generated from the following files: