tiny_native_scheduler/
lib.rs

1use std::process::{Command, Output};
2
3type CustomResult<T> = std::result::Result<T, Box<dyn std::error::Error>>;
4
5/// Execute a command in x minutes using `at` or `schtasks` depending on the OS.
6///
7/// Returns the output of the command.
8///
9/// # Arguments
10///
11/// * `command` - The command to execute.
12/// * `minutes` - The amount of minutes to wait before executing the command.
13/// * `win_task_name` - The name of the task to create on Windows.
14///
15/// # Example
16///
17/// ```no_run
18/// use execute_command_in_x_minutes::execute_command_in_x_minutes;
19///
20/// execute_command_in_x_minutes("cargo install cargo-update", 5).unwrap();
21/// ```
22///
23/// # Errors
24///
25/// This function will return an error if the command fails to execute.
26///
27pub fn execute_command_in_x_minutes(
28    command: &str,
29    minutes: i64,
30    #[allow(unused_variables)] win_task_name: &str,
31) -> CustomResult<Output> {
32    #[cfg(windows)]
33    let output = exec_in_x_minutes_win(command, minutes, win_task_name)?;
34    #[cfg(not(windows))]
35    let output = exec_in_x_minutes_unix(command, minutes)?;
36
37    Ok(output)
38}
39
40#[cfg(windows)]
41use time::{format_description, Duration, OffsetDateTime};
42
43#[cfg(windows)]
44fn exec_in_x_minutes_win(command: &str, minutes: i64, task_name: &str) -> CustomResult<Output> {
45    let scheduled_time = OffsetDateTime::now_local()? + Duration::minutes(minutes);
46    let format = format_description::parse("[hour]:[minute]")?;
47
48    let scheduled_time_string = scheduled_time.format(&format)?;
49
50    let output = Command::new("schtasks")
51        .args([
52            "/create",
53            "/tn",
54            task_name,
55            "/tr",
56            &format!("cmd /C start \"\" /MIN \"cmd\" \"/C {command}\""),
57            "/sc",
58            "once",
59            "/st",
60            &scheduled_time_string,
61            "/f",
62        ])
63        .output()?;
64
65    Ok(output)
66}
67
68#[cfg(not(windows))]
69use std::{
70    io::{Error, ErrorKind},
71    process::Stdio,
72};
73
74#[cfg(not(windows))]
75fn exec_in_x_minutes_unix(command: &str, minutes: i64) -> CustomResult<Output> {
76    let output = Command::new("echo")
77        .arg(command)
78        .stdout(Stdio::piped())
79        .spawn()?
80        .stdout
81        .ok_or_else(|| Error::new(ErrorKind::Other, "'at' failed to read from 'echo'"))?;
82
83    let output = Command::new("at")
84        .args(["now", "+", &minutes.to_string(), "minute"])
85        .stdin(Stdio::from(output))
86        .output()?;
87
88    Ok(output)
89}