fm/app/
previewer_plugins.rs

1use 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
11/// Build an hashmap of name and preview builder from an hashmap of name and path.
12pub fn build_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
66/// Preview the file if any loaded plugin is able to.
67pub fn try_build(path: &Path, plugins: &[(String, PreviewerPlugin)]) -> Option<Preview> {
68    let s_path = path.to_string_lossy().to_string();
69    let candidate = CString::new(s_path).ok()?.into_raw();
70    for (_, plugin) in plugins.iter() {
71        if unsafe { (plugin.is_match)(candidate) } {
72            let c_path = CString::new(path.display().to_string()).ok()?.into_raw();
73            let output = unsafe { plugin.get_output(c_path) }.ok()?;
74            return Some(PreviewBuilder::plugin_text(output, &plugin.name, path));
75        }
76    }
77    None
78}
79
80#[derive(Debug)]
81pub struct PreviewerPlugin {
82    _lib: Library,
83    name: String,
84    is_match: unsafe extern "C" fn(*mut c_char) -> bool,
85    previewer: unsafe extern "C" fn(*mut c_char) -> *mut c_char,
86}
87
88impl PreviewerPlugin {
89    unsafe fn get_output(&self, c_path: *mut c_char) -> Result<String> {
90        let output = (self.previewer)(c_path);
91        Ok(CString::from_raw(output).into_string()?)
92    }
93}