Skip to content

Commit d045eea

Browse files
committed
fix(plugins): unregister running driver and verify installed manifest
Stop drivers before install to avoid locked files; validate manifest id and version; add unit tests
1 parent 996fc26 commit d045eea

5 files changed

Lines changed: 107 additions & 24 deletions

File tree

plugins/registry.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,17 @@
180180
"darwin-arm64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.2.0/google-sheets-plugin-darwin-arm64.zip",
181181
"win-x64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.2.0/google-sheets-plugin-win-x64.zip"
182182
}
183+
},
184+
{
185+
"version": "0.1.0",
186+
"min_tabularis_version": "0.9.21",
187+
"assets": {
188+
"linux-x64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.1.0/google-sheets-plugin-linux-x64.zip",
189+
"linux-arm64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.1.0/google-sheets-plugin-linux-arm64.zip",
190+
"darwin-x64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.1.0/google-sheets-plugin-darwin-x64.zip",
191+
"darwin-arm64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.1.0/google-sheets-plugin-darwin-arm64.zip",
192+
"win-x64": "https://github.com/TabularisDB/tabularis-google-sheets-plugin/releases/download/v0.1.0/google-sheets-plugin-win-x64.zip"
193+
}
183194
}
184195
]
185196
}

src-tauri/src/plugins/commands.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use std::fs;
2+
use std::time::Duration;
23

34
use crate::drivers::driver_trait::PluginManifest;
45
use crate::plugins::installer::{self, InstalledPluginInfo};
56
use crate::plugins::manager::ConfigManifest;
67
use crate::plugins::registry::{self, RegistryPluginWithStatus, RegistryReleaseWithStatus};
78
use tauri::AppHandle;
9+
use tokio::time::sleep;
810

