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        let mut manager = FileExtManagerBuilder {
47            plugins: HashMap::new(),
48        };
49
50        manager
51            .load_statically_linked_plugin::<DefaultFileExtPlugin>()
52            .await?;
53
54        Ok(manager)
55    }
56
57    pub async fn load_statically_linked_plugin<T: FileExtPlugin + '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(RuntimeFileExt::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 DynamicallyLinkedFileExtPluginInstance>(
93                                    b"FLARROW_FILE_EXT_PLUGIN",
94                                )
95                                .wrap_err(format!(
96                                    "Failed to load symbol 'FLARROW_FILE_EXT_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(RuntimeFileExt::DynamicallyLinked(
107                        DynamicallyLinkedFileExtPlugin {
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 DynamicallyLinkedFileExtPlugin {
136    pub handle: Box<dyn FileExtPlugin>,
137    pub _library: Library, // Order is important !!! TODO: change to ManuallyDrop
138}
139
140pub enum RuntimeFileExt {
141    StaticallyLinked(Box<dyn FileExtPlugin>),
142    DynamicallyLinked(DynamicallyLinkedFileExtPlugin),
143}
144
145impl RuntimeFileExt {
146    pub fn target(&self) -> Vec<String> {
147        match self {
148            RuntimeFileExt::StaticallyLinked(plugin) => plugin.target(),
149            RuntimeFileExt::DynamicallyLinked(plugin) => plugin.handle.target(),
150        }
151    }
152
153    pub async fn load(
154        &self,
155        path: PathBuf,
156        inputs: Inputs,
157        outputs: Outputs,
158        queries: Queries,
159        queryables: Queryables,
160        configuration: serde_yml::Value,
161    ) -> Result<RuntimeNode> {
162        match self {
163            RuntimeFileExt::StaticallyLinked(plugin) => {
164                plugin
165                    .load(path, inputs, outputs, queries, queryables, configuration)
166                    .await?
167            }
168            RuntimeFileExt::DynamicallyLinked(plugin) => {
169                plugin
170                    .handle
171                    .load(path, inputs, outputs, queries, queryables, configuration)
172                    .await?
173            }
174        }
175    }
176}