From 157412419e8142e30c49bcd171d96a68475d44d6 Mon Sep 17 00:00:00 2001 From: Hakan Seven Date: Wed, 17 Jun 2026 15:57:44 +0300 Subject: [PATCH] feat(plugin): uninstall + clean upgrade/reinstall of external plugins MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add an Uninstall button on installed external plugin cards that deletes the package folder (effective next restart — the library stays resident for the session). Reinstalling or upgrading via the marketplace now clears any stale native library with a different name before writing the new one, so the loader always picks the freshly installed binary. Part of the #100 extensibility epic (phase 2). Co-Authored-By: Claude Opus 4.8 --- src/app/mod.rs | 2 ++ src/app/update.rs | 18 ++++++++++++++++++ src/plugin/external.rs | 10 ++++++++++ src/plugin/marketplace.rs | 13 +++++++++++++ src/ui/plugin_manager.rs | 6 ++++++ 5 files changed, 49 insertions(+) diff --git a/src/app/mod.rs b/src/app/mod.rs index c00046393..aa43a9618 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1073,6 +1073,8 @@ pub enum Message { PluginInstall(String), /// Result of an install: the plugin id, or an error message. PluginInstalled(Result), + /// Delete an installed plugin's folder (effective next restart). + PluginUninstall(String), // ── Point Style (DDPTYPE) dialog ────────────────────────────────────── /// Set the full PDMODE value from a glyph-grid cell. PointStyleSetMode(i16), diff --git a/src/app/update.rs b/src/app/update.rs index 7318f1ef0..e62c65a3e 100644 --- a/src/app/update.rs +++ b/src/app/update.rs @@ -5699,6 +5699,24 @@ impl OpenCADStudio { self.marketplace_status = format!("Install failed: {e}"); Task::none() } + Message::PluginUninstall(id) => { + #[cfg(not(target_arch = "wasm32"))] + { + match crate::plugin::external::uninstall(&id) { + Ok(()) => { + self.marketplace_status = + format!("Uninstalled '{id}'. Restart to unload it."); + self.external_plugins = crate::plugin::external::discover(); + } + Err(e) => { + self.marketplace_status = format!("Uninstall failed: {e}"); + } + } + } + #[cfg(target_arch = "wasm32")] + let _ = id; + Task::none() + } Message::PointStyleSetMode(mode) => { self.set_point_mode_bits(!0, mode); Task::none() diff --git a/src/plugin/external.rs b/src/plugin/external.rs index 15816ab11..3b059fcbd 100644 --- a/src/plugin/external.rs +++ b/src/plugin/external.rs @@ -76,6 +76,16 @@ pub fn plugins_dir() -> Option { Some(p) } +/// Delete an installed package's folder. It stays loaded for the current +/// session (the library is resident); the removal takes effect on next start. +pub fn uninstall(id: &str) -> Result<(), String> { + let dir = plugins_dir().ok_or("cannot locate the plugins folder")?.join(id); + if dir.is_dir() { + std::fs::remove_dir_all(&dir).map_err(|e| e.to_string())?; + } + Ok(()) +} + /// Native dynamic-library extension for the current platform (no dot). fn lib_extension() -> &'static str { if cfg!(target_os = "windows") { diff --git a/src/plugin/marketplace.rs b/src/plugin/marketplace.rs index 05831a0f9..cf71ac539 100644 --- a/src/plugin/marketplace.rs +++ b/src/plugin/marketplace.rs @@ -192,6 +192,19 @@ pub fn install(release: &Release) -> Result { std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?; let bytes = download_bytes(&lib.url)?; + // Clean upgrade / reinstall: drop any previously-installed native library + // with a different name so the loader doesn't pick a stale one. The + // currently-resident library (if loaded) keeps running until restart. + let ext = external_lib_ext(); + if let Ok(rd) = std::fs::read_dir(&dir) { + for old in rd.flatten() { + let p = old.path(); + let is_lib = p.extension().and_then(|s| s.to_str()) == Some(ext); + if is_lib && p.file_name().and_then(|s| s.to_str()) != Some(lib.name.as_str()) { + let _ = std::fs::remove_file(&p); + } + } + } std::fs::write(dir.join(&lib.name), bytes).map_err(|e| e.to_string())?; std::fs::write(dir.join("plugin.toml"), toml_text).map_err(|e| e.to_string())?; Ok(manifest.id) diff --git a/src/ui/plugin_manager.rs b/src/ui/plugin_manager.rs index 65c70f7e4..2bb724e60 100644 --- a/src/ui/plugin_manager.rs +++ b/src/ui/plugin_manager.rs @@ -150,6 +150,12 @@ fn external_card<'a>(p: &ExternalPlugin, loaded: bool, disabled: bool) -> Elemen header = header.push(Space::new().width(10)); header = header.push(toggle_button(&p.id, disabled)); } + header = header.push(Space::new().width(6)); + header = header.push(pill_button( + "Uninstall", + Message::PluginUninstall(p.id.clone()), + Color { r: 0.4, g: 0.25, b: 0.25, a: 1.0 }, + )); let id_line = text(p.id.clone()).size(11).color(ACCENT); let mut body = column![header, id_line].spacing(5);