Skip to main content

mangofetch_core/core/manager/
plugin_manager.rs

1use crate::core::manager::plugin_host::CorePluginHost;
2use anyhow::Context;
3use libloading::{Library, Symbol};
4use mangofetch_plugin_sdk::MangoFetchPlugin;
5use std::collections::HashMap;
6use std::path::PathBuf;
7use std::sync::Arc;
8
9type PluginInitFn = unsafe extern "C" fn() -> *mut dyn MangoFetchPlugin;
10
11pub struct PluginInstance {
12    pub plugin: Box<dyn MangoFetchPlugin>,
13    _library: Library, // Keep library in memory
14}
15
16pub struct PluginManager {
17    plugins: HashMap<String, PluginInstance>,
18    host: Arc<CorePluginHost>,
19}
20
21impl PluginManager {
22    pub fn new() -> Self {
23        Self {
24            plugins: HashMap::new(),
25            host: Arc::new(CorePluginHost),
26        }
27    }
28
29    pub async fn load_plugins(&mut self) -> anyhow::Result<()> {
30        let plugins_dir = crate::core::paths::app_data_dir()
31            .context("Failed to get app data directory")?
32            .join("plugins");
33
34        if !plugins_dir.exists() {
35            std::fs::create_dir_all(&plugins_dir)?;
36            return Ok(());
37        }
38
39        for entry in std::fs::read_dir(plugins_dir)? {
40            let entry = entry?;
41            let path = entry.path();
42
43            if path.is_file() {
44                if let Some(ext) = path.extension() {
45                    let ext_str = ext.to_string_lossy();
46                    if ext_str == "dll" || ext_str == "so" || ext_str == "dylib" {
47                        if let Err(e) = self.load_single_plugin(path.clone()) {
48                            tracing::error!("Failed to load plugin from {:?}: {}", path, e);
49                        }
50                    }
51                }
52            }
53        }
54
55        Ok(())
56    }
57
58    fn load_single_plugin(&mut self, path: PathBuf) -> anyhow::Result<()> {
59        unsafe {
60            let lib = Library::new(&path).context("Failed to load dynamic library")?;
61
62            let init_fn: Symbol<PluginInitFn> = lib
63                .get(b"mangofetch_plugin_init")
64                .context("Failed to find mangofetch_plugin_init symbol")?;
65
66            let plugin_ptr = init_fn();
67            let mut plugin = Box::from_raw(plugin_ptr);
68
69            plugin
70                .initialize(self.host.clone())
71                .context("Failed to initialize plugin")?;
72
73            let id = plugin.id().to_string();
74            tracing::info!("Loaded plugin: {} ({}) from {:?}", plugin.name(), id, path);
75
76            self.plugins.insert(
77                id,
78                PluginInstance {
79                    plugin,
80                    _library: lib,
81                },
82            );
83        }
84
85        Ok(())
86    }
87
88    pub fn get_plugin(&self, id: &str) -> Option<&dyn MangoFetchPlugin> {
89        self.plugins.get(id).map(|p| p.plugin.as_ref())
90    }
91
92    pub fn list_plugins(&self) -> Vec<(&String, &str)> {
93        self.plugins
94            .iter()
95            .map(|(id, p)| (id, p.plugin.name()))
96            .collect()
97    }
98}
99
100impl Default for PluginManager {
101    fn default() -> Self {
102        Self::new()
103    }
104}