lla_plugin_interface/
lib.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3use std::path::PathBuf;
4
5pub mod proto {
6    #[cfg(not(feature = "regenerate-protobuf"))]
7    include!("generated/mod.rs");
8
9    #[cfg(feature = "regenerate-protobuf")]
10    include!(concat!(env!("OUT_DIR"), "/lla_plugin.rs"));
11}
12
13pub trait Plugin: Default {
14    fn handle_raw_request(&mut self, request: &[u8]) -> Vec<u8>;
15}
16
17#[derive(Clone, Serialize, Deserialize)]
18pub struct DecoratedEntry {
19    pub path: PathBuf,
20    pub metadata: EntryMetadata,
21    pub custom_fields: HashMap<String, String>,
22}
23
24#[derive(Clone, Serialize, Deserialize)]
25pub struct EntryMetadata {
26    pub size: u64,
27    pub modified: u64,
28    pub accessed: u64,
29    pub created: u64,
30    pub is_dir: bool,
31    pub is_file: bool,
32    pub is_symlink: bool,
33    pub permissions: u32,
34    pub uid: u32,
35    pub gid: u32,
36}
37
38#[derive(Serialize, Deserialize)]
39pub enum PluginRequest {
40    GetName,
41    GetVersion,
42    GetDescription,
43    GetSupportedFormats,
44    Decorate(DecoratedEntry),
45    FormatField(DecoratedEntry, String),
46    PerformAction(String, Vec<String>),
47}
48
49#[derive(Serialize, Deserialize)]
50pub enum PluginResponse {
51    Name(String),
52    Version(String),
53    Description(String),
54    SupportedFormats(Vec<String>),
55    Decorated(DecoratedEntry),
56    FormattedField(Option<String>),
57    ActionResult(Result<(), String>),
58    Error(String),
59}
60
61impl From<EntryMetadata> for proto::EntryMetadata {
62    fn from(meta: EntryMetadata) -> Self {
63        proto::EntryMetadata {
64            size: meta.size,
65            modified: meta.modified,
66            accessed: meta.accessed,
67            created: meta.created,
68            is_dir: meta.is_dir,
69            is_file: meta.is_file,
70            is_symlink: meta.is_symlink,
71            permissions: meta.permissions,
72            uid: meta.uid,
73            gid: meta.gid,
74        }
75    }
76}
77
78impl From<proto::EntryMetadata> for EntryMetadata {
79    fn from(meta: proto::EntryMetadata) -> Self {
80        EntryMetadata {
81            size: meta.size,
82            modified: meta.modified,
83            accessed: meta.accessed,
84            created: meta.created,
85            is_dir: meta.is_dir,
86            is_file: meta.is_file,
87            is_symlink: meta.is_symlink,
88            permissions: meta.permissions,
89            uid: meta.uid,
90            gid: meta.gid,
91        }
92    }
93}
94
95impl From<DecoratedEntry> for proto::DecoratedEntry {
96    fn from(entry: DecoratedEntry) -> Self {
97        proto::DecoratedEntry {
98            path: entry.path.to_string_lossy().to_string(),
99            metadata: Some(entry.metadata.into()),
100            custom_fields: entry.custom_fields,
101        }
102    }
103}
104
105impl TryFrom<proto::DecoratedEntry> for DecoratedEntry {
106    type Error = std::io::Error;
107
108    fn try_from(entry: proto::DecoratedEntry) -> Result<Self, Self::Error> {
109        Ok(DecoratedEntry {
110            path: PathBuf::from(entry.path),
111            metadata: entry.metadata.unwrap_or_default().into(),
112            custom_fields: entry.custom_fields,
113        })
114    }
115}
116
117#[repr(C)]
118pub struct RawBuffer {
119    pub ptr: *mut u8,
120    pub len: usize,
121    pub capacity: usize,
122}
123
124impl RawBuffer {
125    pub fn from_vec(mut vec: Vec<u8>) -> Self {
126        let ptr = vec.as_mut_ptr();
127        let len = vec.len();
128        let capacity = vec.capacity();
129        std::mem::forget(vec);
130        RawBuffer { ptr, len, capacity }
131    }
132
133    pub unsafe fn into_vec(self) -> Vec<u8> {
134        Vec::from_raw_parts(self.ptr, self.len, self.capacity)
135    }
136}
137
138#[repr(C)]
139pub struct PluginApi {
140    pub version: u32,
141    pub handle_request: extern "C" fn(*mut std::ffi::c_void, *const u8, usize) -> RawBuffer,
142    pub free_response: extern "C" fn(*mut RawBuffer),
143}
144
145pub const CURRENT_PLUGIN_API_VERSION: u32 = 1;
146
147#[repr(C)]
148pub struct PluginContext(*mut std::ffi::c_void);
149
150#[macro_export]
151macro_rules! declare_plugin {
152    ($plugin_type:ty) => {
153        static mut PLUGIN_INSTANCE: Option<$plugin_type> = None;
154
155        #[no_mangle]
156        pub extern "C" fn _plugin_create() -> *mut $crate::PluginApi {
157            let api = Box::new($crate::PluginApi {
158                version: $crate::CURRENT_PLUGIN_API_VERSION,
159                handle_request: {
160                    extern "C" fn handle_request(
161                        _ctx: *mut std::ffi::c_void,
162                        request: *const u8,
163                        len: usize,
164                    ) -> $crate::RawBuffer {
165                        unsafe {
166                            if PLUGIN_INSTANCE.is_none() {
167                                PLUGIN_INSTANCE = Some(<$plugin_type>::default());
168                            }
169                            let plugin = PLUGIN_INSTANCE.as_mut().unwrap();
170                            let request_slice = std::slice::from_raw_parts(request, len);
171                            let response = plugin.handle_raw_request(request_slice);
172                            $crate::RawBuffer::from_vec(response)
173                        }
174                    }
175                    handle_request
176                },
177                free_response: {
178                    extern "C" fn free_response(response: *mut $crate::RawBuffer) {
179                        unsafe {
180                            let buffer = Box::from_raw(response);
181                            drop(Vec::from_raw_parts(buffer.ptr, buffer.len, buffer.capacity));
182                        }
183                    }
184                    free_response
185                },
186            });
187            Box::into_raw(api)
188        }
189    };
190}