nodium_plugins/
plugins.rs

1use crate::plugin_utils::{download, install};
2use crate::Registry;
3use dirs_next::document_dir;
4use libloading::{Library, Symbol};
5use log::{debug, error, info};
6use nodium_events::{NodiumEventType, NodiumEvents};
7use nodium_pdk::NodiuimPlugin;
8use serde_json::Value;
9use std::fs;
10use std::path::Path;
11use std::sync::Arc;
12use tokio::sync::Mutex;
13
14pub struct NodiumPlugins {
15    install_location: String,
16    event_bus: Arc<Mutex<NodiumEvents>>,
17    registry: Registry,
18}
19
20impl NodiumPlugins {
21    pub async fn new(event_bus: Arc<Mutex<NodiumEvents>>) -> Arc<Mutex<Self>> {
22        let doc_dir = document_dir().expect("Unable to get user's document directory");
23        let install_location = doc_dir.join("nodium").join("plugins");
24        debug!("Plugin install location: {:?}", install_location);
25        if !install_location.exists() {
26            debug!("Creating plugin directory");
27            fs::create_dir_all(&install_location).expect("Unable to create plugin directory");
28        }
29        let installer = Arc::new(Mutex::new(NodiumPlugins {
30            install_location: install_location.to_str().unwrap().to_string(),
31            event_bus: event_bus.clone(),
32            registry: Registry::new(),
33        }));
34
35        // load plugins in the plugins directory
36        installer.lock().await.reload().await;
37        installer
38    }
39
40    pub async fn reload(&mut self) {
41        debug!("Reloading plugins");
42        // load plugins in the plugins directory
43        let plugins_dir = Path::new(&self.install_location);
44        if !plugins_dir.exists() {
45            debug!("Plugins directory does not exist");
46            // create plugins directory
47            match fs::create_dir_all(&plugins_dir) {
48                Ok(_) => {
49                    debug!("Plugins directory created successfully");
50                }
51                Err(e) => {
52                    error!("Error creating plugins directory: {}", e);
53                    return;
54                }
55            }
56        }
57        let folders = match fs::read_dir(&plugins_dir) {
58            Ok(folders) => folders,
59            Err(e) => {
60                error!("Error reading plugins directory: {}", e);
61                return;
62            }
63        };
64
65        for entry in folders {
66            let entry = match entry {
67                Ok(entry) => entry,
68                Err(e) => {
69                    error!("Error reading plugin directory: {}", e);
70                    continue;
71                }
72            };
73            let path = entry.path();
74            debug!("Plugin path: {:?}", path);
75            if path.is_dir() {
76                let plugin_name = path.file_name().unwrap().to_str().unwrap();
77                let plugin_version = path.file_name().unwrap().to_str().unwrap();
78                debug!(
79                    "Plugin name and version: {} {}",
80                    plugin_name, plugin_version
81                );
82                match self.register(plugin_name, plugin_version, true) {
83                    Ok(_) => {
84                        info!("Plugin registered successfully");
85                    }
86                    Err(e) => {
87                        info!("Error registering plugin: {} ... trying to build", e);
88                        // get folder name of last folder in path
89                        match install(
90                            path.file_name().unwrap().to_str().unwrap(),
91                            "",
92                            &self.install_location,
93                            true,
94                        )
95                        .await
96                        {
97                            Ok(_) => {
98                                info!("Plugin installed successfully");
99                            }
100                            Err(e) => {
101                                error!("Error installing plugin: {}", e);
102                            }
103                        }
104                    }
105                }
106            }
107        }
108    }
109
110    pub async fn listen(this: Arc<Mutex<Self>>) {
111        let plugins_clone = this.clone();
112        let plugins_clone_callback = plugins_clone.clone();
113
114        let event_bus_guard = plugins_clone.lock().await.event_bus.clone();
115
116        event_bus_guard
117            .lock()
118            .await
119            .register(
120                &NodiumEventType::AddPlugin.to_string(),
121                Box::new(move |payload: String| {
122                    let plugins_clone = plugins_clone_callback.clone();
123
124                    tokio::spawn(async move {
125                        let mut plugins_guard = plugins_clone.lock().await;
126                        let install_location = plugins_guard.install_location.clone();
127                        match plugins_guard
128                            .plugin_install(payload, install_location.clone())
129                            .await
130                        {
131                            Ok((crate_name, crate_version)) => {
132                                let mut plugins_guard = plugins_clone.lock().await;
133                                match plugins_guard.register(&crate_name, &crate_version, false) {
134                                    Ok(_) => {
135                                        info!("Plugin registered successfully");
136                                    }
137                                    Err(e) => {
138                                        error!("Error registering plugin: {}", e);
139                                    }
140                                }
141                            }
142                            Err(e) => {
143                                error!("Error installing crate: {}", e);
144                            }
145                        }
146                    });
147                }) as Box<dyn Fn(String) + Send + Sync>,
148            )
149            .await;
150    }
151
152    async fn plugin_install(
153        &mut self,
154        payload: String,
155        install_location: String,
156    ) -> Result<(String, String), Box<dyn std::error::Error + Send + Sync>> {
157        let install_location = install_location.clone();
158        debug!("Installing crate {}", payload);
159        let data: Value = serde_json::from_str(&payload).unwrap();
160        debug!("Handling event: {}", payload);
161        debug!("Event data: {:?}", data);
162
163        let crate_version = data
164            .get("crate_version")
165            .and_then(Value::as_str)
166            .unwrap()
167            .to_string();
168        let crate_name = data
169            .get("crate_name")
170            .and_then(Value::as_str)
171            .unwrap()
172            .to_string();
173
174        match download(&crate_name, &crate_version, &install_location).await {
175            Ok(_) => {
176                info!("Crate downloaded successfully");
177                match install(&crate_name, &crate_version, &install_location, false).await {
178                    Ok(_) => {
179                        info!("Crate installed successfully");
180                        Ok((crate_name, crate_version))
181                    }
182                    Err(e) => {
183                        error!("Error installing crate: {}", e);
184                        Err(e)
185                    }
186                }
187            }
188            Err(e) => {
189                error!("Error downloading crate: {}", e);
190                Err(e)
191            }
192        }
193    }
194
195    fn register(
196        &mut self,
197        crate_name: &str,
198        crate_version: &str,
199        is_local: bool,
200    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
201        let folder_name = if is_local {
202            crate_name.to_string()
203        } else {
204            format!("{}-{}", crate_name, crate_version)
205        };
206        let lib_path = Path::new(&self.install_location)
207            .join(folder_name)
208            .join("target")
209            .join("release")
210            .join(if cfg!(windows) { "lib" } else { "" })
211            .join(format!(
212                "lib{}{}",
213                crate_name,
214                if cfg!(windows) {
215                    ".dll"
216                } else if cfg!(unix) {
217                    ".so"
218                } else {
219                    return Err(Box::new(std::io::Error::new(
220                        std::io::ErrorKind::Other,
221                        "Unsupported platform",
222                    )));
223                }
224            ));
225
226        let lib = unsafe { Library::new(&lib_path) }?;
227        let plugin: Symbol<unsafe extern "C" fn() -> Box<dyn NodiuimPlugin>> =
228            unsafe { lib.get(b"plugin")? };
229
230        let plugin = unsafe { plugin() };
231        let plugin_name = plugin.name();
232        debug!("Registering plugin: {}", plugin_name);
233        let event_bus = self.event_bus.clone();
234        self.registry.register_plugin(event_bus, plugin);
235        Ok(())
236    }
237}