Compare commits

...

3 Commits

Author SHA1 Message Date
Ian Chua
83815d8d77 Merge branch 'main' into fix/missing-local-plugins 2026-07-21 12:55:19 +08:00
Ian Chua
09580e1c29 fix: don't treat file not found as an error 2026-07-20 18:57:54 +08:00
Ian Chua
2f07eb2cd6 fix: resolve plugins that are missing locally (physical file deleted) 2026-07-20 18:51:25 +08:00
5 changed files with 79 additions and 9 deletions

View File

@@ -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<PluginDescriptor> missing = manager.get_missing_plugin_descriptors();
if (missing.empty())
return;
wxString names;
std::vector<std::string> 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()

View File

@@ -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);

View File

@@ -120,8 +120,7 @@ bool delete_plugin_root(const boost::filesystem::path& resolved_root, const std:
}
if (removed_count == 0) {
error = "Plugin folder was not found: " + resolved_root.string();
return false;
return true;
}
BOOST_LOG_TRIVIAL(info) << "Deleted plugin: " << plugin_id << " from " << resolved_root.string();

View File

@@ -310,6 +310,7 @@ void PluginManager::merge_discovered_plugins(std::vector<PluginDescriptor> 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<PluginDescriptor> 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<std::mutex> 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<PluginDescriptor> PluginManager::get_missing_plugin_descriptors() const
{
std::lock_guard<std::mutex> lock(m_mutex);
std::vector<PluginDescriptor> 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<std::string>& plugin_keys)
{
const std::unordered_set<std::string> 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<std::mutex> 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<bool(const Plugin&)>& should_remove,

View File

@@ -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<PluginDescriptor> get_missing_plugin_descriptors() const;
void remove_missing_plugins(const std::vector<std::string>& plugin_keys);
// Packages whose .install_state.json marks them for auto-load.
std::vector<std::string> 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<Plugin> m_plugins;
std::unordered_set<std::string> m_missing_plugin_keys;
std::unordered_set<std::string> m_load_in_progress;
// Keys whose in-flight load has been cancelled. Cancellation does NOT remove the key from