-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add plugin system for extensibility #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| using Booky.Plugins; | ||
|
|
||
| namespace Booky.Plugins.Examples; | ||
|
|
||
| /// <summary> | ||
| /// Example format plugin template. | ||
| /// Shows how to add support for additional ebook formats. | ||
| /// | ||
| /// To create a real plugin: | ||
| /// 1. Create a new .NET Class Library project | ||
| /// 2. Copy this file as a starting point | ||
| /// 3. Implement your own conversion logic | ||
| /// 4. Build and copy the DLL to Booky's Plugins folder | ||
| /// </summary> | ||
| public class ExampleFormatPlugin : IFormatPlugin | ||
| { | ||
| public string Id => "example-format"; | ||
| public string Name => "Example Format Plugin"; | ||
| public string Description => "Template for format plugins (adds new input format support)"; | ||
| public string Version => "1.0.0"; | ||
|
|
||
| public string[] SupportedExtensions => new[] { ".example" }; | ||
|
|
||
| public void Initialize() | ||
| { | ||
| // Called when plugin is loaded | ||
| } | ||
|
|
||
| public void Shutdown() | ||
| { | ||
| // Called when plugin is unloaded | ||
| } | ||
|
|
||
| public string GetFileTypeDescription(string extension) | ||
| { | ||
| return extension.ToLowerInvariant() switch | ||
| { | ||
| ".example" => "Example Ebook File", | ||
| _ => "Unknown File" | ||
| }; | ||
| } | ||
|
|
||
| public async Task<PluginResult> ConvertToEpubAsync(string inputPath, string outputPath, string title, string author) | ||
| { | ||
| // This is where you would implement your conversion logic | ||
| // | ||
| // For example: | ||
| // 1. Read and parse the input file | ||
| // 2. Extract content, images, metadata | ||
| // 3. Create an EPUB file at outputPath | ||
| // 4. Return PluginResult.Ok(outputPath) on success | ||
| // | ||
| // You can use the same EPUB creation approach as Booky's built-in converter, | ||
| // or use a library like EpubSharp, VersOne.Epub, etc. | ||
|
|
||
| await Task.CompletedTask; // Placeholder for async operations | ||
|
|
||
| // Default: return failure (not implemented) | ||
| return PluginResult.Fail("This is an example plugin - conversion not implemented"); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| using Booky.Plugins; | ||
|
|
||
| namespace Booky.Plugins.Examples; | ||
|
|
||
| /// <summary> | ||
| /// Example pre-conversion plugin template. | ||
| /// This is a SKELETON - it does not contain any actual DRM removal code. | ||
| /// | ||
| /// To create a real plugin: | ||
| /// 1. Create a new .NET Class Library project | ||
| /// 2. Copy this file as a starting point | ||
| /// 3. Implement your own processing logic | ||
| /// 4. Build and copy the DLL to Booky's Plugins folder | ||
| /// </summary> | ||
| public class ExamplePreConversionPlugin : IPreConversionPlugin | ||
| { | ||
| public string Id => "example-pre-conversion"; | ||
| public string Name => "Example Pre-Conversion Plugin"; | ||
| public string Description => "Template for pre-conversion plugins (e.g., file preprocessing)"; | ||
| public string Version => "1.0.0"; | ||
|
|
||
| public string[] SupportedExtensions => new[] { ".mobi", ".azw", ".azw3" }; | ||
|
|
||
| public void Initialize() | ||
| { | ||
| // Called when plugin is loaded | ||
| // Initialize any resources here | ||
| } | ||
|
|
||
| public void Shutdown() | ||
| { | ||
| // Called when plugin is unloaded | ||
| // Clean up resources here | ||
| } | ||
|
|
||
| public async Task<PluginResult> ProcessAsync(string inputPath, string outputPath) | ||
| { | ||
| // This is where you would implement your processing logic | ||
| // | ||
| // For example: | ||
| // 1. Read the input file | ||
| // 2. Process it (decrypt, normalize, etc.) | ||
| // 3. Write the result to outputPath | ||
| // 4. Return PluginResult.Ok(outputPath) on success | ||
| // | ||
| // If you can't process this file, return PluginResult.Skip() | ||
| // If there's an error, return PluginResult.Fail("error message") | ||
|
|
||
| await Task.CompletedTask; // Placeholder for async operations | ||
|
|
||
| // Default: skip processing (pass through unchanged) | ||
| return PluginResult.Skip(); | ||
|
Comment on lines
+36
to
+52
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,105 @@ | ||
| namespace Booky.Plugins; | ||
|
|
||
| /// <summary> | ||
| /// Base interface for all Booky plugins. | ||
| /// Plugins are loaded from DLLs in the Plugins folder. | ||
| /// </summary> | ||
| public interface IBookyPlugin | ||
| { | ||
| /// <summary> | ||
| /// Unique identifier for the plugin | ||
| /// </summary> | ||
| string Id { get; } | ||
|
|
||
| /// <summary> | ||
| /// Display name shown in UI | ||
| /// </summary> | ||
| string Name { get; } | ||
|
|
||
| /// <summary> | ||
| /// Description of what the plugin does | ||
| /// </summary> | ||
| string Description { get; } | ||
|
|
||
| /// <summary> | ||
| /// Plugin version | ||
| /// </summary> | ||
| string Version { get; } | ||
|
|
||
| /// <summary> | ||
| /// Called when the plugin is loaded | ||
| /// </summary> | ||
| void Initialize(); | ||
|
|
||
| /// <summary> | ||
| /// Called when the plugin is unloaded | ||
| /// </summary> | ||
| void Shutdown(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Plugin that processes files before conversion. | ||
| /// Use case: DRM removal, format normalization, etc. | ||
| /// </summary> | ||
| public interface IPreConversionPlugin : IBookyPlugin | ||
| { | ||
| /// <summary> | ||
| /// File extensions this plugin can process (e.g., ".mobi", ".azw") | ||
| /// </summary> | ||
| string[] SupportedExtensions { get; } | ||
|
|
||
| /// <summary> | ||
| /// Process a file before conversion. | ||
| /// </summary> | ||
| /// <param name="inputPath">Path to the input file</param> | ||
| /// <param name="outputPath">Path where processed file should be written</param> | ||
| /// <returns>PluginResult with Success=true and OutputPath set if file was processed, | ||
| /// or PluginResult.Skip() if this plugin chose not to process the file</returns> | ||
| Task<PluginResult> ProcessAsync(string inputPath, string outputPath); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Plugin that processes files after conversion. | ||
| /// Use case: Metadata enhancement, cover generation, etc. | ||
| /// </summary> | ||
| public interface IPostConversionPlugin : IBookyPlugin | ||
| { | ||
| /// <summary> | ||
| /// Process a file after conversion to EPUB. | ||
| /// </summary> | ||
| /// <param name="epubPath">Path to the converted EPUB file</param> | ||
| /// <returns>Result of the processing</returns> | ||
| Task<PluginResult> ProcessAsync(string epubPath); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Plugin that adds support for additional input formats. | ||
| /// </summary> | ||
| public interface IFormatPlugin : IBookyPlugin | ||
| { | ||
| /// <summary> | ||
| /// File extensions this plugin handles (e.g., ".azw3", ".kfx") | ||
| /// </summary> | ||
| string[] SupportedExtensions { get; } | ||
|
|
||
| /// <summary> | ||
| /// Description for the file type (e.g., "Kindle AZW3 File") | ||
| /// </summary> | ||
| string GetFileTypeDescription(string extension); | ||
|
|
||
| /// <summary> | ||
| /// Convert the input file to EPUB. | ||
| /// </summary> | ||
| Task<PluginResult> ConvertToEpubAsync(string inputPath, string outputPath, string title, string author); | ||
| } | ||
|
|
||
| public class PluginResult | ||
| { | ||
| public bool Success { get; set; } | ||
| public string? ErrorMessage { get; set; } | ||
| public string? OutputPath { get; set; } | ||
|
|
||
| public static PluginResult Ok(string? outputPath = null) => new() { Success = true, OutputPath = outputPath }; | ||
| public static PluginResult Fail(string error) => new() { Success = false, ErrorMessage = error }; | ||
| public static PluginResult Skip() => new() { Success = true }; // Plugin chose not to process | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,83 @@ | ||||||
| # Booky Plugins | ||||||
|
|
||||||
| Booky supports plugins to extend its functionality. Plugins are .NET DLLs placed in this folder. | ||||||
|
|
||||||
| ## Plugin Types | ||||||
|
|
||||||
| | Interface | Purpose | Example Use Case | | ||||||
| |-----------|---------|------------------| | ||||||
| | `IPreConversionPlugin` | Process files before conversion | DRM removal, format normalization | | ||||||
| | `IPostConversionPlugin` | Process files after conversion | Metadata enhancement, cover generation | | ||||||
| | `IFormatPlugin` | Add support for new input formats | AZW3, KFX support | | ||||||
|
|
||||||
| ## Creating a Plugin | ||||||
|
|
||||||
| 1. Create a .NET Class Library project targeting `net8.0-windows` | ||||||
| 2. Reference `Booky.exe` or copy `IBookyPlugin.cs` interfaces | ||||||
| 3. Implement one or more plugin interfaces | ||||||
| 4. Build and copy the DLL to the `Plugins` folder | ||||||
|
|
||||||
| ### Example: Pre-Conversion Plugin | ||||||
|
|
||||||
| ```csharp | ||||||
| using Booky.Plugins; | ||||||
|
|
||||||
| public class MyDrmPlugin : IPreConversionPlugin | ||||||
| { | ||||||
| public string Id => "my-drm-plugin"; | ||||||
| public string Name => "My DRM Plugin"; | ||||||
| public string Description => "Removes DRM from ebooks"; | ||||||
| public string Version => "1.0.0"; | ||||||
| public string[] SupportedExtensions => new[] { ".mobi", ".azw" }; | ||||||
|
|
||||||
| public void Initialize() { } | ||||||
| public void Shutdown() { } | ||||||
|
|
||||||
| public async Task<PluginResult> ProcessAsync(string inputPath, string outputPath) | ||||||
| { | ||||||
| // Your DRM removal logic here | ||||||
| // Write the processed file to outputPath | ||||||
|
|
||||||
| return PluginResult.Ok(outputPath); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ### Example: Format Plugin | ||||||
|
|
||||||
| ```csharp | ||||||
| using Booky.Plugins; | ||||||
|
|
||||||
| public class Azw3Plugin : IFormatPlugin | ||||||
| { | ||||||
| public string Id => "azw3-plugin"; | ||||||
| public string Name => "AZW3 Support"; | ||||||
| public string Description => "Adds AZW3 format support"; | ||||||
| public string Version => "1.0.0"; | ||||||
| public string[] SupportedExtensions => new[] { ".azw3" }; | ||||||
|
|
||||||
| public void Initialize() { } | ||||||
| public void Shutdown() { } | ||||||
|
|
||||||
| public string GetFileTypeDescription(string extension) => "Kindle AZW3 File"; | ||||||
|
|
||||||
| public async Task<PluginResult> ConvertToEpubAsync(string inputPath, string outputPath, string title, string author) | ||||||
| { | ||||||
| // Your conversion logic here | ||||||
|
|
||||||
| return PluginResult.Ok(outputPath); | ||||||
| } | ||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
| ## Plugin Guidelines | ||||||
|
|
||||||
| - Plugins should fail gracefully and not crash Booky | ||||||
| - Return `PluginResult.Skip()` if the plugin can't process a file | ||||||
|
||||||
| - Return `PluginResult.Skip()` if the plugin can't process a file | |
| - Return `PluginResult.Skip()` when the plugin chooses not to process a file (Booky will continue using the original, unmodified file) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The plugin service is never unloaded when the application closes. This means plugin Shutdown methods are never called, potentially leaving resources unreleased. Consider adding a handler for window closing event in MainWindow that calls _pluginService.UnloadPlugins() to ensure proper cleanup.