Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: I7c9ccac175440d278fd129dbd53f04d66a6a6964 |
||
|---|---|---|
| .. | ||
| heif-support | ||
| markdown-metadata | ||
| media-stats-ui | ||
| README.md | ||
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)
[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 kindsdependencies: 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:
[capabilities.filesystem]
read = ["/tmp/cache", "/var/data"]
write = ["/tmp/output"]
Network:
[capabilities]
network = true # or false
Environment:
[capabilities]
environment = ["PATH", "HOME"] # or omit for no access
Resource Limits:
[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 definitionscan_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 filesupported_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 thumbnailsupported_types(): Returns list of supported media type IDs
search_backend
Implement custom search algorithms.
Trait: SearchBackend
Methods:
index_item(item): Index a media itemremove_item(item_id): Remove item from indexsearch(query): Perform searchget_stats(): Get index statistics
event_handler
React to system events.
Trait: EventHandler
Methods:
handle_event(event): Handle an eventinterested_events(): Returns list of event types to receive
theme_provider
Provide UI themes.
Trait: ThemeProvider
Methods:
get_themes(): List available themesload_theme(theme_id): Load theme data
Creating a Plugin
Step 1: Set Up Project
# 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
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
# 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
# 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
#[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
# 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:
[capabilities.filesystem]
read = ["/tmp/cache"]
Bad:
[capabilities.filesystem]
read = ["/", "/home", "/etc"] # Too broad!
Resource Limits
Always set appropriate resource limits:
[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:
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
PluginErrorvariants - 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 for detailed API reference.
Plugin Distribution
Plugin Registry (Future)
A centralized plugin registry is planned for future releases:
# 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.tomlmanifest- WASM binary
- README and documentation
Troubleshooting
Plugin Won't Load
Check manifest syntax:
# 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:
# 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.