diff --git a/examples/plugins/README.md b/examples/plugins/README.md deleted file mode 100644 index 12c5217..0000000 --- a/examples/plugins/README.md +++ /dev/null @@ -1,518 +0,0 @@ -# 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 <, -} - -#[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 { - Ok(HealthStatus { - healthy: true, - message: None, - metrics: Default::default(), - }) - } -} - -#[async_trait] -impl MetadataExtractor for MyPlugin { - async fn extract_metadata(&self, path: &PathBuf) -> PluginResult { - // Extract metadata from file - } - - fn supported_types(&self) -> Vec { - 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 { - // 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. diff --git a/examples/plugins/heif-support/README.md b/examples/plugins/heif-support/README.md deleted file mode 100644 index d3e866d..0000000 --- a/examples/plugins/heif-support/README.md +++ /dev/null @@ -1,269 +0,0 @@ -# HEIF/HEIC Support Plugin - -This example plugin adds support for HEIF (High Efficiency Image Format) and -HEIC (HEIF Container) to Pinakes. - -## Overview - -HEIF is a modern image format that provides better compression than JPEG while -maintaining higher quality. This plugin enables Pinakes to: - -- Recognize HEIF/HEIC files as a media type -- Extract metadata from HEIF images -- Generate thumbnails from HEIF images - -## Features - -- **Media Type Registration**: Registers `.heif`, `.heic`, `.hif` extensions as - image media types -- **EXIF Extraction**: Extracts EXIF metadata including camera info, GPS - coordinates, timestamps -- **Thumbnail Generation**: Generates thumbnails in JPEG, PNG, or WebP format -- **Resource Limits**: Configurable memory and CPU limits for safe processing -- **Large Image Support**: Handles images up to 8192x8192 pixels - -## Supported Formats - -- **HEIF**: High Efficiency Image Format (`.heif`, `.hif`) -- **HEIC**: HEIF Container format used by Apple devices (`.heic`) -- **HEIF Sequences**: Multi-image HEIF files -- **HEIF with Alpha**: HEIF images with transparency - -## Implementation - -The plugin implements three traits: - -### MediaTypeProvider - -```rust -#[async_trait] -impl MediaTypeProvider for HeifPlugin { - fn supported_media_types(&self) -> Vec { - vec![MediaTypeDefinition { - id: "heif".to_string(), - name: "HEIF Image".to_string(), - category: "image".to_string(), - extensions: vec!["heif".to_string(), "heic".to_string(), "hif".to_string()], - mime_types: vec!["image/heif".to_string(), "image/heic".to_string()], - icon: Some("image".to_string()), - }] - } - - async fn can_handle(&self, path: &PathBuf, mime_type: Option<&str>) -> PluginResult { - // Check file extension and/or MIME type - } -} -``` - -### MetadataExtractor - -```rust -#[async_trait] -impl MetadataExtractor for HeifPlugin { - async fn extract_metadata(&self, path: &PathBuf) -> PluginResult { - // 1. Parse HEIF file structure - // 2. Extract EXIF metadata - // 3. Get image dimensions - // 4. Return ExtractedMetadata - } - - fn supported_types(&self) -> Vec { - vec!["heif".to_string()] - } -} -``` - -### ThumbnailGenerator - -```rust -#[async_trait] -impl ThumbnailGenerator for HeifPlugin { - async fn generate_thumbnail( - &self, - path: &PathBuf, - output_path: &PathBuf, - options: ThumbnailOptions, - ) -> PluginResult { - // 1. Decode HEIF image - // 2. Resize to thumbnail dimensions - // 3. Encode to output format - // 4. Save to output_path - // 5. Return ThumbnailInfo - } - - fn supported_types(&self) -> Vec { - vec!["heif".to_string()] - } -} -``` - -## Dependencies - -The plugin uses the following Rust crates (compiled to WASM): - -- `libheif-rs`: HEIF decoding and encoding -- `image`: Image processing and thumbnail generation -- `kamadak-exif`: EXIF metadata parsing - -## Building - -### Prerequisites - -```bash -# Install WASM target -rustup target add wasm32-wasi - -# Install wasm-tools for optimization (provides strip functionality) -cargo install wasm-tools -``` - -### Build Process - -```bash -# Build the plugin -cargo build --target wasm32-wasi --release - -# Strip debug symbols to reduce size -wasm-tools strip target/wasm32-wasi/release/heif_support.wasm -o target/wasm32-wasi/release/heif_support.wasm - -# Copy to plugin directory -cp target/wasm32-wasi/release/heif_support.wasm . -``` - -### Size Optimization - -```bash -# Use wasm-opt for further optimization -wasm-opt -Oz heif_support.wasm -o heif_support_optimized.wasm -``` - -## Installation - -### Manual Installation - -```bash -# Copy plugin directory to Pinakes plugins directory -cp -r examples/plugins/heif-support ~/.config/pinakes/plugins/ -``` - -### Via API - -```bash -curl -X POST http://localhost:3000/api/v1/plugins/install \ - -H "Content-Type: application/json" \ - -d '{"source": "/path/to/heif-support"}' -``` - -### Via Plugin Manager - -```bash -pinakes plugin install /path/to/heif-support -``` - -## Configuration - -The plugin can be configured through the `config` section in `plugin.toml`: - -### EXIF Extraction - -- `extract_exif`: Enable EXIF metadata extraction (default: true) - -### Thumbnail Generation - -- `generate_thumbnails`: Enable thumbnail generation (default: true) -- `thumbnail_quality`: JPEG quality for thumbnails, 1-100 (default: 85) -- `thumbnail_format`: Output format - "jpeg", "png", or "webp" (default: "jpeg") - -### Resource Limits - -- `max_memory_mb`: Maximum memory the plugin can use in megabytes (default: 256, - set in `[capabilities]`) -- `max_width`: Maximum image width to process (default: 8192) -- `max_height`: Maximum image height to process (default: 8192) - -## Security - -### Capabilities - -- **Filesystem Read**: Only files being processed (via Pinakes) -- **Filesystem Write**: Thumbnail directory only -- **Network**: Disabled -- **Environment**: No access - -### Resource Limits - -- **Memory**: 256 MB maximum -- **CPU Time**: 30 seconds maximum per operation - -### Sandboxing - -The plugin runs in a WASM sandbox with: - -- No access to host filesystem beyond granted paths -- No network access -- No arbitrary code execution -- Memory and CPU time limits enforced by runtime - -## Performance - -### Typical Performance - -- **Metadata Extraction**: ~50-100ms for typical HEIF files -- **Thumbnail Generation**: ~200-500ms depending on source image size -- **Memory Usage**: 50-150 MB typical, 256 MB maximum - -### Optimization Tips - -1. Keep source images below 8192x8192 for best performance -2. Use JPEG thumbnail format for smaller file sizes -3. Adjust thumbnail quality vs file size tradeoff with `thumbnail_quality` - -## Error Handling - -The plugin handles: - -- **Corrupted Files**: Returns descriptive error -- **Unsupported Variants**: Gracefully skips unsupported HEIF features -- **Memory Limits**: Fails safely if image too large -- **Timeout**: Returns error if processing exceeds CPU time limit - -## Testing - -```bash -# Run unit tests -cargo test - -# Test with sample HEIF files -cargo test --test integration -- --nocapture -``` - -## Troubleshooting - -### Plugin Fails to Load - -- Check that `heif_support.wasm` exists in plugin directory -- Verify `plugin.toml` is valid TOML -- Check Pinakes logs for detailed error messages - -### Thumbnails Not Generated - -- Verify `generate_thumbnails = true` in config -- Check filesystem write permissions for thumbnail directory -- Ensure source image is below size limits - -### Out of Memory Errors - -- Reduce `max_width` and `max_height` in config -- Increase `max_memory_mb` if source images are large -- Check that source files aren't corrupted - -## Future Enhancements - -- Support for HEIF image sequences (burst photos) -- HDR metadata extraction -- Live Photo support -- AVIF format support (similar to HEIF) - -## License - -MIT diff --git a/examples/plugins/heif-support/plugin.toml b/examples/plugins/heif-support/plugin.toml deleted file mode 100644 index 6f30eb1..0000000 --- a/examples/plugins/heif-support/plugin.toml +++ /dev/null @@ -1,29 +0,0 @@ -[plugin] -name = "heif-support" -version = "1.0.0" -api_version = "1.0" -author = "Pinakes Contributors" -description = "HEIF/HEIC image format support for Pinakes" -homepage = "https://github.com/notashelf/pinakes" -license = "MIT" -kind = ["media_type", "metadata_extractor", "thumbnail_generator"] - -[plugin.binary] -wasm = "heif_support.wasm" - -[capabilities] -network = false -max_memory_mb = 256 -max_cpu_time_secs = 30 - -[capabilities.filesystem] -read = ["/media"] -write = ["/tmp/pinakes"] - -[config] -extract_exif = true -generate_thumbnails = true -thumbnail_quality = 85 -thumbnail_format = "jpeg" -max_width = 8192 -max_height = 8192 diff --git a/examples/plugins/markdown-metadata/README.md b/examples/plugins/markdown-metadata/README.md deleted file mode 100644 index 39044a4..0000000 --- a/examples/plugins/markdown-metadata/README.md +++ /dev/null @@ -1,108 +0,0 @@ -# Markdown Metadata Extractor Plugin - -This example plugin demonstrates how to create a metadata extractor plugin for -Pinakes. - -## Overview - -The Markdown Metadata Extractor enhances Pinakes' built-in markdown support by: - -- Parsing YAML and TOML frontmatter -- Extracting metadata from frontmatter fields -- Converting frontmatter tags to Pinakes media tags -- Extracting custom fields from frontmatter - -## Features - -- **Frontmatter Parsing**: Supports both YAML (`---`) and TOML (`+++`) - frontmatter formats -- **Tag Extraction**: Automatically extracts tags from frontmatter and applies - them to media items -- **Custom Fields**: Preserves all frontmatter fields as custom metadata -- **Configuration**: Configurable via `plugin.toml` config section - -## Frontmatter Example - -```markdown ---- -title: "My Document" -author: "John Doe" -date: "2024-01-15" -tags: ["documentation", "example", "markdown"] -category: "tutorials" -draft: false ---- - -# My Document - -Content goes here... -``` - -## Implementation - -The plugin implements the `MetadataExtractor` trait from `pinakes-plugin-api`: - -```rust -#[async_trait] -impl MetadataExtractor for MarkdownMetadataPlugin { - async fn extract_metadata(&self, path: &PathBuf) -> PluginResult { - // 1. Read the file - // 2. Parse frontmatter - // 3. Extract metadata fields - // 4. Return ExtractedMetadata - } - - fn supported_types(&self) -> Vec { - vec!["markdown".to_string()] - } -} -``` - -## Building - -The plugin is compiled to WebAssembly: - -```bash -cargo build --target wasm32-wasi --release -wasm-strip target/wasm32-wasi/release/markdown_metadata.wasm -cp target/wasm32-wasi/release/markdown_metadata.wasm . -``` - -## Installation - -```bash -# Copy plugin directory to Pinakes plugins directory -cp -r examples/plugins/markdown-metadata ~/.config/pinakes/plugins/ - -# Or via API -curl -X POST http://localhost:3000/api/v1/plugins/install \ - -H "Content-Type: application/json" \ - -d '{"source": "/path/to/markdown-metadata"}' -``` - -## Configuration - -The plugin can be configured through the `config` section in `plugin.toml`: - -- `extract_tags`: Extract tags from frontmatter (default: true) -- `parse_yaml`: Parse YAML frontmatter (default: true) -- `parse_toml`: Parse TOML frontmatter (default: true) -- `max_file_size`: Maximum file size to process in bytes (default: 10MB) - -## Security - -This plugin has minimal capabilities: - -- **Filesystem**: No write access, read access only to files being processed -- **Network**: Disabled -- **Environment**: No access - -## Testing - -```bash -cargo test -``` - -## License - -MIT diff --git a/examples/plugins/markdown-metadata/plugin.toml b/examples/plugins/markdown-metadata/plugin.toml deleted file mode 100644 index a558095..0000000 --- a/examples/plugins/markdown-metadata/plugin.toml +++ /dev/null @@ -1,25 +0,0 @@ -[plugin] -name = "markdown-metadata" -version = "1.0.0" -api_version = "1.0" -author = "Pinakes Contributors" -description = "Extract metadata from Markdown files with YAML frontmatter" -homepage = "https://github.com/notashelf/pinakes" -license = "MIT" -kind = ["metadata_extractor"] - -[plugin.binary] -wasm = "markdown_metadata.wasm" - -[capabilities] -network = false - -[capabilities.filesystem] -read = [] -write = [] - -[config] -extract_tags = true -parse_yaml = true -parse_toml = true -max_file_size = 10485760 diff --git a/examples/plugins/media-stats-ui/Cargo.lock b/examples/plugins/media-stats-ui/Cargo.lock deleted file mode 100644 index 882e3ef..0000000 Binary files a/examples/plugins/media-stats-ui/Cargo.lock and /dev/null differ diff --git a/examples/plugins/media-stats-ui/Cargo.toml b/examples/plugins/media-stats-ui/Cargo.toml deleted file mode 100644 index f3004cc..0000000 --- a/examples/plugins/media-stats-ui/Cargo.toml +++ /dev/null @@ -1,20 +0,0 @@ -[workspace] - -[package] -name = "media-stats-ui" -version = "1.0.0" -edition = "2024" -description = "Library statistics dashboard and tag manager, a UI-only Pinakes plugin" -license = "EUPL-1.2" - -[lib] -name = "media_stats_ui" -crate-type = ["cdylib"] - -[dependencies] -dlmalloc = { version = "0.2.12", features = ["global"] } - -[profile.release] -opt-level = "s" -lto = true -strip = true diff --git a/examples/plugins/media-stats-ui/pages/stats.json b/examples/plugins/media-stats-ui/pages/stats.json deleted file mode 100644 index 6f860c5..0000000 --- a/examples/plugins/media-stats-ui/pages/stats.json +++ /dev/null @@ -1,132 +0,0 @@ -{ - "id": "stats", - "title": "Library Statistics", - "route": "/plugins/media-stats-ui/stats", - "icon": "chart-bar", - "layout": { - "type": "tabs", - "default_tab": 0, - "tabs": [ - { - "label": "Overview", - "content": { - "type": "container", - "gap": 24, - "children": [ - { - "type": "heading", - "level": 2, - "content": "Library Statistics" - }, - { - "type": "text", - "content": "Live summary of your media library. Refreshes every 30 seconds.", - "variant": "secondary" - }, - { - "type": "card", - "title": "Summary", - "content": [ - { - "type": "description_list", - "data": "stats", - "horizontal": true - } - ] - }, - { - "type": "chart", - "chart_type": "bar", - "data": "type-breakdown", - "title": "Files by Type", - "x_axis_label": "Media Type", - "y_axis_label": "Count", - "height": 280 - } - ] - } - }, - { - "label": "Recent Files", - "content": { - "type": "container", - "gap": 16, - "children": [ - { - "type": "heading", - "level": 2, - "content": "Recently Added" - }, - { - "type": "data_table", - "data": "recent", - "sortable": true, - "filterable": true, - "page_size": 10, - "columns": [ - { - "key": "file_name", - "header": "Filename" - }, - { - "key": "title", - "header": "Title" - }, - { - "key": "media_type", - "header": "Type" - }, - { - "key": "file_size", - "header": "Size", - "data_type": "file_size" - }, - { - "key": "created_at", - "header": "Added", - "data_type": "date_time" - } - ] - } - ] - } - }, - { - "label": "Media Grid", - "content": { - "type": "container", - "gap": 16, - "children": [ - { - "type": "heading", - "level": 2, - "content": "Browse Media" - }, - { - "type": "media_grid", - "data": "recent", - "columns": 4, - "gap": 12 - } - ] - } - } - ] - }, - "data_sources": { - "stats": { - "type": "endpoint", - "path": "/api/v1/statistics", - "poll_interval": 30 - }, - "recent": { - "type": "endpoint", - "path": "/api/v1/media" - }, - "type-breakdown": { - "type": "transform", - "source": "stats", - "expression": "stats.media_by_type" - } - } -} diff --git a/examples/plugins/media-stats-ui/pages/tag-manager.json b/examples/plugins/media-stats-ui/pages/tag-manager.json deleted file mode 100644 index 30b3c2f..0000000 --- a/examples/plugins/media-stats-ui/pages/tag-manager.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "id": "tag-manager", - "title": "Tag Manager", - "route": "/plugins/media-stats-ui/tag-manager", - "icon": "tag", - "layout": { - "type": "tabs", - "default_tab": 0, - "tabs": [ - { - "label": "All Tags", - "content": { - "type": "container", - "gap": 16, - "children": [ - { - "type": "heading", - "level": 2, - "content": "Manage Tags" - }, - { - "type": "conditional", - "condition": { - "op": "eq", - "left": { "function": "len", "args": ["tags"] }, - "right": 0 - }, - "then": { - "type": "text", - "content": "No tags yet. Use the 'Create Tag' tab to add one.", - "variant": "secondary" - }, - "else": { - "type": "data_table", - "data": "tags", - "sortable": true, - "filterable": true, - "page_size": 20, - "columns": [ - { "key": "name", "header": "Tag Name" }, - { "key": "color", "header": "Color" }, - { "key": "item_count", "header": "Items", "data_type": "number" } - ] - } - } - ] - } - }, - { - "label": "Create Tag", - "content": { - "type": "container", - "gap": 24, - "children": [ - { - "type": "heading", - "level": 2, - "content": "Create New Tag" - }, - { - "type": "text", - "content": "Tags are used to organise media items. Choose a name and an optional colour.", - "variant": "secondary" - }, - { - "type": "form", - "submit_label": "Create Tag", - "submit_action": "create-tag", - "cancel_label": "Reset", - "fields": [ - { - "id": "name", - "label": "Tag Name", - "type": { "type": "text", "max_length": 64 }, - "required": true, - "placeholder": "e.g. favourite, to-watch, archived", - "help_text": "Must be unique. Alphanumeric characters, spaces, and hyphens.", - "validation": [ - { "type": "min_length", "value": 1 }, - { "type": "max_length", "value": 64 }, - { "type": "pattern", "regex": "^[a-zA-Z0-9 \\-]+$" } - ] - }, - { - "id": "color", - "label": "Colour", - "type": { - "type": "select", - "options": [ - { "value": "#ef4444", "label": "Red" }, - { "value": "#f97316", "label": "Orange" }, - { "value": "#eab308", "label": "Yellow" }, - { "value": "#22c55e", "label": "Green" }, - { "value": "#3b82f6", "label": "Blue" }, - { "value": "#8b5cf6", "label": "Purple" }, - { "value": "#ec4899", "label": "Pink" }, - { "value": "#6b7280", "label": "Grey" } - ] - }, - "required": false, - "default_value": "#3b82f6", - "help_text": "Optional accent colour shown beside the tag." - } - ] - } - ] - } - } - ] - }, - "data_sources": { - "tags": { - "type": "endpoint", - "path": "/api/v1/tags", - "poll_interval": 0 - } - }, - "actions": { - "create-tag": { - "method": "POST", - "path": "/api/v1/tags", - "success_message": "Tag created successfully!", - "error_message": "Failed to create tag: the name may already be in use." - } - } -} diff --git a/examples/plugins/media-stats-ui/plugin.toml b/examples/plugins/media-stats-ui/plugin.toml deleted file mode 100644 index 0e8116a..0000000 --- a/examples/plugins/media-stats-ui/plugin.toml +++ /dev/null @@ -1,39 +0,0 @@ -[plugin] -name = "media-stats-ui" -version = "1.0.0" -api_version = "1.0" -author = "Pinakes Contributors" -description = "Library statistics dashboard and tag manager UI plugin" -homepage = "https://github.com/notashelf/pinakes" -license = "EUPL-1.2" -kind = ["ui_page"] - -[plugin.binary] -wasm = "target/wasm32-unknown-unknown/release/media_stats_ui.wasm" - -[capabilities] -network = false - -[capabilities.filesystem] -read = [] -write = [] - -[ui] -required_endpoints = ["/api/v1/statistics", "/api/v1/media", "/api/v1/tags"] - -# UI pages -[[ui.pages]] -file = "pages/stats.json" - -[[ui.pages]] -file = "pages/tag-manager.json" - -# Widgets injected into host views -[[ui.widgets]] -id = "stats-badge" -target = "library_header" - -[ui.widgets.content] -type = "badge" -text = "Stats" -variant = "info" diff --git a/examples/plugins/media-stats-ui/src/lib.rs b/examples/plugins/media-stats-ui/src/lib.rs deleted file mode 100644 index c11a346..0000000 --- a/examples/plugins/media-stats-ui/src/lib.rs +++ /dev/null @@ -1,101 +0,0 @@ -//! Media Stats UI - Pinakes plugin -//! -//! A UI-only plugin that adds a library statistics dashboard and a tag manager -//! page. All UI definitions live in `pages/stats.json` and -//! `pages/tag-manager.json`; this WASM binary provides the minimum lifecycle -//! surface the host runtime requires. -//! -//! This plugin is kind = ["ui_page"]: no media-type, metadata, thumbnail, or -//! event-handler extension points are needed. The host will never call them, -//! but exporting them avoids linker warnings if the host performs capability -//! discovery via symbol inspection. - -#![no_std] - -extern crate alloc; - -use core::alloc::Layout; - -#[global_allocator] -static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc; - -#[panic_handler] -fn panic(_: &core::panic::PanicInfo) -> ! { - core::arch::wasm32::unreachable() -} - -// Host functions provided by the Pinakes runtime. -unsafe extern "C" { - // Write a result value back to the host (ptr + byte length). - fn host_set_result(ptr: i32, len: i32); - - // Emit a structured log message to the host logger. - // `level` mirrors tracing severity: 0=trace 1=debug 2=info 3=warn 4=error - fn host_log(level: i32, ptr: i32, len: i32); -} - -/// # Safety -/// -/// `json` is a valid slice; the host copies the bytes before -/// returning so there are no lifetime concerns. -fn set_response(json: &[u8]) { - unsafe { host_set_result(json.as_ptr() as i32, json.len() as i32) } -} - -/// # Safety -/// -/// Same as [`set_response`] -fn log_info(msg: &[u8]) { - unsafe { host_log(2, msg.as_ptr() as i32, msg.len() as i32) } -} - -/// Allocate a buffer for the host to write request data into. -/// -/// # Returns -/// -/// The byte offset of the allocation, or -1 on failure. -/// -/// # Safety -/// -/// Size is positive; Layout construction cannot fail for align=1. -#[unsafe(no_mangle)] -pub extern "C" fn alloc(size: i32) -> i32 { - if size <= 0 { - return 0; - } - unsafe { - let layout = Layout::from_size_align_unchecked(size as usize, 1); - let ptr = alloc::alloc::alloc(layout); - if ptr.is_null() { -1 } else { ptr as i32 } - } -} - -/// Called once after the plugin is loaded. Returns 0 on success. -#[unsafe(no_mangle)] -pub extern "C" fn initialize() -> i32 { - log_info(b"media-stats-ui: initialized"); - 0 -} - -/// Called before the plugin is unloaded. Returns 0 on success. -#[unsafe(no_mangle)] -pub extern "C" fn shutdown() -> i32 { - log_info(b"media-stats-ui: shutdown"); - 0 -} - -/// # Returns -/// -/// an empty JSON array; this plugin adds no custom media types. -#[unsafe(no_mangle)] -pub extern "C" fn supported_media_types(_ptr: i32, _len: i32) { - set_response(b"[]"); -} - -/// # Returns -/// -/// An empty JSON array; this plugin handles no event types. -#[unsafe(no_mangle)] -pub extern "C" fn interested_events(_ptr: i32, _len: i32) { - set_response(b"[]"); -}