fm/app/
previewer_plugins.rs1use std::{
2 ffi::{c_char, CString},
3 path::Path,
4};
5
6use anyhow::{bail, Result};
7use libloading::{Library, Symbol};
8
9use crate::modes::{Preview, PreviewBuilder};
10
11pub fn build_previewer_plugins(plugins: Vec<(String, String)>) -> Vec<(String, PreviewerPlugin)> {
13 let mut loaded_plugins = vec![];
14 for (name, path) in plugins.into_iter() {
15 match load_plugin(path) {
16 Ok(loaded_plugin) => loaded_plugins.push((name, loaded_plugin)),
17 Err(error) => {
18 crate::log_info!("Error loading plugin {error:?}");
19 crate::log_line!("Plugin {name} couldn't be loaded. See logs.")
20 }
21 }
22 }
23 loaded_plugins
24}
25
26fn load_plugin(path: String) -> Result<PreviewerPlugin> {
27 let _lib = unsafe { get_lib(path) }?;
28 let name = unsafe { get_name(&_lib) }?;
29 let is_match = unsafe { *(get_matcher(&_lib)?) };
30 let previewer = unsafe { *(get_previewer(&_lib))? };
31 Ok(PreviewerPlugin {
32 _lib,
33 name,
34 is_match,
35 previewer,
36 })
37}
38
39unsafe fn get_lib(path: String) -> Result<Library, libloading::Error> {
40 Library::new(&path)
41}
42
43unsafe fn get_name(lib: &Library) -> Result<String> {
44 let name_fn: Symbol<extern "C" fn() -> *mut c_char> = unsafe { lib.get(b"name")? };
45 let c_name = (name_fn)();
46 if !c_name.is_null() {
47 unsafe {
48 return Ok(CString::from_raw(c_name).into_string()?);
49 }
50 }
51 bail!("name string is null");
52}
53
54unsafe fn get_matcher(
55 lib: &Library,
56) -> Result<Symbol<'_, unsafe extern "C" fn(*mut c_char) -> bool>, libloading::Error> {
57 lib.get(b"is_match")
58}
59
60unsafe fn get_previewer(
61 lib: &Library,
62) -> Result<Symbol<'_, unsafe extern "C" fn(*mut c_char) -> *mut c_char>, libloading::Error> {
63 lib.get(b"preview")
64}
65
66pub fn try_build_plugin(path: &Path, plugins: &[(String, PreviewerPlugin)]) -> Option<Preview> {
68 let s_path = path.to_string_lossy().to_string();
69 for (_, plugin) in plugins.iter() {
70 let candidate = CString::new(s_path.clone()).ok()?.into_raw();
73 if unsafe { (plugin.is_match)(candidate) } {
74 let c_path = CString::new(path.display().to_string()).ok()?.into_raw();
75 let output = unsafe { plugin.get_output(c_path) }.ok()?;
76 return Some(PreviewBuilder::plugin_text(output, &plugin.name, path));
77 }
78 }
79 None
80}
81
82#[derive(Debug)]
83pub struct PreviewerPlugin {
84 _lib: Library,
85 name: String,
86 is_match: unsafe extern "C" fn(*mut c_char) -> bool,
87 previewer: unsafe extern "C" fn(*mut c_char) -> *mut c_char,
88}
89
90impl PreviewerPlugin {
91 unsafe fn get_output(&self, c_path: *mut c_char) -> Result<String> {
92 let output = (self.previewer)(c_path);
93 Ok(CString::from_raw(output).into_string()?)
94 }
95}