//! Lua bindings for the proactive scheduler. //! //! Usage: //! jarvis.scheduler.add({ //! name = "Daily briefing", //! schedule = "daily 09:00", //! action = { type = "speak", text = "Доброе утро. Готов к работе." } //! }) //! //! jarvis.scheduler.add({ //! name = "Reminder", //! schedule = "in 5 minutes", //! action = { type = "speak", text = "Выключи кофеварку." } //! }) //! //! for _, t in ipairs(jarvis.scheduler.list()) do //! print(t.id, t.name, t.schedule_human) //! end //! //! jarvis.scheduler.remove(id) //! jarvis.scheduler.clear() //! //! Schedule string syntax (see `scheduler::Schedule::parse`): //! "daily HH:MM" | "at HH:MM" | "every N minutes" | "every N hours" | "in N minutes" | "in N hours" use mlua::{Lua, Table, Value}; use crate::scheduler::{self, Action, Schedule, ScheduledTask}; pub fn register(lua: &Lua, jarvis: &Table) -> mlua::Result<()> { let sched = lua.create_table()?; let add_fn = lua.create_function(|_, t: Table| { let name: String = t.get::>("name")?.unwrap_or_else(|| "task".to_string()); let schedule_str: String = t.get::("schedule")?; let id: String = t.get::>("id")?.unwrap_or_default(); let action_tbl: Table = t.get::("action")?; let action_type: String = action_tbl.get::("type")?; let action = match action_type.as_str() { "speak" => Action::Speak { text: action_tbl.get::("text").unwrap_or_default(), }, "lua" => Action::Lua { script_path: action_tbl.get::("script_path").unwrap_or_default(), }, other => return Err(mlua::Error::external(format!("unknown action type: {}", other))), }; let schedule = Schedule::parse(&schedule_str) .map_err(|e| mlua::Error::external(format!("bad schedule '{}': {}", schedule_str, e)))?; let task = ScheduledTask { id, name, schedule, action, last_fired: None, enabled: true, created_at: 0, }; scheduler::add(task).map_err(mlua::Error::external) })?; sched.set("add", add_fn)?; let remove_fn = lua.create_function(|_, id: String| { Ok(scheduler::remove(&id)) })?; sched.set("remove", remove_fn)?; let clear_fn = lua.create_function(|_, ()| { Ok(scheduler::clear()) })?; sched.set("clear", clear_fn)?; // Remove all tasks whose name OR speak text matches `query` (substring, // case-insensitive). Returns count removed. let remove_by_text_fn = lua.create_function(|_, query: String| { Ok(scheduler::remove_by_text(&query)) })?; sched.set("remove_by_text", remove_by_text_fn)?; // Find (don't remove) tasks matching query. Returns array of tasks. let find_by_text_fn = lua.create_function(|lua, (query, n): (String, Option)| { let arr = lua.create_table()?; for (i, t) in scheduler::find_by_text(&query, n.unwrap_or(5)).iter().enumerate() { arr.set(i + 1, task_to_lua(lua, t)?)?; } Ok(arr) })?; sched.set("find_by_text", find_by_text_fn)?; let list_fn = lua.create_function(|lua, ()| { let arr = lua.create_table()?; for (i, t) in scheduler::list().iter().enumerate() { arr.set(i + 1, task_to_lua(lua, t)?)?; } Ok(arr) })?; sched.set("list", list_fn)?; let count_fn = lua.create_function(|_, ()| { Ok(scheduler::list().len()) })?; sched.set("count", count_fn)?; jarvis.set("scheduler", sched)?; Ok(()) } fn task_to_lua(lua: &Lua, t: &ScheduledTask) -> mlua::Result
{ let tbl = lua.create_table()?; tbl.set("id", t.id.clone())?; tbl.set("name", t.name.clone())?; tbl.set("enabled", t.enabled)?; tbl.set("created_at", t.created_at)?; tbl.set("schedule_human", schedule_human(&t.schedule))?; let last_fired: Value = match t.last_fired { Some(ts) => Value::Integer(ts), None => Value::Nil, }; tbl.set("last_fired", last_fired)?; let action_tbl = lua.create_table()?; match &t.action { Action::Speak { text } => { action_tbl.set("type", "speak")?; action_tbl.set("text", text.clone())?; } Action::Lua { script_path } => { action_tbl.set("type", "lua")?; action_tbl.set("script_path", script_path.clone())?; } } tbl.set("action", action_tbl)?; Ok(tbl) } fn schedule_human(s: &Schedule) -> String { match s { Schedule::Daily { hour, minute } => format!("каждый день в {:02}:{:02}", hour, minute), Schedule::Interval { seconds } => { if *seconds % 3600 == 0 { format!("каждые {} часов", seconds / 3600) } else if *seconds % 60 == 0 { format!("каждые {} минут", seconds / 60) } else { format!("каждые {} секунд", seconds) } } Schedule::Once { at } => { chrono::DateTime::from_timestamp(*at, 0) .map(|dt: chrono::DateTime| { dt.with_timezone(&chrono::Local).format("один раз в %H:%M %d.%m").to_string() }) .unwrap_or_else(|| format!("один раз в {}", at)) } } }