diff --git a/src/libslic3r/Preset.cpp b/src/libslic3r/Preset.cpp index 68f103fdc2..2c894450dd 100644 --- a/src/libslic3r/Preset.cpp +++ b/src/libslic3r/Preset.cpp @@ -2259,6 +2259,10 @@ bool PresetCollection::load_user_preset(std::string name, std::mapname == m_edited_preset.name && iter->is_dirty) { // Keep modifies when update from remote new_config.apply_only(m_edited_preset.config, m_edited_preset.config.diff(iter->config)); + } else if (iter->name == m_edited_preset.name) { + // Preset is not dirty (no local unsaved changes) — also update the edited preset + // to prevent a false "dirty" indication (orange highlight) after a silent cloud sync + m_edited_preset.config = new_config; } iter->config = new_config; iter->updated_time = cloud_update_time; diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 0339805304..aa58a2e3a1 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -6577,8 +6577,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) dlg->Update(percent, _L("Loading user preset")); }); }; - cancelFn = [this, dlg]() { - return is_closing() || dlg->WasCanceled(); + cancelFn = [this, dlg, t = std::weak_ptr(m_user_sync_token)]() { + return is_closing() || dlg->WasCanceled() || t.expired(); }; finishFn = [this, dlg](bool) { CallAfter([=]{ dlg->Destroy(); }); @@ -6586,8 +6586,8 @@ void GUI_App::start_sync_user_preset(bool with_progress_dlg) } else { finishFn = [](bool) {}; // reload_settings() is now triggered from the background thread - cancelFn = [this]() { - return is_closing(); + cancelFn = [this, t = std::weak_ptr(m_user_sync_token)]() { + return is_closing() || t.expired(); }; } @@ -6819,6 +6819,37 @@ void GUI_App::stop_sync_user_preset() } } +void GUI_App::restart_sync_user_preset() +{ + if (!m_user_sync_token) { + // No sync running. If a restart helper is already in flight it will + // start the new sync once the old thread is joined — don't race it. + if (!m_restart_sync_pending) + start_sync_user_preset(true); + return; + } + + // Resetting the token signals the old thread to stop (cancelFn checks + // t.expired(), so it exits after its current HTTP request completes). + // A helper thread joins the old thread off the UI thread — no freeze — + // then starts the new sync via CallAfter once the old one is fully done. + m_user_sync_token.reset(); + m_restart_sync_pending = true; + + auto old_thread = std::move(m_sync_update_thread); + + std::thread([this, old_thread = std::move(old_thread)]() mutable { + if (old_thread.joinable()) + old_thread.join(); + m_restart_sync_pending = false; + if (!is_closing()) + CallAfter([this]() { + if (!is_closing()) + start_sync_user_preset(true); + }); + }).detach(); +} + 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 3212c719f5..7244425cfb 100644 --- a/src/slic3r/GUI/GUI_App.hpp +++ b/src/slic3r/GUI/GUI_App.hpp @@ -321,6 +321,7 @@ private: boost::thread m_sync_update_thread; std::shared_ptr m_user_sync_token; + std::atomic m_restart_sync_pending {false}; bool m_is_dark_mode{ false }; bool m_adding_script_handler { false }; bool m_side_popup_status{false}; @@ -530,6 +531,7 @@ public: void sync_preset(Preset* preset); void start_sync_user_preset(bool with_progress_dlg = false); void stop_sync_user_preset(); + void restart_sync_user_preset(); void on_stealth_mode_enter(); // Bundle subscription sync diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp index f4aeae01f6..d83cc1445a 100644 --- a/src/slic3r/GUI/MainFrame.cpp +++ b/src/slic3r/GUI/MainFrame.cpp @@ -2780,6 +2780,24 @@ void MainFrame::init_menubar_as_editor() append_submenu(fileMenu, export_menu, wxID_ANY, _L("Export"), ""); fileMenu->AppendSeparator(); + append_menu_item(fileMenu, wxID_ANY, _L("Sync Presets"), _L("Pull and apply the latest presets from OrcaCloud"), + [this](wxCommandEvent&) { + if (!wxGetApp().is_user_login()) { + MessageDialog info_dlg(this, _L("You must be logged in to sync presets from cloud."), + _L("Sync Presets"), wxOK | wxICON_INFORMATION); + info_dlg.ShowModal(); + return; + } + if (m_plater) + m_plater->get_notification_manager()->push_notification( + into_u8(_L("Syncing presets from cloud\u2026"))); + wxGetApp().restart_sync_user_preset(); + }, "", nullptr, + [this]() { + return wxGetApp().is_user_login() && !wxGetApp().app_config->get_stealth_mode(); + }, this); + + fileMenu->AppendSeparator(); #ifndef __APPLE__ append_menu_item(fileMenu, wxID_EXIT, _L("Quit"), wxString::Format(_L("Quit")),