pinakes/examples/plugins
NotAShelf 489e55d0b4
examples/media-stats-ui: fix Transform source key; add file_name column
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I4c741e4b36708f2078fed8154d7341de6a6a6964
2026-03-12 20:49:38 +03:00
..
heif-support pinakes-plugin-api: expand test coverage; fix merge conflicts 2026-02-05 14:36:12 +03:00
markdown-metadata pinakes-plugin-api: expand test coverage; fix merge conflicts 2026-02-05 14:36:12 +03:00
media-stats-ui examples/media-stats-ui: fix Transform source key; add file_name column 2026-03-12 20:49:38 +03:00
README.md examples: add example plugins 2026-02-05 14:36:04 +03:00

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 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:

[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 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

# 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 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 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.toml manifest
  • 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

License

All example plugins are licensed under MIT.