From 5e30b3ef0607611474b0d56f73fdb6ddcd19827c Mon Sep 17 00:00:00 2001 From: Ian Chua Date: Sun, 31 May 2026 16:23:10 +0800 Subject: [PATCH] fix: 409 conflicts resolution in notifications (#13900) * fix: 409 conflicts resolution in notifications * fix: silently log other http errors * fix: pass force push flag to start_sync_user_preset * remove formatting churn * fix: propagate force push down put_setting * refactor render_hyperlink_action to PopNotification for reuse * fix an issue that hold status should be cleared before force pushing. --------- Co-authored-by: SoftFever --- src/slic3r/GUI/GUI_App.cpp | 141 ++++++++++++++++----- src/slic3r/GUI/GUI_App.hpp | 7 +- src/slic3r/GUI/NotificationManager.cpp | 117 ++++++++++++----- src/slic3r/GUI/NotificationManager.hpp | 34 +++++ src/slic3r/Utils/BBLCloudServiceAgent.cpp | 2 +- src/slic3r/Utils/BBLCloudServiceAgent.hpp | 2 +- src/slic3r/Utils/ICloudServiceAgent.hpp | 2 +- src/slic3r/Utils/NetworkAgent.cpp | 5 +- src/slic3r/Utils/NetworkAgent.hpp | 2 +- src/slic3r/Utils/OrcaCloudServiceAgent.cpp | 19 +-- src/slic3r/Utils/OrcaCloudServiceAgent.hpp | 14 +- 11 files changed, 259 insertions(+), 86 deletions(-) diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 8a1d317b97..90dc54ec0f 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include // Localization headers: include libslic3r version first so everything in this file @@ -4812,6 +4813,8 @@ void GUI_App::handle_http_error(unsigned int status, std::string body, const std wxQueueEvent(this, evt); } +static std::mutex conflict_ids_mutex; + void GUI_App::on_http_error(wxCommandEvent &evt) { int status = evt.GetInt(); @@ -4887,32 +4890,62 @@ void GUI_App::on_http_error(wxCommandEvent &evt) return; } - static bool m_is_error_shown = false; + if (status == 409 && provider == ORCA_CLOUD_PROVIDER) { + BOOST_LOG_TRIVIAL(info) << "Http error 409."; + // Parse the conflict body to extract the error code and server profile id + int conflict_code = 0; + std::string conflict_setting_id; + try { + json conflict_body = json::parse(body_str); + if (conflict_body.contains("code")) + conflict_code = conflict_body["code"].get(); + if (conflict_body.contains("server_profile") && conflict_body["server_profile"].contains("id") + && conflict_body["server_profile"]["id"].is_string()) + conflict_setting_id = conflict_body["server_profile"]["id"].get(); + } catch (...) { + BOOST_LOG_TRIVIAL(warning) << "Failed to parse 409 conflict body."; + } + auto* plater = wxGetApp().plater(); + if (plater != nullptr && wxGetApp().imgui()->display_initialized()) { + std::string text; + if (conflict_code == -1) { + text = _u8L("Cloud sync conflict: this preset has a newer version in OrcaCloud.\n" + "Pull downloads the cloud copy. Force push overwrites it with your local preset."); + } else { + text = _u8L("Cloud sync conflict: a preset with this name already exists in OrcaCloud.\n" + "Pull downloads the cloud copy. Force push overwrites it with your local preset."); + } + plater->get_notification_manager()->push_orca_sync_conflict_notification( + text, + [this](wxEvtHandler*) { + // Runs on the GUI thread (on_http_error is a queued wx event); restart_sync_user_preset() + // already joins the old sync thread off the UI thread, so no extra thread is needed here. + if (is_closing() || !m_agent || !preset_bundle) + return false; + BOOST_LOG_TRIVIAL(info) << "Pulling Orca Cloud settings to resolve sync conflict."; + restart_sync_user_preset(); + return true; + }, + [this, conflict_setting_id](wxEvtHandler*) { + if (mainframe == nullptr) + return false; + MessageDialog + dlg(mainframe, + _L("Force push will overwrite the cloud copy with your local preset changes.\nDo you want to continue?"), + _L("Resolve cloud sync conflict"), wxCENTER | wxYES_NO | wxNO_DEFAULT | wxICON_WARNING); + if (dlg.ShowModal() != wxID_YES) + return false; + + force_push_conflicting_preset(conflict_setting_id); + return true; + }); + } + return; + } + // Show general error notification for Orca Cloud API failures (not Bambu) if (provider == ORCA_CLOUD_PROVIDER && status >= 400 && code != HttpErrorVersionLimited) { - wxString msg; - if (!error.empty()) { - msg = wxString::Format(_L("Failed to connect to OrcaCloud.\nPlease check your network connectivity\n(HTTP %u): %s"), status, wxString::FromUTF8(error)); - } else { - msg = wxString::Format(_L("Failed to connect to OrcaCloud.\nPlease check your network connectivity\n(HTTP %u)"), status); - } - - if (app_config->get_bool("developer_mode")) { - // Use notification manager if ImGui is ready; fall back to wxMessageBox on Linux - // where ImGui may not be initialized until the user switches to the Prepare tab. - if (wxGetApp().plater() != nullptr && wxGetApp().imgui()->display_initialized()) { - wxGetApp() - .plater() - ->get_notification_manager() - ->push_notification(NotificationType::PlaterError, NotificationManager::NotificationLevel::WarningNotificationLevel, - msg.ToUTF8().data()); - } - } - - if (!m_is_error_shown) { - m_is_error_shown = true; - wxMessageBox(msg, _L("Cloud Error"), wxOK | wxICON_ERROR, wxGetApp().mainframe); - } + BOOST_LOG_TRIVIAL(warning) << "API call to OrcaCloud failed with status=" << status; } } @@ -6181,13 +6214,14 @@ void GUI_App::load_pending_vendors() need_add_filaments.clear(); } -void GUI_App::sync_preset(Preset* preset) +void GUI_App::sync_preset(Preset* preset, bool force) { int result = -1; unsigned int http_code = 200; std::string updated_info; long long update_time = 0; // only sync user's preset + if (!m_agent) return; if (!preset->is_user()) return; auto setting_id = preset->setting_id; @@ -6259,9 +6293,9 @@ void GUI_App::sync_preset(Preset* preset) result = 0; } else { - result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code); + result = m_agent->put_setting(setting_id, preset->name, &values_map, &http_code, ORCA_CLOUD_PROVIDER, force); if (http_code >= 400) { - result = 0; + result = 0; updated_info = "hold"; BOOST_LOG_TRIVIAL(error) << "[sync_preset] put setting_id = " << setting_id << " failed, http_code = " << http_code; } else { @@ -6722,7 +6756,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) // Sync once immediately, then every 60 seconds. while (!t.expired()) { ++tick_tock; - if (tick_tock % 120 == 0) { + // Sync once immediately, then every 60s, or right away when a force-push asked for it. + if (tick_tock % 120 == 0 || m_sync_user_presets_now.exchange(false, std::memory_order_acq_rel)) { tick_tock = 0; if (m_agent) { if (!m_agent->is_user_login()) { @@ -6733,9 +6768,24 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) int total_count = 0; sync_count = preset_bundle->prints.get_user_presets(preset_bundle, presets_to_sync); + + auto sync_with_lock = [this](Preset& preset) { + bool force = false; + { + std::scoped_lock lock(conflict_ids_mutex); + auto it = std::find_if(m_pending_conflict_setting_ids.begin(), m_pending_conflict_setting_ids.end(), + [&preset](const std::string& id) { return id == preset.setting_id; }); + if (it != m_pending_conflict_setting_ids.end()) { + force = true; + m_pending_conflict_setting_ids.erase(it); + } + } + sync_preset(&preset, force); + }; + if (sync_count > 0) { for (Preset& preset : presets_to_sync) { - sync_preset(&preset); + sync_with_lock(preset); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } } @@ -6744,7 +6794,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) sync_count = preset_bundle->filaments.get_user_presets(preset_bundle, presets_to_sync); if (sync_count > 0) { for (Preset& preset : presets_to_sync) { - sync_preset(&preset); + sync_with_lock(preset); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } } @@ -6753,7 +6803,7 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) sync_count = preset_bundle->printers.get_user_presets(preset_bundle, presets_to_sync); if (sync_count > 0) { for (Preset& preset : presets_to_sync) { - sync_preset(&preset); + sync_with_lock(preset); boost::this_thread::sleep_for(boost::chrono::milliseconds(100)); } } @@ -6930,6 +6980,35 @@ void GUI_App::restart_sync_user_preset() }).detach(); } +void GUI_App::force_push_conflicting_preset(const std::string& setting_id) +{ + if (setting_id.empty() || !preset_bundle) + return; + + // Queue the id so the next push-sync re-uploads this preset with force=true. + { + std::scoped_lock lock(conflict_ids_mutex); + m_pending_conflict_setting_ids.push_back(setting_id); + } + + // The 409 left this preset on "hold", which get_user_presets() skips. Restore it to + // "update" so the next push-sync re-includes it and consumes the queued force flag. + // (We must NOT pull from the cloud here as the Pull path does — that would overwrite + // the local changes the user is trying to force-push.) + PresetCollection* collections[] = {&preset_bundle->prints, &preset_bundle->filaments, &preset_bundle->printers}; + for (PresetCollection* coll : collections) { + for (const Preset& preset : coll->get_presets()) { + if (preset.setting_id == setting_id && preset.sync_info == "hold") { + coll->set_sync_info_and_save(preset.name, preset.setting_id, "update", 0); + break; + } + } + } + + // Nudge the sync loop to push on its next tick instead of waiting for the 60s cadence. + m_sync_user_presets_now.store(true, std::memory_order_release); +} + void GUI_App::on_stealth_mode_enter() { stop_sync_user_preset(); diff --git a/src/slic3r/GUI/GUI_App.hpp b/src/slic3r/GUI/GUI_App.hpp index 5b6bcc6581..dc0f1e52de 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -297,6 +297,7 @@ private: NetworkAgent* m_agent { nullptr }; std::map need_delete_presets; // store setting ids of preset std::vector m_create_preset_blocked { false, false, false, false, false, false }; // excceed limit + std::vector m_pending_conflict_setting_ids; // setting_id from the most recent 409 conflict bool m_networking_compatible { false }; bool m_networking_need_update { false }; bool m_networking_cancel_update { false }; @@ -322,6 +323,7 @@ private: boost::thread m_sync_update_thread; std::shared_ptr m_user_sync_token; std::atomic m_restart_sync_pending {false}; + std::atomic m_sync_user_presets_now {false}; // request the sync loop to push user presets on its next tick bool m_is_dark_mode{ false }; bool m_adding_script_handler { false }; bool m_side_popup_status{false}; @@ -529,10 +531,13 @@ public: void add_pending_vendor_preset(const std::pair>& preset_data); void load_pending_vendors(); - void sync_preset(Preset* preset); + void sync_preset(Preset* preset, bool force = false); void start_sync_user_preset(bool with_progress_dlg = false); void stop_sync_user_preset(); void restart_sync_user_preset(); + // Resolve a cloud sync 409 by force-pushing the conflicting preset: clears the "hold" + // state the conflict left behind and queues it to be re-uploaded with force=true. + void force_push_conflicting_preset(const std::string& setting_id); void on_stealth_mode_enter(); // Bundle subscription sync diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp index 81e28fec1a..5079dfecf0 100644 --- a/src/slic3r/GUI/NotificationManager.cpp +++ b/src/slic3r/GUI/NotificationManager.cpp @@ -791,6 +791,42 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui, } +void NotificationManager::PopNotification::render_hyperlink_action(ImGuiWrapper& imgui, float text_x, float text_y, + const std::string& text, const char* button_id, const std::function& on_click) +{ + // Invisible button over the label + ImVec2 part_size = ImGui::CalcTextSize(text.c_str()); + ImGui::SetCursorPosX(text_x - 4); + ImGui::SetCursorPosY(text_y - 5); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); + ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); + if (imgui.button(button_id, part_size.x + 6, part_size.y + 10) && on_click) + on_click(); + ImGui::PopStyleColor(3); + + // Hover color + ImVec4 color = m_HyperTextColor; + if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) + color = m_HyperTextColorHover; + + // Text + push_style_color(ImGuiCol_Text, color, m_state == EState::FadingOut, m_current_fade_opacity); + ImGui::SetCursorPosX(text_x); + ImGui::SetCursorPosY(text_y); + imgui.text(text.c_str()); + ImGui::PopStyleColor(); + + // Underline + ImVec2 lineEnd = ImGui::GetItemRectMax(); + lineEnd.y -= 2; + ImVec2 lineStart = lineEnd; + lineStart.x = ImGui::GetItemRectMin().x; + ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, + IM_COL32((int)(color.x * 255), (int)(color.y * 255), (int)(color.z * 255), + (int)(color.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f)))); +} + void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y) { ensure_ui_inited(); @@ -2346,40 +2382,49 @@ bool NotificationManager::SharedProfilesNotification::on_text_click() void NotificationManager::SharedProfilesNotification::render_hypertext(ImGuiWrapper& imgui, const float text_x, const float text_y, const std::string text, bool more) { - // Invisible button - ImVec2 part_size = ImGui::CalcTextSize(text.c_str()); - ImGui::SetCursorPosX(text_x - 4); - ImGui::SetCursorPosY(text_y - 5); - ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f)); - ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f)); - if (imgui.button("##browse_btn", part_size.x + 6, part_size.y + 10)) { - if (on_text_click()) { - close(); + render_hyperlink_action(imgui, text_x, text_y, text, "##browse_btn", + [this] { if (on_text_click()) close(); }); +} + +void NotificationManager::OrcaSyncConflictNotification::init() +{ + PopNotification::init(); + // Reserve a dedicated action row for the two conflict-resolution links. + m_lines_count = m_lines_count + 1; +} + +void NotificationManager::OrcaSyncConflictNotification::render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) +{ + float x_offset = m_left_indentation; + float shift_y = m_line_height; + float starting_y = m_line_height / 2; + + int last_end = 0; + std::string line; + for (size_t i = 0; i < m_endlines.size(); i++) { + if (m_text1.size() >= m_endlines[i]) { + line = m_text1.substr(last_end, m_endlines[i] - last_end); + last_end = m_endlines[i]; + if (m_text1.size() > m_endlines[i]) + last_end += (m_text1[m_endlines[i]] == '\n' || m_text1[m_endlines[i]] == ' ' ? 1 : 0); + ImGui::SetCursorPosX(x_offset); + ImGui::SetCursorPosY(starting_y + i * shift_y); + imgui.text(line.c_str()); } } - ImGui::PopStyleColor(3); - // Hover color - ImVec4 HyperColor = m_HyperTextColor; - if (ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)) - HyperColor = m_HyperTextColorHover; - - // Text - push_style_color(ImGuiCol_Text, HyperColor, m_state == EState::FadingOut, m_current_fade_opacity); - ImGui::SetCursorPosX(text_x); - ImGui::SetCursorPosY(text_y); - imgui.text(text.c_str()); - ImGui::PopStyleColor(); - - // Underline - ImVec2 lineEnd = ImGui::GetItemRectMax(); - lineEnd.y -= 2; - ImVec2 lineStart = lineEnd; - lineStart.x = ImGui::GetItemRectMin().x; - ImGui::GetWindowDrawList()->AddLine(lineStart, lineEnd, - IM_COL32((int)(HyperColor.x * 255), (int)(HyperColor.y * 255), (int)(HyperColor.z * 255), - (int)(HyperColor.w * 255.f * (m_state == EState::FadingOut ? m_current_fade_opacity : 1.f)))); + const float action_y = starting_y + m_endlines.size() * shift_y; + const std::string pull_text = _u8L("Pull"); + render_hyperlink_action(imgui, x_offset, action_y, pull_text, "##orca_sync_pull", + [this] { if (m_pull_callback && m_pull_callback(m_evt_handler)) close(); }); + if (m_force_push_callback) { + const std::string force_push_text = _u8L("Force push"); + const float force_x = x_offset + ImGui::CalcTextSize((pull_text + " ").c_str()).x; + render_hyperlink_action(imgui, force_x, action_y, force_push_text, "##orca_sync_force_push", + [this] { if (m_force_push_callback && m_force_push_callback(m_evt_handler)) close(); }); + } } void NotificationManager::push_shared_profiles_notification(const std::string& explore_url) @@ -2391,6 +2436,16 @@ void NotificationManager::push_shared_profiles_notification(const std::string& e push_notification_data(std::make_unique(data, m_id_provider, m_evt_handler, explore_url), 0); } +void NotificationManager::push_orca_sync_conflict_notification(const std::string& text, + std::function pull_callback, + std::function force_push_callback) +{ + close_notification_of_type(NotificationType::OrcaSyncConflict); + NotificationData data{ NotificationType::OrcaSyncConflict, NotificationLevel::WarningNotificationLevel, 0, text }; + push_notification_data(std::make_unique( + data, m_id_provider, m_evt_handler, std::move(pull_callback), std::move(force_push_callback)), 0); +} + void NotificationManager::push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback) { // If already exists diff --git a/src/slic3r/GUI/NotificationManager.hpp b/src/slic3r/GUI/NotificationManager.hpp index 5e8f4f7e95..006de5cff6 100644 --- a/src/slic3r/GUI/NotificationManager.hpp +++ b/src/slic3r/GUI/NotificationManager.hpp @@ -15,6 +15,8 @@ #include #include +#include +#include #include #include #include @@ -162,6 +164,8 @@ enum class NotificationType BBLMixUsePLAAndPETG, BBLNozzleFilamentIncompatible, OrcaSharedProfilesAvailable, + OrcaCloudAPIError, + OrcaSyncConflict, NotificationTypeCount }; @@ -274,6 +278,9 @@ public: // Shared profiles available for selected printer void push_shared_profiles_notification(const std::string& explore_url); + void push_orca_sync_conflict_notification(const std::string& text, + std::function pull_callback, + std::function force_push_callback); // Download URL progress notif void push_download_URL_progress_notification(size_t id, const std::string& text, std::function user_action_callback); @@ -491,6 +498,11 @@ private: const float text_x, const float text_y, const std::string text, bool more = false); + // Renders an underlined, hyperlink-style clickable label backed by an invisible button. + // on_click runs when pressed; the callback itself decides whether to close(). + void render_hyperlink_action(ImGuiWrapper& imgui, float text_x, float text_y, + const std::string& text, const char* button_id, + const std::function& on_click); virtual void bbl_render_block_notif_text(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y); @@ -887,6 +899,28 @@ private: std::string m_explore_url; bool m_dont_show_clicked{ false }; }; + + class OrcaSyncConflictNotification : public PopNotification + { + public: + OrcaSyncConflictNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, + std::function pull_callback, + std::function force_push_callback) + : PopNotification(n, id_provider, evt_handler) + , m_pull_callback(std::move(pull_callback)) + , m_force_push_callback(std::move(force_push_callback)) + { + m_multiline = true; + } + protected: + void init() override; + void render_text(ImGuiWrapper& imgui, + const float win_size_x, const float win_size_y, + const float win_pos_x, const float win_pos_y) override; + + std::function m_pull_callback; + std::function m_force_push_callback; + }; class SlicingProgressNotification; // in HintNotification.hpp diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.cpp b/src/slic3r/Utils/BBLCloudServiceAgent.cpp index 2c17d117fc..846e4ce509 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.cpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.cpp @@ -433,7 +433,7 @@ std::string BBLCloudServiceAgent::request_setting_id(std::string name, std::map< return ""; } -int BBLCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code) +int BBLCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, bool force) { auto& plugin = BBLNetworkPlugin::instance(); auto agent = plugin.get_agent(); diff --git a/src/slic3r/Utils/BBLCloudServiceAgent.hpp b/src/slic3r/Utils/BBLCloudServiceAgent.hpp index b48a65b76c..9f03f0b839 100644 --- a/src/slic3r/Utils/BBLCloudServiceAgent.hpp +++ b/src/slic3r/Utils/BBLCloudServiceAgent.hpp @@ -70,7 +70,7 @@ public: // Settings Synchronization int get_user_presets(std::map>* user_presets) override; std::string request_setting_id(std::string name, std::map* values_map, unsigned int* http_code) override; - int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code) override; + int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, bool force = false) override; 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; diff --git a/src/slic3r/Utils/ICloudServiceAgent.hpp b/src/slic3r/Utils/ICloudServiceAgent.hpp index 6d131341f7..556c253641 100644 --- a/src/slic3r/Utils/ICloudServiceAgent.hpp +++ b/src/slic3r/Utils/ICloudServiceAgent.hpp @@ -247,7 +247,7 @@ public: /** * Update or create a preset with a known setting_id. */ - virtual int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code) = 0; + virtual int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, bool force = false) = 0; /** * Trigger bulk download of user presets. diff --git a/src/slic3r/Utils/NetworkAgent.cpp b/src/slic3r/Utils/NetworkAgent.cpp index e59795c058..8a3d2953b0 100644 --- a/src/slic3r/Utils/NetworkAgent.cpp +++ b/src/slic3r/Utils/NetworkAgent.cpp @@ -383,11 +383,12 @@ int NetworkAgent::put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, - const std::string& provider) + const std::string& provider, + bool force) { const auto cloud_agent = get_cloud_agent(provider); if (cloud_agent) - return cloud_agent->put_setting(std::move(setting_id), std::move(name), values_map, http_code); + return cloud_agent->put_setting(std::move(setting_id), std::move(name), values_map, http_code, force); return -1; } diff --git a/src/slic3r/Utils/NetworkAgent.hpp b/src/slic3r/Utils/NetworkAgent.hpp index 49a2e9f5d6..57a23737a2 100644 --- a/src/slic3r/Utils/NetworkAgent.hpp +++ b/src/slic3r/Utils/NetworkAgent.hpp @@ -93,7 +93,7 @@ public: // NOTE: this should always call only OrcaCloud int get_user_presets(std::map>* user_presets, const std::string& provider = ORCA_CLOUD_PROVIDER); std::string request_setting_id(std::string name, std::map* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER); - int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER); + int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, const std::string& provider = ORCA_CLOUD_PROVIDER, bool force = false); int get_setting_list(std::string bundle_version, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr, const std::string& provider = ORCA_CLOUD_PROVIDER); int get_setting_list2(std::string bundle_version, CheckFn chk_fn, ProgressFn pro_fn = nullptr, WasCancelledFn cancel_fn = nullptr, const std::string& provider = ORCA_CLOUD_PROVIDER); int delete_setting(std::string setting_id, const std::string& provider = ORCA_CLOUD_PROVIDER); diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp index 0e1027cd46..1b7f779914 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.cpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.cpp @@ -56,6 +56,7 @@ constexpr const char* ORCA_DEFAULT_PUB_KEY = "sb_publishable_lvVe_whOi80SU9BPSxM constexpr const char* ORCA_HEALTH_PATH = "/api/v1/health"; constexpr const char* ORCA_SYNC_PULL_PATH = "/api/v1/sync/pull"; constexpr const char* ORCA_SYNC_PUSH_PATH = "/api/v1/sync/push"; +constexpr const char* ORCA_SYNC_FORCE_PUSH_PATH = "/api/v1/sync/force-push"; constexpr const char* ORCA_SYNC_DELETE_PATH = "/api/v1/sync/delete"; constexpr const char* ORCA_PROFILES_PATH = "/api/v1/sync/profiles"; constexpr const char* ORCA_SUBSCRIPTIONS_PATH = "/api/v1/subscriptions"; @@ -965,7 +966,7 @@ std::string OrcaCloudServiceAgent::request_setting_id(std::string name, std::map return ""; } -int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code) +int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code, bool force) { // Extract original_updated_time for Optimistic Concurrency Control // If present, server will verify version before update. If absent, treated as insert. @@ -989,7 +990,7 @@ int OrcaCloudServiceAgent::put_setting(std::string setting_id, std::string name, } } - auto result = sync_push(setting_id, name, content, original_updated_time); + auto result = sync_push(setting_id, name, content, original_updated_time, force); if (http_code) *http_code = result.http_code; if (result.success) { @@ -1208,11 +1209,11 @@ int OrcaCloudServiceAgent::sync_pull( } } -SyncPushResult OrcaCloudServiceAgent::sync_push( - const std::string& profile_id, - const std::string& name, - const nlohmann::json& content, - const std::string& original_updated_time) +SyncPushResult OrcaCloudServiceAgent::sync_push(const std::string& profile_id, + const std::string& name, + const nlohmann::json& content, + const std::string& original_updated_time, + bool force) { SyncPushResult result; result.success = false; @@ -1243,7 +1244,7 @@ SyncPushResult OrcaCloudServiceAgent::sync_push( std::string response; unsigned int http_code = 0; - int http_result = http_post(ORCA_SYNC_PUSH_PATH, body_str, &response, &http_code); + int http_result = http_post(force ? ORCA_SYNC_FORCE_PUSH_PATH : ORCA_SYNC_PUSH_PATH, body_str, &response, &http_code); result.http_code = http_code; @@ -1888,7 +1889,7 @@ int OrcaCloudServiceAgent::http_post(const std::string& path, const std::string& .on_error([&](std::string resp_body, std::string error, unsigned resp_status) { result.success = false; result.status = resp_status == 0 ? 404 : resp_status; - result.body = body; + result.body = resp_body; BOOST_LOG_TRIVIAL(error) << "OrcaCloudServiceAgent: HTTP error - " << error; }) .timeout_max(30) diff --git a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp index 4552b487d4..5c58987a0b 100644 --- a/src/slic3r/Utils/OrcaCloudServiceAgent.hpp +++ b/src/slic3r/Utils/OrcaCloudServiceAgent.hpp @@ -176,7 +176,12 @@ public: // ======================================================================== int get_user_presets(std::map>* user_presets) override; std::string request_setting_id(std::string name, std::map* values_map, unsigned int* http_code) override; - int put_setting(std::string setting_id, std::string name, std::map* values_map, unsigned int* http_code) override; + int put_setting(std::string setting_id, std::string name, std::map* 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; @@ -294,13 +299,6 @@ private: std::function on_error ); - SyncPushResult sync_push( - const std::string& profile_id, - const std::string& name, - const nlohmann::json& content, - const std::string& original_updated_time = "" - ); - // HTTP request helpers 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);