145 lines
4.7 KiB
Rust
145 lines
4.7 KiB
Rust
|
|
//! 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::<Option<String>>("name")?.unwrap_or_else(|| "task".to_string());
|
||
|
|
let schedule_str: String = t.get::<String>("schedule")?;
|
||
|
|
let id: String = t.get::<Option<String>>("id")?.unwrap_or_default();
|
||
|
|
|
||
|
|
let action_tbl: Table = t.get::<Table>("action")?;
|
||
|
|
let action_type: String = action_tbl.get::<String>("type")?;
|
||
|
|
let action = match action_type.as_str() {
|
||
|
|
"speak" => Action::Speak {
|
||
|
|
text: action_tbl.get::<String>("text").unwrap_or_default(),
|
||
|
|
},
|
||
|
|
"lua" => Action::Lua {
|
||
|
|
script_path: action_tbl.get::<String>("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)?;
|
||
|
|
|
||
|
|
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<Table> {
|
||
|
|
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<chrono::Utc>| {
|
||
|
|
dt.with_timezone(&chrono::Local).format("один раз в %H:%M %d.%m").to_string()
|
||
|
|
})
|
||
|
|
.unwrap_or_else(|| format!("один раз в {}", at))
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|