911
#[tauri::command]
1012
pub async fn fetch_plugin_registry(
@@ -71,6 +73,12 @@ pub async fn install_plugin(
7173
plugin_id: String,
7274
version: Option<String>,
7375
) -> Result<(), String> {
76+
// Updating an installed plugin must stop the existing process first,
77+
// otherwise the OS may keep files locked while we replace the directory.
78+
crate::drivers::registry::unregister_driver(&plugin_id).await;
79+
crate::drivers::registry::unregister_manifest(&plugin_id).await;
80+
sleep(Duration::from_millis(500)).await;
81+
7482
let config = crate::config::load_config_internal(&app);
7583
let remote = registry::fetch_registry(config.custom_registry_url.as_deref()).await?;
7684
let platform = registry::get_current_platform();
@@ -102,6 +110,20 @@ pub async fn install_plugin(
102110

103111
installer::download_and_install(&plugin_id, download_url).await?;
104112

113+
let installed_plugin = installer::read_installed_plugin(&plugin_id)?;
114+
if installed_plugin.id != plugin_id {
115+
return Err(format!(
116+
"Plugin archive mismatch: registry expected id '{}' but installed manifest reports '{}'",
117+
plugin_id, installed_plugin.id
118+
));
119+
}
120+
if installed_plugin.version != target_version {
121+
return Err(format!(
122+
"Plugin archive version mismatch: registry expected version '{}' but installed manifest reports '{}'. The published asset appears inconsistent.",
123+
target_version, installed_plugin.version
124+
));
125+
}
126+
105127
// Hot-register the new driver (no restart needed)
106128
let plugin_cfg = config.plugins.as_ref().and_then(|m| m.get(&plugin_id));
107129
let interpreter_override = plugin_cfg.and_then(|c| c.interpreter.clone());

src-tauri/src/plugins/installer.rs

Lines changed: 32 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use std::fs;
22
use std::io::Read;
3-
use std::path::PathBuf;
3+
use std::path::{Path, PathBuf};
44

55
use directories::ProjectDirs;
66
use serde::{Deserialize, Serialize};
@@ -13,6 +13,14 @@ pub struct InstalledPluginInfo {
1313
pub description: String,
1414
}
1515

16+
#[derive(Deserialize)]
17+
struct InstalledPluginManifest {
18+
id: String,
19+
name: String,
20+
version: String,
21+
description: String,
22+
}
23+
1624
pub fn get_plugins_dir() -> Result<PathBuf, String> {
1725
let proj_dirs = ProjectDirs::from("com", "debba", "tabularis")
1826
.ok_or_else(|| "Could not determine project directories".to_string())?;
@@ -24,6 +32,27 @@ pub fn get_plugins_dir() -> Result<PathBuf, String> {
2432
Ok(plugins_dir)
2533
}
2634

35+
pub(crate) fn read_plugin_info_from_dir(path: &Path) -> Result<InstalledPluginInfo, String> {
36+
let manifest_path = path.join("manifest.json");
37+
let manifest_str = fs::read_to_string(&manifest_path)
38+
.map_err(|e| format!("Failed to read plugin manifest {:?}: {}", manifest_path, e))?;
39+
40+
let manifest: InstalledPluginManifest = serde_json::from_str(&manifest_str)
41+
.map_err(|e| format!("Failed to parse plugin manifest {:?}: {}", manifest_path, e))?;
42+
43+
Ok(InstalledPluginInfo {
44+
id: manifest.id,
45+
name: manifest.name,
46+
version: manifest.version,
47+
description: manifest.description,
48+
})
49+
}
50+
51+
pub fn read_installed_plugin(plugin_id: &str) -> Result<InstalledPluginInfo, String> {
52+
let plugins_dir = get_plugins_dir()?;
53+
read_plugin_info_from_dir(&plugins_dir.join(plugin_id))
54+
}
55+
2756
pub async fn download_and_install(plugin_id: &str, download_url: &str) -> Result<(), String> {
2857
let plugins_dir = get_plugins_dir()?;
2958
let tmp_dir = plugins_dir.join(format!(".tmp-{}", plugin_id));
@@ -210,30 +239,9 @@ pub fn list_installed() -> Result<Vec<InstalledPluginInfo>, String> {
210239
continue;
211240
}
212241

213-
let manifest_str = match fs::read_to_string(&manifest_path) {
214-
Ok(s) => s,
215-
Err(_) => continue,
216-
};
217-
218-
#[derive(Deserialize)]
219-
struct Manifest {
220-
id: String,
221-
name: String,
222-
version: String,
223-
description: String,
242+
if let Ok(plugin) = read_plugin_info_from_dir(&path) {
243+
plugins.push(plugin);
224244
}
225-
226-
let manifest: Manifest = match serde_json::from_str(&manifest_str) {
227-
Ok(m) => m,
228-
Err(_) => continue,
229-
};
230-
231-
plugins.push(InstalledPluginInfo {
232-
id: manifest.id,
233-
name: manifest.name,
234-
version: manifest.version,
235-
description: manifest.description,
236-
});
237245
}
238246

239247
Ok(plugins)

src-tauri/src/plugins/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ pub mod installer;
44
pub mod manager;
55
pub mod registry;
66
pub mod rpc;
7+
8+
#[cfg(test)]
9+
mod tests;

src-tauri/src/plugins/tests.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
use std::fs;
2+
3+
use tempfile::tempdir;
4+
5+
use super::installer::read_plugin_info_from_dir;
6+
7+
#[test]
8+
fn reads_installed_plugin_info_from_manifest() {
9+
let dir = tempdir().expect("temp dir");
10+
let manifest_path = dir.path().join("manifest.json");
11+
fs::write(
12+
&manifest_path,
13+
r#"{
14+
"id": "google-sheets",
15+
"name": "Google Sheets",
16+
"version": "0.2.0",
17+
"description": "Query Sheets"
18+
}"#,
19+
)
20+
.expect("write manifest");
21+
22+
let plugin = read_plugin_info_from_dir(dir.path()).expect("read manifest");
23+
24+
assert_eq!(plugin.id, "google-sheets");
25+
assert_eq!(plugin.name, "Google Sheets");
26+
assert_eq!(plugin.version, "0.2.0");
27+
assert_eq!(plugin.description, "Query Sheets");
28+
}
29+
30+
#[test]
31+
fn returns_error_for_invalid_manifest() {
32+
let dir = tempdir().expect("temp dir");
33+
let manifest_path = dir.path().join("manifest.json");
34+
fs::write(&manifest_path, "{ invalid json").expect("write manifest");
35+
36+
let error = read_plugin_info_from_dir(dir.path()).expect_err("invalid manifest");
37+
38+
assert!(error.contains("Failed to parse plugin manifest"));
39+
}

0 commit comments

Comments
 (0)