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}