iridis_url_scheme/
url_scheme.rs

1use std::{collections::HashMap, path::PathBuf, sync::Arc};
2
3use crate::prelude::{thirdparty::libloading, *};
4
5pub struct UrlSchemeManager {
6    pub plugins: HashMap<String, Arc<RuntimeUrlScheme>>,
7}
8
9pub struct UrlSchemeManagerBuilder {
10    pub plugins: HashMap<String, Arc<RuntimeUrlScheme>>,
11}
12
13impl UrlSchemeManager {
14    pub fn new(plugins: HashMap<String, Arc<RuntimeUrlScheme>>) -> Self {
15        UrlSchemeManager { plugins }
16    }
17
18    #[allow(clippy::too_many_arguments)]
19    pub async fn load(
20        &self,
21        url: Url,
22        inputs: Inputs,
23        outputs: Outputs,
24        queries: Queries,
25        queryables: Queryables,
26        configuration: serde_yml::Value,
27        file_ext: Arc<FileExtManager>,
28    ) -> Result<RuntimeNode> {
29        let scheme = url.scheme();
30
31        let plugin = self
32            .plugins
33            .get(scheme)
34            .ok_or_eyre(format!("Plugin not found for scheme '{}'", scheme))?;
35
36        plugin
37            .load(
38                url,
39                inputs,
40                outputs,
41                queries,
42                queryables,
43                configuration,
44                file_ext,
45            )
46            .await
47    }
48}
49
50impl UrlSchemeManagerBuilder {
51    pub async fn new() -> Result<Self> {
52        Ok(Self {
53            plugins: HashMap::new(),
54        })
55    }
56
57    pub async fn load_statically_linked_plugin<T: UrlSchemePlugin + 'static>(
58        &mut self,
59    ) -> Result<()> {
60        let plugin = T::new().await?.wrap_err(format!(
61            "Failed to load static plugin '{}'",
62            std::any::type_name::<T>(),
63        ))?;
64
65        let plugin = Arc::new(RuntimeUrlScheme::StaticallyLinked(plugin));
66
67        for ext in &plugin.target() {
68            self.plugins.insert(ext.to_string(), plugin.clone());
69        }
70
71        tracing::debug!(
72            "Loaded statically linked plugin: {}",
73            std::any::type_name::<T>()
74        );
75
76        Ok(())
77    }
78
79    pub async fn load_dynamically_linked_plugin(&mut self, path: PathBuf) -> Result<()> {
80        match path.extension() {
81            Some(ext) => {
82                if ext == std::env::consts::DLL_EXTENSION {
83                    let path_buf = path.clone();
84                    let (library, constructor) = tokio::task::spawn_blocking(move || {
85                            let library = unsafe {
86                                #[cfg(target_family = "unix")]
87                                let library = libloading::os::unix::Library::open(
88                                    Some(path_buf.clone()),
89                                    libloading::os::unix::RTLD_NOW | libloading::os::unix::RTLD_GLOBAL,
90                                )
91                                .wrap_err(format!("Failed to load path {:?}", path_buf))?;
92
93                                #[cfg(not(target_family = "unix"))]
94                                let library = Library::new(path_buf.clone())
95                                    .wrap_err(format!("Failed to load path {:?}", path_buf))?;
96
97                                library
98                            };
99
100                            let constructor = unsafe {
101                                library
102                                .get::<*mut DynamicallyLinkedUrlSchemePluginInstance>(
103                                    b"IRIDIS_URL_SCHEME_PLUGIN",
104                                )
105                                .wrap_err(format!(
106                                    "Failed to load symbol 'IRIDIS_URL_SCHEME_PLUGIN' from cdylib {:?}",
107                                    path_buf
108                                ))?
109                                .read()
110                            };
111
112                            Ok::<_, eyre::Report>((library, constructor))
113                        })
114                        .await??;
115
116                    let plugin = Arc::new(RuntimeUrlScheme::DynamicallyLinked(
117                        DynamicallyLinkedUrlSchemePlugin {
118                            _library: library,
119                            handle: (constructor)().await?.wrap_err(format!(
120                                "Failed to load dynamically linked plugin '{:?}'",
121                                path,
122                            ))?,
123                        },
124                    ));
125
126                    for ext in &plugin.target() {
127                        self.plugins.insert(ext.to_string(), plugin.clone());
128                    }
129
130                    tracing::debug!(
131                        "Loaded dynamically linked plugin from path: {}",
132                        path.display()
133                    );
134
135                    Ok(())
136                } else {
137                    Err(eyre::eyre!("Extension '{:?}' is not supported", ext))
138                }
139            }
140            _ => Err(eyre::eyre!("Unsupported path '{:?}'", path)),
141        }
142    }
143}
144
145pub struct DynamicallyLinkedUrlSchemePlugin {
146    pub handle: Box<dyn UrlSchemePlugin>,
147
148    // Order is important !!! TODO: change to ManuallyDrop
149    #[cfg(not(target_family = "unix"))]
150    pub _library: libloading::Library,
151    #[cfg(target_family = "unix")]
152    pub _library: libloading::os::unix::Library,
153}
154
155pub enum RuntimeUrlScheme {
156    StaticallyLinked(Box<dyn UrlSchemePlugin>),
157    DynamicallyLinked(DynamicallyLinkedUrlSchemePlugin),
158}
159
160impl RuntimeUrlScheme {
161    pub fn target(&self) -> Vec<String> {
162        match self {
163            RuntimeUrlScheme::StaticallyLinked(plugin) => plugin.target(),
164            RuntimeUrlScheme::DynamicallyLinked(plugin) => plugin.handle.target(),
165        }
166    }
167
168    #[allow(clippy::too_many_arguments)]
169    pub async fn load(
170        &self,
171        url: Url,
172        inputs: Inputs,
173        outputs: Outputs,
174        queries: Queries,
175        queryables: Queryables,
176        configuration: serde_yml::Value,
177        file_ext: Arc<FileExtManager>,
178    ) -> Result<RuntimeNode> {
179        match self {
180            RuntimeUrlScheme::StaticallyLinked(plugin) => {
181                plugin
182                    .load(
183                        url,
184                        inputs,
185                        outputs,
186                        queries,
187                        queryables,
188                        configuration,
189                        file_ext,
190                    )
191                    .await?
192            }
193            RuntimeUrlScheme::DynamicallyLinked(plugin) => {
194                plugin
195                    .handle
196                    .load(
197                        url,
198                        inputs,
199                        outputs,
200                        queries,
201                        queryables,
202                        configuration,
203                        file_ext,
204                    )
205                    .await?
206            }
207        }
208    }
209}