pinakes/examples/plugins/README.md
NotAShelf 708f8a0b67
examples: add example plugins
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I9eac30c7d4c1c89178f4930b215e523d6a6a6964
2026-02-05 14:36:04 +03:00

518 lines
11 KiB
Markdown

# Pinakes Plugin Examples
This directory contains example plugins demonstrating the Pinakes plugin system.
## Overview
Pinakes supports extensibility through a WASM-based plugin system. Plugins can
extend Pinakes functionality by:
- **Media Type Providers**: Add support for new file formats
- **Metadata Extractors**: Extract metadata from files
- **Thumbnail Generators**: Generate thumbnails for media types
- **Search Backends**: Implement custom search algorithms
- **Event Handlers**: React to system events
- **Theme Providers**: Provide custom UI themes
## Example Plugins
### 1. Markdown Metadata Extractor
**Directory**: `markdown-metadata/`
Enhances markdown file support with advanced frontmatter parsing.
**Demonstrates**:
- Metadata extraction from files
- Plugin configuration via `plugin.toml`
- Minimal capability requirements
**Plugin Kind**: `metadata_extractor`
### 2. HEIF/HEIC Support
**Directory**: `heif-support/`
Adds support for HEIF and HEIC image formats.
**Demonstrates**:
- Media type registration
- Metadata extraction from binary formats
- Thumbnail generation
- Resource limits (memory, CPU time)
**Plugin Kinds**: `media_type`, `metadata_extractor`, `thumbnail_generator`
## Plugin Architecture
### Plugin Structure
```
my-plugin/
├── plugin.toml # Plugin manifest
├── Cargo.toml # Rust project configuration
├── src/
│ └── lib.rs # Plugin implementation
└── README.md # Plugin documentation
```
### Plugin Manifest (plugin.toml)
```toml
[plugin]
name = "my-plugin"
version = "1.0.0"
api_version = "1.0"
author = "Your Name"
description = "Description of your plugin"
kind = ["metadata_extractor"]
[plugin.binary]
wasm = "my_plugin.wasm"
[capabilities]
network = false
[capabilities.filesystem]
read = ["/path/to/read"]
write = ["/path/to/write"]
[config]
# Plugin-specific configuration
option1 = "value1"
option2 = 42
```
### Manifest Fields
#### [plugin] Section
- `name`: Plugin identifier (must be unique)
- `version`: Semantic version (e.g., "1.0.0")
- `api_version`: Pinakes Plugin API version (currently "1.0")
- `author`: Plugin author (optional)
- `description`: Short description (optional)
- `homepage`: Plugin homepage URL (optional)
- `license`: License identifier (optional)
- `kind`: Array of plugin kinds
- `dependencies`: Array of plugin names this plugin depends on (optional)
#### [plugin.binary] Section
- `wasm`: Path to WASM binary (relative to manifest)
- `entrypoint`: Custom entrypoint function name (optional, default: "_start")
#### [capabilities] Section
Capabilities define what the plugin can access:
**Filesystem**:
```toml
[capabilities.filesystem]
read = ["/tmp/cache", "/var/data"]
write = ["/tmp/output"]
```
**Network**:
```toml
[capabilities]
network = true # or false
```
**Environment**:
```toml
[capabilities]
environment = ["PATH", "HOME"] # or omit for no access
```
**Resource Limits**:
```toml
[capabilities]
max_memory_mb = 128
max_cpu_time_secs = 10
```
### Plugin Kinds
#### media_type
Register new media types with file extensions and MIME types.
**Trait**: `MediaTypeProvider`
**Methods**:
- `supported_media_types()`: Returns list of media type definitions
- `can_handle(path, mime_type)`: Check if plugin can handle a file
#### metadata_extractor
Extract metadata from files.
**Trait**: `MetadataExtractor`
**Methods**:
- `extract_metadata(path)`: Extract metadata from file
- `supported_types()`: Returns list of supported media type IDs
#### thumbnail_generator
Generate thumbnails for media files.
**Trait**: `ThumbnailGenerator`
**Methods**:
- `generate_thumbnail(path, output_path, options)`: Generate thumbnail
- `supported_types()`: Returns list of supported media type IDs
#### search_backend
Implement custom search algorithms.
**Trait**: `SearchBackend`
**Methods**:
- `index_item(item)`: Index a media item
- `remove_item(item_id)`: Remove item from index
- `search(query)`: Perform search
- `get_stats()`: Get index statistics
#### event_handler
React to system events.
**Trait**: `EventHandler`
**Methods**:
- `handle_event(event)`: Handle an event
- `interested_events()`: Returns list of event types to receive
#### theme_provider
Provide UI themes.
**Trait**: `ThemeProvider`
**Methods**:
- `get_themes()`: List available themes
- `load_theme(theme_id)`: Load theme data
## Creating a Plugin
### Step 1: Set Up Project
```bash
# Create new Rust library project
cargo new --lib my-plugin
cd my-plugin
# Add dependencies
cat >> Cargo.toml <<EOF
[dependencies]
pinakes-plugin-api = { path = "../../crates/pinakes-plugin-api" }
async-trait = "0.1"
serde = { version = "1.0", features = ["derive"] }
[lib]
crate-type = ["cdylib"]
[profile.release]
opt-level = "z"
lto = true
EOF
```
### Step 2: Implement Plugin
```rust
use async_trait::async_trait;
use pinakes_plugin_api::*;
use std::path::PathBuf;
pub struct MyPlugin {
context: Option<PluginContext>,
}
#[async_trait]
impl Plugin for MyPlugin {
fn metadata(&self) -> &PluginMetadata {
// Return plugin metadata
}
async fn initialize(&mut self, context: PluginContext) -> PluginResult<()> {
self.context = Some(context);
Ok(())
}
async fn shutdown(&mut self) -> PluginResult<()> {
Ok(())
}
async fn health_check(&self) -> PluginResult<HealthStatus> {
Ok(HealthStatus {
healthy: true,
message: None,
metrics: Default::default(),
})
}
}
#[async_trait]
impl MetadataExtractor for MyPlugin {
async fn extract_metadata(&self, path: &PathBuf) -> PluginResult<ExtractedMetadata> {
// Extract metadata from file
}
fn supported_types(&self) -> Vec<String> {
vec!["my_type".to_string()]
}
}
```
### Step 3: Build to WASM
```bash
# Install WASM target
rustup target add wasm32-wasi
# Build
cargo build --target wasm32-wasi --release
# Optimize (optional, wasm-tools provides wasm-strip functionality)
cargo install wasm-tools
wasm-tools strip target/wasm32-wasi/release/my_plugin.wasm -o target/wasm32-wasi/release/my_plugin.wasm
# Copy to plugin directory
cp target/wasm32-wasi/release/my_plugin.wasm .
```
### Step 4: Create Manifest
Create `plugin.toml` with appropriate configuration (see examples above).
### Step 5: Install Plugin
```bash
# Copy to plugins directory
cp -r my-plugin ~/.config/pinakes/plugins/
# Or use API
curl -X POST http://localhost:3000/api/v1/plugins/install \
-d '{"source": "/path/to/my-plugin"}'
```
## Testing Plugins
### Unit Tests
```rust
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_metadata_extraction() {
let mut plugin = MyPlugin::default();
let context = PluginContext {
data_dir: PathBuf::from("/tmp/data"),
cache_dir: PathBuf::from("/tmp/cache"),
config: Default::default(),
capabilities: Default::default(),
};
plugin.initialize(context).await.unwrap();
let metadata = plugin
.extract_metadata(&PathBuf::from("test.txt"))
.await
.unwrap();
assert!(metadata.title.is_some());
}
}
```
### Integration Tests
```bash
# Load plugin in test Pinakes instance
pinakes --config test-config.toml plugin load /path/to/plugin
# Verify plugin is loaded
pinakes plugin list
# Test plugin functionality
pinakes scan /path/to/test/files
```
## Security Considerations
### Capability-Based Security
Plugins operate in a sandbox with explicit capabilities. Only request the
minimum capabilities needed:
**Good**:
```toml
[capabilities.filesystem]
read = ["/tmp/cache"]
```
**Bad**:
```toml
[capabilities.filesystem]
read = ["/", "/home", "/etc"] # Too broad!
```
### Resource Limits
Always set appropriate resource limits:
```toml
[capabilities]
max_memory_mb = 128 # Reasonable for image processing
max_cpu_time_secs = 10 # Prevent runaway operations
```
### Input Validation
Always validate input in your plugin:
```rust
async fn extract_metadata(&self, path: &PathBuf) -> PluginResult<ExtractedMetadata> {
// Check file exists
if !path.exists() {
return Err(PluginError::InvalidInput("File not found".to_string()));
}
// Check file size
let metadata = std::fs::metadata(path)
.map_err(|e| PluginError::IoError(e.to_string()))?;
if metadata.len() > 10_000_000 { // 10MB limit
return Err(PluginError::InvalidInput("File too large".to_string()));
}
// Process file...
}
```
## Best Practices
### Error Handling
- Use descriptive error messages
- Return appropriate `PluginError` variants
- Don't panic - return errors instead
### Performance
- Avoid blocking operations in async functions
- Use streaming for large files
- Implement timeouts for external operations
- Cache results when appropriate
### Configuration
- Provide sensible defaults
- Document all configuration options
- Validate configuration during initialization
### Documentation
- Write clear README with examples
- Document all configuration options
- Include troubleshooting section
- Provide integration examples
## API Reference
See the
[pinakes-plugin-api documentation](../../crates/pinakes-plugin-api/README.md)
for detailed API reference.
## Plugin Distribution
### Plugin Registry (Future)
A centralized plugin registry is planned for future releases:
```bash
# Install from registry
pinakes plugin install markdown-metadata
# Search plugins
pinakes plugin search heif
# Update all plugins
pinakes plugin update --all
```
### Manual Distribution
Currently, plugins are distributed as directories containing:
- `plugin.toml` manifest
- WASM binary
- README and documentation
## Troubleshooting
### Plugin Won't Load
**Check manifest syntax**:
```bash
# Validate TOML syntax
taplo check plugin.toml
```
**Check API version**: Ensure `api_version = "1.0"` in manifest.
**Check binary path**: Verify WASM binary exists at path specified in
`plugin.binary.wasm`.
### Plugin Crashes
**Check resource limits**: Increase `max_memory_mb` or `max_cpu_time_secs` if
operations are timing out.
**Check capabilities**: Ensure plugin has necessary filesystem/network
capabilities.
**Check logs**:
```bash
# View plugin logs
tail -f ~/.local/share/pinakes/logs/plugin.log
```
### Permission Denied Errors
**Filesystem capabilities**: Add required paths to
`capabilities.filesystem.read` or `.write`.
**Network capabilities**: Set `capabilities.network = true` if plugin needs
network access.
## Support
- **Issues**: https://github.com/notashelf/pinakes/issues
- **Discussions**: https://github.com/notashelf/pinakes/discussions
- **Documentation**: https://pinakes.readthedocs.io
## License
All example plugins are licensed under MIT.