feat(plugin): uninstall + clean upgrade/reinstall of external plugins
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 <noreply@anthropic.com>
This commit is contained in:
parent
eb53e445ab
commit
157412419e
5 changed files with 49 additions and 0 deletions
|
|
@ -1073,6 +1073,8 @@ pub enum Message {
|
||||||
PluginInstall(String),
|
PluginInstall(String),
|
||||||
/// Result of an install: the plugin id, or an error message.
|
/// Result of an install: the plugin id, or an error message.
|
||||||
PluginInstalled(Result<String, String>),
|
PluginInstalled(Result<String, String>),
|
||||||
|
/// Delete an installed plugin's folder (effective next restart).
|
||||||
|
PluginUninstall(String),
|
||||||
// ── Point Style (DDPTYPE) dialog ──────────────────────────────────────
|
// ── Point Style (DDPTYPE) dialog ──────────────────────────────────────
|
||||||
/// Set the full PDMODE value from a glyph-grid cell.
|
/// Set the full PDMODE value from a glyph-grid cell.
|
||||||
PointStyleSetMode(i16),
|
PointStyleSetMode(i16),
|
||||||
|
|
|
||||||
|
|
@ -5699,6 +5699,24 @@ impl OpenCADStudio {
|
||||||
self.marketplace_status = format!("Install failed: {e}");
|
self.marketplace_status = format!("Install failed: {e}");
|
||||||
Task::none()
|
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) => {
|
Message::PointStyleSetMode(mode) => {
|
||||||
self.set_point_mode_bits(!0, mode);
|
self.set_point_mode_bits(!0, mode);
|
||||||
Task::none()
|
Task::none()
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,16 @@ pub fn plugins_dir() -> Option<PathBuf> {
|
||||||
Some(p)
|
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).
|
/// Native dynamic-library extension for the current platform (no dot).
|
||||||
fn lib_extension() -> &'static str {
|
fn lib_extension() -> &'static str {
|
||||||
if cfg!(target_os = "windows") {
|
if cfg!(target_os = "windows") {
|
||||||
|
|
|
||||||
|
|
@ -192,6 +192,19 @@ pub fn install(release: &Release) -> Result<String, String> {
|
||||||
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
std::fs::create_dir_all(&dir).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let bytes = download_bytes(&lib.url)?;
|
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(&lib.name), bytes).map_err(|e| e.to_string())?;
|
||||||
std::fs::write(dir.join("plugin.toml"), toml_text).map_err(|e| e.to_string())?;
|
std::fs::write(dir.join("plugin.toml"), toml_text).map_err(|e| e.to_string())?;
|
||||||
Ok(manifest.id)
|
Ok(manifest.id)
|
||||||
|
|
|
||||||
|
|
@ -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(Space::new().width(10));
|
||||||
header = header.push(toggle_button(&p.id, disabled));
|
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 id_line = text(p.id.clone()).size(11).color(ACCENT);
|
||||||
let mut body = column![header, id_line].spacing(5);
|
let mut body = column![header, id_line].spacing(5);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue