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