flarrow_file_ext/
file_ext.rs

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