iridis_file_ext/
file_ext.rs

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