flarrow_url_scheme/
url_scheme.rs

1use std::{collections::HashMap, path::PathBuf, sync::Arc};
2
3use crate::prelude::{thirdparty::libloading::Library, *};
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                                Library::new(path_buf.clone())
87                                    .wrap_err(format!("Failed to load path {:?}", path_buf))?
88                            };
89
90                            let constructor = unsafe {
91                                library
92                                .get::<*mut DynamicallyLinkedUrlSchemePluginInstance>(
93                                    b"FLARROW_URL_SCHEME_PLUGIN",
94                                )
95                                .wrap_err(format!(
96                                    "Failed to load symbol 'FLARROW_URL_SCHEME_PLUGIN' from cdylib {:?}",
97                                    path_buf
98                                ))?
99                                .read()
100                            };
101
102                            Ok::<_, eyre::Report>((library, constructor))
103                        })
104                        .await??;
105
106                    let plugin = Arc::new(RuntimeUrlScheme::DynamicallyLinked(
107                        DynamicallyLinkedUrlSchemePlugin {
108                            _library: library,
109                            handle: (constructor)().await?.wrap_err(format!(
110                                "Failed to load dynamically linked plugin '{:?}'",
111                                path,
112                            ))?,
113                        },
114                    ));
115
116                    for ext in &plugin.target() {
117                        self.plugins.insert(ext.to_string(), plugin.clone());
118                    }
119
120                    tracing::debug!(
121                        "Loaded dynamically linked plugin from path: {}",
122                        path.display()
123                    );
124
125                    Ok(())
126                } else {
127                    Err(eyre::eyre!("Extension '{:?}' is not supported", ext))
128                }
129            }
130            _ => Err(eyre::eyre!("Unsupported path '{:?}'", path)),
131        }
132    }
133}
134
135pub struct DynamicallyLinkedUrlSchemePlugin {
136    pub handle: Box<dyn UrlSchemePlugin>,
137    pub _library: Library, // Order is important !!! TODO: change to ManuallyDrop
138}
139
140pub enum RuntimeUrlScheme {
141    StaticallyLinked(Box<dyn UrlSchemePlugin>),
142    DynamicallyLinked(DynamicallyLinkedUrlSchemePlugin),
143}
144
145impl RuntimeUrlScheme {
146    pub fn target(&self) -> Vec<String> {
147        match self {
148            RuntimeUrlScheme::StaticallyLinked(plugin) => plugin.target(),
149            RuntimeUrlScheme::DynamicallyLinked(plugin) => plugin.handle.target(),
150        }
151    }
152
153    #[allow(clippy::too_many_arguments)]
154    pub async fn load(
155        &self,
156        url: Url,
157        inputs: Inputs,
158        outputs: Outputs,
159        queries: Queries,
160        queryables: Queryables,
161        configuration: serde_yml::Value,
162        file_ext: Arc<FileExtManager>,
163    ) -> Result<RuntimeNode> {
164        match self {
165            RuntimeUrlScheme::StaticallyLinked(plugin) => {
166                plugin
167                    .load(
168                        url,
169                        inputs,
170                        outputs,
171                        queries,
172                        queryables,
173                        configuration,
174                        file_ext,
175                    )
176                    .await?
177            }
178            RuntimeUrlScheme::DynamicallyLinked(plugin) => {
179                plugin
180                    .handle
181                    .load(
182                        url,
183                        inputs,
184                        outputs,
185                        queries,
186                        queryables,
187                        configuration,
188                        file_ext,
189                    )
190                    .await?
191            }
192        }
193    }
194}