use chrono::Utc;
use cron::Schedule;
use directories::ProjectDirs;
use std::collections::HashMap;
use std::env::consts::OS;
use std::fmt;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::{Child, Command};
mod handle;
pub use handle::*;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub enum TaskStatus {
NotStarted,
Running,
Completed,
UserStopped,
ExceptionalExit,
Unknown, }
impl fmt::Display for TaskStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let var_name = match self {
TaskStatus::Running => write!(f, "Running"),
TaskStatus::Completed => write!(f, "Completed"),
TaskStatus::ExceptionalExit => write!(f, "ExceptionalExit"),
TaskStatus::UserStopped => write!(f, "UserStopped"),
TaskStatus::Unknown => write!(f, "Unknown"),
TaskStatus::NotStarted => write!(f, "NotStarted"),
};
var_name
}
}
#[derive(Debug)]
pub struct Task {
pub name: String,
pub schedule: Option<Schedule>,
pub child: Option<Child>,
pub run_on_startup: bool,
pub command: Option<String>,
pub restart_after_stop: bool,
pub status: TaskStatus,
pub enabled: bool,
pub completion_count: u32,
}
impl Task {
pub fn new(
name: &str,
schedule: Option<Schedule>,
run_on_startup: bool,
command: Option<String>,
restart_after_stop: bool,
enabled: bool,
) -> Task {
Task {
schedule,
child: None,
run_on_startup,
command,
restart_after_stop,
name: name.to_string(),
status: TaskStatus::NotStarted,
enabled: enabled,
completion_count: 0,
}
}
pub fn start(&mut self) {
if self.run_on_startup || self.schedule.is_none() {
self.status = TaskStatus::Running;
self.child = Some(self.start_subprocess());
}
}
pub fn start_subprocess(&mut self) -> Child {
if let Err(_) = std::fs::create_dir_all(logs_dir()) {}
let shell = match OS {
"windows" => "powershell",
_ => "bash",
};
let mut cmd = Command::new(shell);
if OS == "windows" {
cmd.arg("-NoProfile");
cmd.arg("-Command");
} else {
cmd.arg("-c");
}
cmd.arg(self.command.clone().expect("请传入命令"));
let mut output_file = logs_dir();
output_file.push(format!("{}-output.log", self.name));
let mut err_file = logs_dir();
err_file.push(format!("{}-error.log", self.name));
cmd.stdout(File::create(output_file).expect("stdout err"));
cmd.stderr(File::create(err_file).expect("stderr err"));
self.set_status(TaskStatus::Running);
cmd.spawn().expect("Failed to start subprocess")
}
pub fn check_child(&mut self) {
if let Some(ref mut child) = self.child {
if let Some(status) = child.try_wait().expect("Failed to check child status") {
if status.success() {
self.status = TaskStatus::Completed;
self.completion_count += 1
} else {
self.status = TaskStatus::ExceptionalExit;
}
}
}
}
pub fn schedule_to_string(&self) -> String {
self.schedule
.as_ref()
.map_or_else(|| "None".to_string(), |s| s.to_string())
}
pub fn stop(&mut self) {
if let Some(ref mut child) = self.child {
println!("Stopping task...");
child.kill().expect("Failed to kill child process");
self.status = TaskStatus::UserStopped;
self.child = None;
self.schedule = None;
}
}
pub fn restart(&mut self) {
self.stop();
self.start();
}
pub fn set_status(&mut self, new_status: TaskStatus) {
self.status = new_status;
}
pub fn should_run_now(&self) -> bool {
std::io::stdout().flush().unwrap(); match self.schedule {
Some(ref s) => {
let upcoming_times: Vec<_> = s.upcoming(Utc).take(1).collect();
let should_run = upcoming_times
.iter()
.any(|dt| (Utc::now().timestamp()) - dt.timestamp() == -1);
should_run
}
None => {
false
}
}
}
}
fn list_tasks(tasks: &HashMap<String, Task>) {
println!(
"{: <20} {: <20} {: <15} {: <10} {: <15} {: <10} {: <10} {: <10}",
"Task Name",
"Schedule",
"Run on Startup",
"Enabled",
"Command",
"Restart",
"Status",
"CompletionCount"
);
for (_, task) in tasks {
println!(
"{: <20} {: <20} {: <15} {: <10} {: <15} {: <10} {: <10} {: <10}",
task.name,
task.schedule_to_string(),
task.run_on_startup,
task.enabled,
task.command.as_ref().map_or("", |c| c.as_str()),
task.restart_after_stop,
task.status,
task.completion_count
);
}
}
pub fn logs_dir() -> PathBuf {
let proj_dirs =
ProjectDirs::from("com", "initcool", "nyar").expect("Failed to get project directories");
let mut config_file = PathBuf::from(proj_dirs.config_dir());
std::fs::create_dir_all(&config_file).expect("");
config_file.push("logs/");
return config_file;
}