diff --git a/src/slic3r/GUI/PluginsDialog.cpp b/src/slic3r/GUI/PluginsDialog.cpp index 3011c35ce3..a3578419a6 100644 --- a/src/slic3r/GUI/PluginsDialog.cpp +++ b/src/slic3r/GUI/PluginsDialog.cpp @@ -585,7 +585,35 @@ bool PluginsDialog::get_descriptor(const std::string& plugin_key, PluginDescript void PluginsDialog::refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud) { - run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() { send_plugins(); }, title, message); + run_with_dialog([fetch_cloud]() { refresh_plugin_metadata_blocking(fetch_cloud); }, [this]() { + prompt_for_missing_plugins(); + send_plugins(); + }, title, message); +} + +void PluginsDialog::prompt_for_missing_plugins() +{ + PluginManager& manager = PluginManager::instance(); + const std::vector missing = manager.get_missing_plugin_descriptors(); + if (missing.empty()) + return; + + wxString names; + std::vector keys; + keys.reserve(missing.size()); + for (const PluginDescriptor& plugin : missing) { + keys.push_back(plugin.plugin_key); + names += "\n- "; + names += plugin_display_name(plugin.plugin_key); + } + + const int result = wxMessageBox( + wxString::Format(_L("The following installed plugins were not found on disk:\n%s\n\nRemove them from OrcaSlicer?"), names), + _L("Missing Plugins"), wxYES_NO | wxNO_DEFAULT | wxICON_WARNING, this); + restore_z_order(); + + if (result == wxYES) + manager.remove_missing_plugins(keys); } void PluginsDialog::refresh_plugins() diff --git a/src/slic3r/GUI/PluginsDialog.hpp b/src/slic3r/GUI/PluginsDialog.hpp index de0f44985d..2a9676f917 100644 --- a/src/slic3r/GUI/PluginsDialog.hpp +++ b/src/slic3r/GUI/PluginsDialog.hpp @@ -68,6 +68,7 @@ private: bool get_descriptor(const std::string& plugin_key, Slic3r::PluginDescriptor& descriptor) const; void refresh_plugin_metadata_async(const wxString& title, const wxString& message, bool fetch_cloud); + void prompt_for_missing_plugins(); void refresh_plugins(); void toggle_plugin(const std::string& plugin_key, bool enabled); void toggle_plugin_capability(const std::string& plugin_key, PluginCapabilityType type, const std::string& capability_name, bool enabled); diff --git a/src/slic3r/plugin/PluginManager.cpp b/src/slic3r/plugin/PluginManager.cpp index c586aafe99..31e16aacad 100644 --- a/src/slic3r/plugin/PluginManager.cpp +++ b/src/slic3r/plugin/PluginManager.cpp @@ -310,6 +310,7 @@ void PluginManager::merge_discovered_plugins(std::vector disco } seen.push_back(descriptor.plugin_key); + m_missing_plugin_keys.erase(descriptor.plugin_key); Plugin* existing = find_plugin_locked(descriptor.plugin_key); if (existing == nullptr) { @@ -328,12 +329,47 @@ void PluginManager::merge_discovered_plugins(std::vector disco return; } - // Unloading may call Python and lifecycle subscribers may re-enter the manager, so never do it - // while holding m_mutex. unload_and_erase_if() retries until no matching entry is loaded at the - // moment of erase, in case another caller starts a load between the initial snapshot and the - // teardown. - unload_and_erase_if( - [&seen](const Plugin& plugin) { return std::find(seen.begin(), seen.end(), plugin.descriptor.plugin_key) == seen.end(); }); + // A package can be temporarily absent while an external side-loader replaces it. Keep the + // descriptor and its persisted enable state until the user explicitly removes the missing + // entry, or a later scan rediscovers it. In particular, do not unload here: the unload callback + // would turn a transient filesystem gap into enabled=false in the sidecar. + { + std::lock_guard lock(m_mutex); + for (const Plugin& plugin : m_plugins) { + if (plugin.descriptor.has_local_package() && + std::find(seen.begin(), seen.end(), plugin.descriptor.plugin_key) == seen.end()) + m_missing_plugin_keys.insert(plugin.descriptor.plugin_key); + } + } +} + +std::vector PluginManager::get_missing_plugin_descriptors() const +{ + std::lock_guard lock(m_mutex); + + std::vector result; + result.reserve(m_missing_plugin_keys.size()); + for (const Plugin& plugin : m_plugins) + if (m_missing_plugin_keys.count(plugin.descriptor.plugin_key) != 0) + result.push_back(plugin.descriptor); + return result; +} + +void PluginManager::remove_missing_plugins(const std::vector& plugin_keys) +{ + const std::unordered_set requested(plugin_keys.begin(), plugin_keys.end()); + + // The predicate is evaluated only while m_mutex is held by unload_and_erase_if(). Checking the + // current missing set here prevents a package that reappeared between the dialog and removal + // from being erased. + unload_and_erase_if([this, &requested](const Plugin& plugin) { + return requested.count(plugin.descriptor.plugin_key) != 0 && + m_missing_plugin_keys.count(plugin.descriptor.plugin_key) != 0; + }); + + std::lock_guard lock(m_mutex); + for (const std::string& plugin_key : requested) + m_missing_plugin_keys.erase(plugin_key); } void PluginManager::unload_and_erase_if(const std::function& should_remove, diff --git a/src/slic3r/plugin/PluginManager.hpp b/src/slic3r/plugin/PluginManager.hpp index 59a2791355..67f400e830 100644 --- a/src/slic3r/plugin/PluginManager.hpp +++ b/src/slic3r/plugin/PluginManager.hpp @@ -132,6 +132,11 @@ public: bool try_get_plugin_descriptor(const std::string& plugin_key, PluginDescriptor& out) const; // Same, but only for packages that are loadable (i.e. not an invalid package). bool try_get_valid_plugin_descriptor(const std::string& plugin_key, PluginDescriptor& out) const; + // Packages that were present in the previous discovery pass but were not found on disk in the + // latest rescan. They are retained until the user explicitly removes them or a later scan finds + // them again. + std::vector get_missing_plugin_descriptors() const; + void remove_missing_plugins(const std::vector& plugin_keys); // Packages whose .install_state.json marks them for auto-load. std::vector get_enabled_plugin_keys() const; // The package owning a loaded capability, for the by-name dispatch path. @@ -264,6 +269,7 @@ private: // Every discovered plugin, loaded or not. module == nullptr => not loaded. std::vector m_plugins; + std::unordered_set m_missing_plugin_keys; std::unordered_set m_load_in_progress; // Keys whose in-flight load has been cancelled. Cancellation does NOT remove the key from