nodium_plugins/
plugins.rs1use 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 installer.lock().await.reload().await;
37 installer
38 }
39
40 pub async fn reload(&mut self) {
41 debug!("Reloading plugins");
42 let plugins_dir = Path::new(&self.install_location);
44 if !plugins_dir.exists() {
45 debug!("Plugins directory does not exist");
46 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 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}