flarrow_file_ext/
file_ext.rs1use 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, }
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}