1use std::collections::HashMap;
4use std::path::Path;
5
6use mlua::{Function, Lua, MultiValue, Table, Value as LuaValue};
7
8use crate::config::{PluginConfig, PluginMetadata};
9use crate::hooks::{Hook, HookContext, HookResult};
10use crate::runtime::{BoxFuture, IsolatedContext, PluginHandle, PluginRuntime};
11use crate::sandbox::SandboxConfig;
12use crate::types::{PluginError, PluginResult, Value};
13
14use super::bindings;
15use super::isolate::LuaIsolatedContext;
16
17struct LoadedLuaPlugin {
19 name: String,
21
22 module: mlua::RegistryKey,
24
25 metadata: PluginMetadata,
27
28 hooks: Vec<String>,
30}
31
32pub struct LuaRuntime {
34 lua: Lua,
36
37 plugins: HashMap<PluginHandle, LoadedLuaPlugin>,
39
40 next_handle: usize,
42
43 config: Option<PluginConfig>,
45
46 initialized: bool,
48}
49
50impl LuaRuntime {
51 pub fn new() -> PluginResult<Self> {
53 let lua = Lua::new();
54
55 lua.globals()
57 .set("loadfile", LuaValue::Nil)
58 .map_err(|e| PluginError::LoadError {
59 name: "lua".into(),
60 message: e.to_string(),
61 })?;
62
63 lua.globals()
64 .set("dofile", LuaValue::Nil)
65 .map_err(|e| PluginError::LoadError {
66 name: "lua".into(),
67 message: e.to_string(),
68 })?;
69
70 Ok(Self {
71 lua,
72 plugins: HashMap::new(),
73 next_handle: 0,
74 config: None,
75 initialized: false,
76 })
77 }
78
79 fn init_api(&self) -> PluginResult<()> {
81 let globals = self.lua.globals();
82
83 let gf = self
85 .lua
86 .create_table()
87 .map_err(|e| PluginError::LoadError {
88 name: "lua".into(),
89 message: format!("Failed to create gf table: {}", e),
90 })?;
91
92 gf.set("version", env!("CARGO_PKG_VERSION"))
94 .map_err(|e| PluginError::LoadError {
95 name: "lua".into(),
96 message: e.to_string(),
97 })?;
98
99 let log_info = self
101 .lua
102 .create_function(|_, msg: String| {
103 tracing::info!(target: "plugin", "{}", msg);
104 Ok(())
105 })
106 .map_err(|e| PluginError::LoadError {
107 name: "lua".into(),
108 message: e.to_string(),
109 })?;
110 gf.set("log_info", log_info).ok();
111
112 let log_warn = self
113 .lua
114 .create_function(|_, msg: String| {
115 tracing::warn!(target: "plugin", "{}", msg);
116 Ok(())
117 })
118 .map_err(|e| PluginError::LoadError {
119 name: "lua".into(),
120 message: e.to_string(),
121 })?;
122 gf.set("log_warn", log_warn).ok();
123
124 let log_error = self
125 .lua
126 .create_function(|_, msg: String| {
127 tracing::error!(target: "plugin", "{}", msg);
128 Ok(())
129 })
130 .map_err(|e| PluginError::LoadError {
131 name: "lua".into(),
132 message: e.to_string(),
133 })?;
134 gf.set("log_error", log_error).ok();
135
136 let notify = self
138 .lua
139 .create_function(|_, (msg, level): (String, Option<String>)| {
140 let level = level.unwrap_or_else(|| "info".to_string());
141 tracing::info!(target: "plugin_notify", level = level, "{}", msg);
142 Ok(())
143 })
144 .map_err(|e| PluginError::LoadError {
145 name: "lua".into(),
146 message: e.to_string(),
147 })?;
148 gf.set("notify", notify).ok();
149
150 globals
151 .set("gf", gf)
152 .map_err(|e| PluginError::LoadError {
153 name: "lua".into(),
154 message: e.to_string(),
155 })?;
156
157 let fs = bindings::create_fs_api(&self.lua)?;
159 globals.set("fs", fs).map_err(|e| PluginError::LoadError {
160 name: "lua".into(),
161 message: e.to_string(),
162 })?;
163
164 let ui = bindings::create_ui_api(&self.lua)?;
166 globals.set("ui", ui).map_err(|e| PluginError::LoadError {
167 name: "lua".into(),
168 message: e.to_string(),
169 })?;
170
171 Ok(())
172 }
173
174 fn lua_to_value(lua_val: LuaValue) -> Value {
176 match lua_val {
177 LuaValue::Nil => Value::Null,
178 LuaValue::Boolean(b) => Value::Bool(b),
179 LuaValue::Integer(i) => Value::Integer(i),
180 LuaValue::Number(n) => Value::Float(n),
181 LuaValue::String(s) => Value::String(s.to_string_lossy()),
182 LuaValue::Table(t) => {
183 let mut is_array = true;
185 let mut max_index = 0i64;
186
187 for pair in t.clone().pairs::<i64, LuaValue>() {
188 if let Ok((k, _)) = pair {
189 if k > 0 {
190 max_index = max_index.max(k);
191 } else {
192 is_array = false;
193 break;
194 }
195 } else {
196 is_array = false;
197 break;
198 }
199 }
200
201 if is_array && max_index > 0 {
202 let mut arr = Vec::new();
203 for i in 1..=max_index {
204 if let Ok(v) = t.get::<LuaValue>(i) {
205 arr.push(Self::lua_to_value(v));
206 }
207 }
208 Value::Array(arr)
209 } else {
210 let mut obj = std::collections::HashMap::new();
211 for pair in t.pairs::<String, LuaValue>() {
212 if let Ok((k, v)) = pair {
213 obj.insert(k, Self::lua_to_value(v));
214 }
215 }
216 Value::Object(obj)
217 }
218 }
219 _ => Value::Null,
220 }
221 }
222
223 fn value_to_lua(&self, lua: &Lua, val: &Value) -> mlua::Result<LuaValue> {
225 match val {
226 Value::Null => Ok(LuaValue::Nil),
227 Value::Bool(b) => Ok(LuaValue::Boolean(*b)),
228 Value::Integer(i) => Ok(LuaValue::Integer(*i)),
229 Value::Float(f) => Ok(LuaValue::Number(*f)),
230 Value::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
231 Value::Array(arr) => {
232 let table = lua.create_table()?;
233 for (i, v) in arr.iter().enumerate() {
234 table.set(i + 1, self.value_to_lua(lua, v)?)?;
235 }
236 Ok(LuaValue::Table(table))
237 }
238 Value::Object(obj) => {
239 let table = lua.create_table()?;
240 for (k, v) in obj {
241 table.set(k.as_str(), self.value_to_lua(lua, v)?)?;
242 }
243 Ok(LuaValue::Table(table))
244 }
245 Value::Bytes(b) => Ok(LuaValue::String(lua.create_string(b)?)),
246 }
247 }
248
249 fn hook_to_lua(&self, lua: &Lua, hook: &Hook) -> mlua::Result<Table> {
251 let table = lua.create_table()?;
252
253 let json = serde_json::to_string(hook).map_err(|e| mlua::Error::external(e))?;
255 let json_val: serde_json::Value =
256 serde_json::from_str(&json).map_err(|e| mlua::Error::external(e))?;
257
258 fn json_to_lua(lua: &Lua, val: &serde_json::Value) -> mlua::Result<LuaValue> {
259 match val {
260 serde_json::Value::Null => Ok(LuaValue::Nil),
261 serde_json::Value::Bool(b) => Ok(LuaValue::Boolean(*b)),
262 serde_json::Value::Number(n) => {
263 if let Some(i) = n.as_i64() {
264 Ok(LuaValue::Integer(i))
265 } else {
266 Ok(LuaValue::Number(n.as_f64().unwrap_or(0.0)))
267 }
268 }
269 serde_json::Value::String(s) => Ok(LuaValue::String(lua.create_string(s)?)),
270 serde_json::Value::Array(arr) => {
271 let t = lua.create_table()?;
272 for (i, v) in arr.iter().enumerate() {
273 t.set(i + 1, json_to_lua(lua, v)?)?;
274 }
275 Ok(LuaValue::Table(t))
276 }
277 serde_json::Value::Object(obj) => {
278 let t = lua.create_table()?;
279 for (k, v) in obj {
280 t.set(k.as_str(), json_to_lua(lua, v)?)?;
281 }
282 Ok(LuaValue::Table(t))
283 }
284 }
285 }
286
287 if let serde_json::Value::Object(obj) = json_val {
288 for (k, v) in obj {
289 table.set(k.as_str(), json_to_lua(lua, &v)?)?;
290 }
291 }
292
293 Ok(table)
294 }
295}
296
297impl Default for LuaRuntime {
298 fn default() -> Self {
299 Self::new().expect("Failed to create Lua runtime")
300 }
301}
302
303impl PluginRuntime for LuaRuntime {
304 fn name(&self) -> &'static str {
305 "lua"
306 }
307
308 fn file_extensions(&self) -> &'static [&'static str] {
309 &[".lua"]
310 }
311
312 fn init(&mut self, config: &PluginConfig) -> PluginResult<()> {
313 if self.initialized {
314 return Ok(());
315 }
316
317 self.config = Some(config.clone());
318 self.init_api()?;
319 self.initialized = true;
320
321 Ok(())
322 }
323
324 fn load_plugin(&mut self, id: &str, source: &Path) -> PluginResult<PluginHandle> {
325 let code = std::fs::read_to_string(source)?;
327
328 let chunk = self.lua.load(&code).set_name(id);
330
331 let module: Table = chunk.eval().map_err(|e| PluginError::LoadError {
332 name: id.to_string(),
333 message: e.to_string(),
334 })?;
335
336 let mut hooks = vec![];
338 for hook_name in [
339 "on_navigate",
340 "on_drill_down",
341 "on_back",
342 "on_scan_start",
343 "on_scan_progress",
344 "on_scan_complete",
345 "on_delete_start",
346 "on_delete_complete",
347 "on_copy_start",
348 "on_copy_complete",
349 "on_move_start",
350 "on_move_complete",
351 "on_render",
352 "on_action",
353 "on_mode_change",
354 "on_startup",
355 "on_shutdown",
356 ] {
357 if module.contains_key(hook_name).unwrap_or(false) {
358 hooks.push(hook_name.to_string());
359 }
360 }
361
362 let key = self.lua.create_registry_value(module).map_err(|e| {
364 PluginError::LoadError {
365 name: id.to_string(),
366 message: e.to_string(),
367 }
368 })?;
369
370 let handle = PluginHandle::new(self.next_handle);
371 self.next_handle += 1;
372
373 let metadata = PluginMetadata {
375 name: id.to_string(),
376 runtime: "lua".to_string(),
377 ..Default::default()
378 };
379
380 self.plugins.insert(
381 handle,
382 LoadedLuaPlugin {
383 name: id.to_string(),
384 module: key,
385 metadata,
386 hooks,
387 },
388 );
389
390 Ok(handle)
391 }
392
393 fn unload_plugin(&mut self, handle: PluginHandle) -> PluginResult<()> {
394 if let Some(plugin) = self.plugins.remove(&handle) {
395 self.lua.remove_registry_value(plugin.module).ok();
396 }
397 Ok(())
398 }
399
400 fn get_metadata(&self, handle: PluginHandle) -> Option<&PluginMetadata> {
401 self.plugins.get(&handle).map(|p| &p.metadata)
402 }
403
404 fn has_hook(&self, handle: PluginHandle, hook_name: &str) -> bool {
405 self.plugins
406 .get(&handle)
407 .map(|p| p.hooks.contains(&hook_name.to_string()))
408 .unwrap_or(false)
409 }
410
411 fn call_hook_sync(
412 &self,
413 handle: PluginHandle,
414 hook: &Hook,
415 _ctx: &HookContext,
416 ) -> PluginResult<HookResult> {
417 let plugin = self.plugins.get(&handle).ok_or_else(|| PluginError::NotFound {
418 path: std::path::PathBuf::new(),
419 })?;
420
421 let module: Table = self.lua.registry_value(&plugin.module).map_err(|e| {
422 PluginError::ExecutionError {
423 name: plugin.name.clone(),
424 message: e.to_string(),
425 }
426 })?;
427
428 let hook_name = hook.name();
429 let func: Function = match module.get(hook_name) {
430 Ok(f) => f,
431 Err(_) => return Ok(HookResult::default()),
432 };
433
434 let hook_table = self.hook_to_lua(&self.lua, hook).map_err(|e| {
436 PluginError::ExecutionError {
437 name: plugin.name.clone(),
438 message: e.to_string(),
439 }
440 })?;
441
442 let result: LuaValue = func.call((module.clone(), hook_table)).map_err(|e| {
444 PluginError::ExecutionError {
445 name: plugin.name.clone(),
446 message: e.to_string(),
447 }
448 })?;
449
450 let mut hook_result = HookResult::ok();
452 if let LuaValue::Table(t) = result {
453 if let Ok(prevent) = t.get::<bool>("prevent_default") {
454 if prevent {
455 hook_result = hook_result.prevent_default();
456 }
457 }
458 if let Ok(stop) = t.get::<bool>("stop_propagation") {
459 if stop {
460 hook_result = hook_result.stop_propagation();
461 }
462 }
463 if let Ok(val) = t.get::<LuaValue>("value") {
464 hook_result.value = Some(Self::lua_to_value(val));
465 }
466 }
467
468 Ok(hook_result)
469 }
470
471 fn call_hook_async<'a>(
472 &'a self,
473 handle: PluginHandle,
474 hook: &'a Hook,
475 ctx: &'a HookContext,
476 ) -> BoxFuture<'a, PluginResult<HookResult>> {
477 Box::pin(async move { self.call_hook_sync(handle, hook, ctx) })
480 }
481
482 fn call_method<'a>(
483 &'a self,
484 handle: PluginHandle,
485 method: &'a str,
486 args: Vec<Value>,
487 ) -> BoxFuture<'a, PluginResult<Value>> {
488 Box::pin(async move {
489 let plugin = self.plugins.get(&handle).ok_or_else(|| PluginError::NotFound {
490 path: std::path::PathBuf::new(),
491 })?;
492
493 let module: Table = self.lua.registry_value(&plugin.module).map_err(|e| {
494 PluginError::ExecutionError {
495 name: plugin.name.clone(),
496 message: e.to_string(),
497 }
498 })?;
499
500 let func: Function = module.get(method).map_err(|e| PluginError::ExecutionError {
501 name: plugin.name.clone(),
502 message: format!("Method '{}' not found: {}", method, e),
503 })?;
504
505 let lua_args: Vec<LuaValue> = args
507 .iter()
508 .map(|v| self.value_to_lua(&self.lua, v))
509 .collect::<Result<_, _>>()
510 .map_err(|e| PluginError::ExecutionError {
511 name: plugin.name.clone(),
512 message: e.to_string(),
513 })?;
514
515 let result: LuaValue = func
516 .call(MultiValue::from_vec(
517 std::iter::once(LuaValue::Table(module))
518 .chain(lua_args)
519 .collect(),
520 ))
521 .map_err(|e| PluginError::ExecutionError {
522 name: plugin.name.clone(),
523 message: e.to_string(),
524 })?;
525
526 Ok(Self::lua_to_value(result))
527 })
528 }
529
530 fn create_isolated_context(
531 &self,
532 sandbox: &SandboxConfig,
533 ) -> PluginResult<Box<dyn IsolatedContext>> {
534 Ok(Box::new(LuaIsolatedContext::new(sandbox.clone())?))
535 }
536
537 fn loaded_plugins(&self) -> Vec<PluginHandle> {
538 self.plugins.keys().copied().collect()
539 }
540
541 fn shutdown(&mut self) -> PluginResult<()> {
542 self.plugins.clear();
543 Ok(())
544 }
545}