iridis_url_scheme/
url_scheme.rs1use 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 #[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}