separate app struct

This commit is contained in:
Dmitry Belyaev 2023-04-27 12:32:00 +03:00
parent 21418ce45d
commit c69798ffd6
Signed by: b4tman
GPG Key ID: 41A00BF15EA7E5F3
5 changed files with 122 additions and 16 deletions

1
.gitignore vendored
View File

@ -1 +1,2 @@
/target /target
.vscode/

16
Cargo.lock generated
View File

@ -110,6 +110,12 @@ dependencies = [
"syn 2.0.15", "syn 2.0.15",
] ]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.12.3" version = "0.12.3"
@ -150,6 +156,15 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.61" version = "0.3.61"
@ -164,6 +179,7 @@ name = "laika"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"itertools",
"serde", "serde",
"sysinfo", "sysinfo",
"toml", "toml",

View File

@ -7,6 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
chrono = "0.4.24" chrono = "0.4.24"
itertools = "0.10.5"
serde = { version = "1.0.160", features = ["derive"] } serde = { version = "1.0.160", features = ["derive"] }
sysinfo = { version = "0.28.4", default-features = false } sysinfo = { version = "0.28.4", default-features = false }
toml = "0.7.3" toml = "0.7.3"

View File

@ -2,6 +2,9 @@ command = "calc.exe"
target = "CalculatorApp.exe" target = "CalculatorApp.exe"
workdir = "." workdir = "."
delay = 2 delay = 2
start = { hour = 11, minute = 50 } #start = { hour = 11, minute = 50 }
end = { hour = 12, minute = 55 } start = "11:50"
#end = { hour = 12, minute = 55 }
end = "12:55"

View File

@ -2,16 +2,37 @@
use chrono::prelude::*; use chrono::prelude::*;
use chrono::{DateTime, Local, TimeZone}; use chrono::{DateTime, Local, TimeZone};
use itertools::Itertools;
use serde::Deserialize; use serde::Deserialize;
use std::path::Path; use std::path::Path;
use std::str::FromStr;
use std::{thread, time::Duration}; use std::{thread, time::Duration};
use sysinfo::{System, SystemExt}; use sysinfo::{System, SystemExt};
use std::{env, fs, path::PathBuf, process::Command}; use std::{env, fs, path::PathBuf, process::Command};
trait ErrorToString {
type Output;
fn str_err(self) -> std::result::Result<Self::Output, String>;
}
impl<T, E> ErrorToString for std::result::Result<T, E>
where
E: std::error::Error,
{
type Output = T;
fn str_err(self) -> std::result::Result<Self::Output, String> {
self.map_err(|e| e.to_string())
}
}
/// Time with hours (24) and minutes (60)
#[derive(Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] #[derive(Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
#[serde(try_from = "String")]
struct TimeHM { struct TimeHM {
/// hour num (0..23)
hour: u8, hour: u8,
/// minute num (0..59)
minute: u8, minute: u8,
} }
@ -24,17 +45,60 @@ impl<Tz: TimeZone> From<DateTime<Tz>> for TimeHM {
} }
} }
#[derive(Deserialize, Default)] impl FromStr for TimeHM {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if let Some((str_hour, str_minute)) = s.split(':').collect_tuple() {
let hour: u8 = str_hour.parse().str_err()?;
let minute: u8 = str_minute.parse().str_err()?;
Ok(TimeHM { hour, minute })
} else {
Err("invalid time, must be hh:mm".into())
}
}
}
impl TryFrom<String> for TimeHM {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
value.parse()
}
}
/// Application config
#[derive(Deserialize, Default, Debug)]
struct Config { struct Config {
/// is application active
#[serde(default = "default_true")]
active: bool,
/// need to exit
#[serde(default)]
exit_now: bool,
/// target command
command: String, command: String,
/// target process name
#[serde(default)]
target: String, target: String,
/// workdir for launch target command
#[serde(default)]
workdir: String, workdir: String,
/// args for launch target command
#[serde(default)]
args: Option<Vec<String>>, args: Option<Vec<String>>,
/// start time
start: TimeHM, start: TimeHM,
/// end time
end: TimeHM, end: TimeHM,
/// check delay (seconds)
delay: u32, delay: u32,
} }
fn default_true() -> bool {
true
}
impl Config { impl Config {
fn read<P: AsRef<Path>>(path: P) -> Result<Self, String> { fn read<P: AsRef<Path>>(path: P) -> Result<Self, String> {
let data = fs::read_to_string(path).map_err(|e| format!("can't read config: {:?}", e))?; let data = fs::read_to_string(path).map_err(|e| format!("can't read config: {:?}", e))?;
@ -75,39 +139,58 @@ impl Config {
.unwrap() .unwrap()
} }
} }
fn is_valid(&self) -> bool {
(!self.command.is_empty()) && self.delay > 0
}
}
struct Laika {
config: Config,
}
impl From<Config> for Laika {
fn from(config: Config) -> Self {
Laika { config }
}
}
impl Laika {
fn is_in_time(&self, value: TimeHM) -> bool { fn is_in_time(&self, value: TimeHM) -> bool {
self.start <= value && value <= self.end self.config.start <= value && value <= self.config.end
} }
fn is_active(&self) -> bool { fn is_active(&self) -> bool {
let now: DateTime<Local> = Local::now(); let now: DateTime<Local> = Local::now();
self.is_in_time(now.into()) self.config.active && self.config.is_valid() && self.is_in_time(now.into())
} }
fn sleep(&self) { fn sleep(&self) {
thread::sleep(Duration::from_secs(self.delay.into())); thread::sleep(Duration::from_secs(self.config.delay.into()));
} }
fn is_target_alive(&self) -> bool { fn is_target_alive(&self) -> bool {
let name = self.target_name(); let name = self.config.target_name();
if name.is_empty() {
return false;
}
let sp = System::new_all(); let sp = System::new_all();
let procs = sp.processes_by_exact_name(&name); let procs = sp.processes_by_exact_name(&name);
procs.count() > 0 procs.count() > 0
} }
fn relaunch_target(&self) { fn relaunch_target(&self) {
let mut cmd = Command::new(&self.command); let mut cmd = Command::new(&self.config.command);
cmd.current_dir(&self.workdir); cmd.current_dir(&self.config.workdir);
if let Some(args) = &self.args { if let Some(args) = &self.config.args {
cmd.args(args); cmd.args(args);
} }
let _res = cmd.spawn(); let _res = cmd.spawn();
} }
fn is_valid(&self) -> bool {
(!self.command.is_empty()) && self.delay > 0
}
fn main_loop(self) { fn main_loop(self) {
if !self.is_valid() { if !self.config.is_valid() {
return; return;
} }
loop { loop {
if self.is_active() && !self.is_target_alive() { if self.config.exit_now {
return;
} else if self.is_active() && !self.is_target_alive() {
self.relaunch_target(); self.relaunch_target();
} }
self.sleep(); self.sleep();
@ -116,5 +199,7 @@ impl Config {
} }
fn main() { fn main() {
Config::get().main_loop() let config = Config::get();
let laika: Laika = config.into();
laika.main_loop();
} }