bc9fa36646
Week 10 - Plugin System (FR-19): - Plugin traits: Plugin, OriginPlugin, MetadataPlugin, FormatPlugin - NativePluginHost with libloading for dynamic loading - WasmPluginHost (feature-gated) with wasmtime runtime - PluginManager coordinating both hosts with version checks - OriginInstance::watch() with WatchHandle, WatchEvent for live updates - FormatPlugin::synthesize_header() for metadata overlay Week 11 - Control API & Production (FR-17, FR-18, NFR-6, NFR-10): - gRPC server with full MusicFS service (status, cache, origins, events) - Proto extended: MountState enum, TierStats, full StatusResponse/CacheStats - WebhookHandler with HMAC-SHA256 signing and exponential retry - Metrics with latency histograms (p50/p95/p99) and origin health gauges - CLI with mount, status, cache, search, origin, events, shutdown commands - E2E player compatibility tests (mpv, VLC, file manager) - systemd service, PKGBUILD, RPM spec for packaging Plans added for Weeks 10-14 covering P1 features. All 154 tests passing.
4.8 KiB
4.8 KiB
Week 10: Plugin System
Phase: 4 - Plugin System & Polish
Goal: Extensibility via native and WASM plugins
Requirements: FR-23.1-23.5, FR-24.1-24.3
Deliverables
| Task | Crate | Files | Requirements |
|---|---|---|---|
| Plugin traits | musicfs-plugins | traits.rs |
FR-23.1-23.4 |
| Native host | musicfs-plugins | native.rs |
FR-23.2 |
| WASM host | musicfs-plugins | wasm.rs |
FR-23.3 |
| Plugin lifecycle | musicfs-plugins | manager.rs |
FR-23.5 |
| Example plugins | plugins/ | example-origin/, example-format/ |
FR-23.5 |
Plugin Traits (musicfs-plugins/src/traits.rs)
/// Base plugin interface
pub trait Plugin: Send + Sync {
fn name(&self) -> &str;
fn version(&self) -> Version;
fn init(&mut self, config: Value) -> Result<(), PluginError>;
fn shutdown(&mut self) -> Result<(), PluginError>;
}
/// Origin plugin interface (per architecture 4.3.4)
pub trait OriginPlugin: Plugin {
fn origin_type(&self) -> &str;
fn create(&self, config: Value) -> Result<Box<dyn Origin>, PluginError>;
}
/// Metadata source plugin
pub trait MetadataPlugin: Plugin {
fn lookup(&self, query: &MetadataQuery) -> Result<Option<ExternalMetadata>, PluginError>;
}
/// Format plugin for custom audio formats (FR-24.1)
pub trait FormatPlugin: Plugin {
fn extensions(&self) -> &[&str];
fn can_handle(&self, extension: &str) -> bool;
fn parse(&self, reader: &mut dyn Read) -> Result<AudioMeta, PluginError>;
}
Native Plugin Host (musicfs-plugins/src/native.rs)
pub struct NativePluginHost {
plugins: HashMap<String, LoadedPlugin>,
search_paths: Vec<PathBuf>,
}
struct LoadedPlugin {
library: libloading::Library,
instance: Box<dyn Plugin>,
}
impl NativePluginHost {
pub fn new() -> Self;
/// Load plugin from shared library (.so/.dylib)
pub fn load(&mut self, path: &Path) -> Result<PluginId, PluginError>;
/// Unload plugin (FR-23.5)
pub fn unload(&mut self, id: PluginId) -> Result<(), PluginError>;
/// Hot reload plugin without restart (FR-23.4)
pub fn reload(&mut self, id: PluginId) -> Result<(), PluginError>;
/// List loaded plugins
pub fn list(&self) -> Vec<PluginInfo>;
}
WASM Plugin Host (musicfs-plugins/src/wasm.rs)
pub struct WasmPluginHost {
engine: wasmtime::Engine,
linker: wasmtime::Linker<PluginState>,
}
impl WasmPluginHost {
pub fn new() -> Result<Self, PluginError>;
/// Load WASM plugin with sandboxing (FR-23.3)
pub fn load(&mut self, wasm_bytes: &[u8]) -> Result<WasmPlugin, PluginError>;
/// Resource limits for sandboxed execution
pub fn set_limits(&mut self, limits: ResourceLimits);
}
pub struct ResourceLimits {
pub max_memory_mb: u32,
pub max_cpu_time_ms: u32,
pub allow_network: bool,
pub allow_filesystem: bool,
}
Plugin Manager (musicfs-plugins/src/manager.rs)
pub struct PluginManager {
native_host: NativePluginHost,
wasm_host: WasmPluginHost,
registry: PluginRegistry,
}
impl PluginManager {
/// Initialize and load plugins from config
pub fn init(config: &PluginConfig) -> Result<Self, PluginError>;
/// Get all origin plugins
pub fn origin_plugins(&self) -> Vec<&dyn OriginPlugin>;
/// Get all format plugins
pub fn format_plugins(&self) -> Vec<&dyn FormatPlugin>;
/// Get all metadata plugins
pub fn metadata_plugins(&self) -> Vec<&dyn MetadataPlugin>;
/// Reload all plugins (hot reload)
pub fn reload_all(&mut self) -> Result<(), PluginError>;
}
Tests
| Test | Type | Validates |
|---|---|---|
test_native_plugin_load |
Unit | Native plugin loading (FR-23.2) |
test_native_plugin_unload |
Unit | Clean unload |
test_wasm_plugin_sandbox |
Unit | WASM isolation (FR-23.3) |
test_wasm_resource_limits |
Unit | Memory/CPU limits enforced |
test_plugin_hot_reload |
Integration | Reload without restart (FR-23.4) |
test_example_origin_plugin |
Integration | Custom origin works |
test_example_format_plugin |
Integration | Custom format works |
Exit Criteria
- Native plugins loadable at runtime
- WASM plugins sandboxed with resource limits
- Example plugins functional
- Plugins hot-reloadable without daemon restart
- Plugin lifecycle management (load, unload, reload)
Architecture Alignment
Per architecture.md section 4.3.4:
- Plugin loading: Built-in → Native (.so) → WASM
- Origin plugins create
Box<dyn Origin> - Format plugins register file extensions
- WASM runs in wasmtime sandbox
Per requirements.md:
- FR-23.1: Loadable plugins ✓
- FR-23.2: Stable plugin API ✓
- FR-23.3: Plugins for origins, metadata, formats ✓
- FR-23.4: WASM sandbox ✓
- FR-23.5: Plugin lifecycle ✓