+ weekdays + includes/excludes + upd deps
This commit is contained in:
parent
529ca478b1
commit
4ced16e8da
82
Cargo.lock
generated
82
Cargo.lock
generated
@ -99,7 +99,7 @@ dependencies = [
|
|||||||
"iana-time-zone-haiku",
|
"iana-time-zone-haiku",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-core",
|
"windows-core 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -123,9 +123,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.14.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"either",
|
"either",
|
||||||
]
|
]
|
||||||
@ -142,7 +142,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "laika"
|
name = "laika"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
"chrono",
|
||||||
@ -266,23 +266,22 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sysinfo"
|
name = "sysinfo"
|
||||||
version = "0.28.4"
|
version = "0.33.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4c2f3ca6693feb29a89724516f016488e9aafc7f37264f898593ee4b942f31b"
|
checksum = "4fc858248ea01b66f19d8e8a6d55f41deaf91e9d495246fd01368d99935c6c01"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
|
||||||
"core-foundation-sys",
|
"core-foundation-sys",
|
||||||
"libc",
|
"libc",
|
||||||
|
"memchr",
|
||||||
"ntapi",
|
"ntapi",
|
||||||
"once_cell",
|
"windows",
|
||||||
"winapi",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.7.8"
|
version = "0.8.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
|
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
@ -301,9 +300,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.19.15"
|
version = "0.22.22"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
|
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@ -398,6 +397,16 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "12342cb4d8e3b046f3d80effd474a7a02447231330ef77d71daa6fbc40681143"
|
||||||
|
dependencies = [
|
||||||
|
"windows-core 0.57.0",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-core"
|
name = "windows-core"
|
||||||
version = "0.52.0"
|
version = "0.52.0"
|
||||||
@ -407,6 +416,49 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2ed2439a290666cd67ecce2b0ffaad89c2a56b976b736e6ece670297897832d"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-result",
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9107ddc059d5b6fbfbffdfa7a7fe3e22a226def0b2608f72e9d552763d3e1ad7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.57.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29bee4b38ea3cde66011baa44dba677c432a78593e202392d1e9070cf2a7fca7"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5e383302e8ec8515204254685643de10811af0ed97ea37210dc26fb0032647f8"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-targets"
|
name = "windows-targets"
|
||||||
version = "0.52.6"
|
version = "0.52.6"
|
||||||
@ -473,9 +525,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.40"
|
version = "0.6.25"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876"
|
checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "laika"
|
name = "laika"
|
||||||
version = "0.1.0"
|
version = "0.1.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
@ -8,10 +8,10 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.95"
|
anyhow = "1.0.95"
|
||||||
chrono = "0.4.24"
|
chrono = "0.4.24"
|
||||||
itertools = "0.10.5"
|
itertools = "0.14.0"
|
||||||
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.33.1", default-features = false, features = ["system"]}
|
||||||
toml = "0.7.3"
|
toml = "0.8.19"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = "s"
|
opt-level = "s"
|
||||||
|
127
src/config.rs
Normal file
127
src/config.rs
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
use crate::datetime::{LocalDate, TimeHM, Weekdays};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use std::{env, fs, path::PathBuf};
|
||||||
|
|
||||||
|
/// Application config
|
||||||
|
#[derive(Deserialize, Default, Debug)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
/// modification time
|
||||||
|
#[serde(skip)]
|
||||||
|
pub(crate) modified: Option<SystemTime>,
|
||||||
|
/// is application active
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub(crate) active: bool,
|
||||||
|
/// need to exit
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) exit_now: bool,
|
||||||
|
/// target command
|
||||||
|
pub(crate) command: String,
|
||||||
|
/// target process name
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) target: String,
|
||||||
|
/// workdir for launch target command
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) workdir: String,
|
||||||
|
/// args for launch target command
|
||||||
|
#[serde(default)]
|
||||||
|
pub(crate) args: Option<Vec<String>>,
|
||||||
|
/// weekdays (Mon,Tue,Wed | Пн,Вт,Ср)
|
||||||
|
pub(crate) weekdays: Weekdays,
|
||||||
|
/// exclude dates
|
||||||
|
pub(crate) exclude_dates: Vec<LocalDate>,
|
||||||
|
/// include dates
|
||||||
|
pub(crate) include_dates: Vec<LocalDate>,
|
||||||
|
/// start time
|
||||||
|
pub(crate) start: TimeHM,
|
||||||
|
/// end time
|
||||||
|
pub(crate) end: TimeHM,
|
||||||
|
/// check delay (seconds)
|
||||||
|
pub(crate) delay: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub(crate) fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
|
||||||
|
let data = fs::read_to_string(path).map_err(|e| anyhow!("can't read config: {:?}", e))?;
|
||||||
|
toml::from_str(&data).map_err(|e| anyhow!("can't parse config: {:?}", e))
|
||||||
|
}
|
||||||
|
pub(crate) fn file_location() -> Result<PathBuf> {
|
||||||
|
let res = env::current_exe()
|
||||||
|
.map_err(|e| anyhow!("can't get current exe path: {:?}", e))?
|
||||||
|
.with_extension("toml");
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
pub(crate) fn get() -> Self {
|
||||||
|
let path = Config::file_location();
|
||||||
|
if let Err(_e) = path {
|
||||||
|
//println!("{}", _e);
|
||||||
|
return Config::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = path.unwrap();
|
||||||
|
let cfg = Config::read(&path);
|
||||||
|
match cfg {
|
||||||
|
Err(_e) => {
|
||||||
|
//println!("{}", _e);
|
||||||
|
Config::default()
|
||||||
|
}
|
||||||
|
Ok(mut cfg) => {
|
||||||
|
let metadata = fs::metadata(&path);
|
||||||
|
if let Ok(meta) = metadata {
|
||||||
|
if let Ok(time) = meta.modified() {
|
||||||
|
cfg.modified = Some(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn is_file_modified(&self) -> bool {
|
||||||
|
if self.modified.is_none() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let path = Config::file_location();
|
||||||
|
if let Err(_e) = path {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let path = path.unwrap();
|
||||||
|
let metadata = fs::metadata(path);
|
||||||
|
let mut result = false;
|
||||||
|
if let Ok(meta) = metadata {
|
||||||
|
if let Ok(time) = meta.modified() {
|
||||||
|
result = self.modified.unwrap() < time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
pub(crate) fn reload(&mut self) {
|
||||||
|
if !self.is_file_modified() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let new_cfg = Self::get();
|
||||||
|
*self = new_cfg;
|
||||||
|
}
|
||||||
|
pub(crate) fn target_name(&self) -> String {
|
||||||
|
if !self.target.is_empty() {
|
||||||
|
self.target.clone()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(self.command.clone())
|
||||||
|
.file_name()
|
||||||
|
.unwrap()
|
||||||
|
.to_os_string()
|
||||||
|
.into_string()
|
||||||
|
.unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn is_valid(&self) -> bool {
|
||||||
|
(!self.command.is_empty()) && self.delay > 0
|
||||||
|
}
|
||||||
|
}
|
184
src/datetime.rs
Normal file
184
src/datetime.rs
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
use chrono::prelude::*;
|
||||||
|
use chrono::{DateTime, Local, TimeZone};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fmt::Display;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
|
const WEEKDAYS_STR_EN: [&str; 7] = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
||||||
|
const WEEKDAYS_STR_RU: [&str; 7] = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"];
|
||||||
|
|
||||||
|
/// Time with hours (24) and minutes (60)
|
||||||
|
#[derive(Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
||||||
|
#[serde(try_from = "String")]
|
||||||
|
pub(crate) struct TimeHM {
|
||||||
|
/// hour num (0..23)
|
||||||
|
hour: u8,
|
||||||
|
/// minute num (0..59)
|
||||||
|
minute: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Tz: TimeZone> From<DateTime<Tz>> for TimeHM {
|
||||||
|
fn from(value: DateTime<Tz>) -> TimeHM {
|
||||||
|
TimeHM {
|
||||||
|
hour: value.hour() as u8,
|
||||||
|
minute: value.minute() as u8,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for TimeHM {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
|
if let Some((str_hour, str_minute)) = s.split(':').collect_tuple() {
|
||||||
|
let hour: u8 = str_hour.parse()?;
|
||||||
|
let minute: u8 = str_minute.parse()?;
|
||||||
|
Ok(TimeHM { hour, minute })
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("invalid time, must be hh:mm"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for TimeHM {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
||||||
|
#[serde(try_from = "String")]
|
||||||
|
pub(crate) struct Weekdays {
|
||||||
|
/// bits of weekdays
|
||||||
|
bits: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for Weekdays {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self> {
|
||||||
|
let mut bits: u8 = 0b10000000;
|
||||||
|
|
||||||
|
let mut has_invalid = false;
|
||||||
|
let mut has_valid = false;
|
||||||
|
|
||||||
|
for &weekdays_str in [&WEEKDAYS_STR_EN, &WEEKDAYS_STR_RU] {
|
||||||
|
let found = s.split(',').map(|p| {
|
||||||
|
weekdays_str
|
||||||
|
.iter()
|
||||||
|
.find_position(|&&x| x.to_lowercase() == p.trim().to_lowercase())
|
||||||
|
});
|
||||||
|
|
||||||
|
for item in found {
|
||||||
|
if let Some((pos, _)) = item {
|
||||||
|
bits |= 1 << pos;
|
||||||
|
has_valid = true;
|
||||||
|
} else {
|
||||||
|
has_invalid = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if has_valid || !has_invalid {
|
||||||
|
Ok(Weekdays { bits })
|
||||||
|
} else {
|
||||||
|
Err(anyhow!("invalid time, must be hh:mm"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for Weekdays {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Weekdays {
|
||||||
|
pub(crate) fn is_weekday_enabled(&self, day: u8) -> bool {
|
||||||
|
(self.bits >> day) & 1 == 1
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_today_enabled(&self) -> bool {
|
||||||
|
let now = Local::now();
|
||||||
|
let day = now.weekday().num_days_from_monday();
|
||||||
|
self.is_weekday_enabled(day as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for Weekdays {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut res = String::new();
|
||||||
|
for (i, item) in WEEKDAYS_STR_EN.iter().enumerate() {
|
||||||
|
if self.is_weekday_enabled(i as u8) {
|
||||||
|
res.push_str(&format!("{},", item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.pop();
|
||||||
|
write!(f, "{}", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
|
||||||
|
#[serde(try_from = "String")]
|
||||||
|
pub(crate) struct LocalDate {
|
||||||
|
inner: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for LocalDate {
|
||||||
|
fn default() -> Self {
|
||||||
|
LocalDate {
|
||||||
|
inner: Local::now().date_naive(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for LocalDate {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.inner.format("%d.%m.%Y"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for LocalDate {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let date = NaiveDateTime::parse_from_str(s, "%d.%m.%Y")?;
|
||||||
|
Ok(LocalDate { inner: date.date() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<String> for LocalDate {
|
||||||
|
type Error = anyhow::Error;
|
||||||
|
|
||||||
|
fn try_from(value: String) -> Result<Self, Self::Error> {
|
||||||
|
value.parse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LocalDate {
|
||||||
|
pub(crate) fn is_date(&self, value: NaiveDate) -> bool {
|
||||||
|
self.inner == value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_date_of(&self, value: DateTime<Local>) -> bool {
|
||||||
|
self.is_date(value.date_naive())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait HasToday {
|
||||||
|
fn has_today(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasToday for Vec<LocalDate> {
|
||||||
|
fn has_today(&self) -> bool {
|
||||||
|
let now = Local::now();
|
||||||
|
self.iter().any(|x| x.is_date_of(now))
|
||||||
|
}
|
||||||
|
}
|
190
src/main.rs
190
src/main.rs
@ -1,172 +1,13 @@
|
|||||||
#![windows_subsystem = "windows"]
|
#![windows_subsystem = "windows"]
|
||||||
|
|
||||||
use chrono::prelude::*;
|
use chrono::{DateTime, Local};
|
||||||
use chrono::{DateTime, Local, TimeZone};
|
use config::Config;
|
||||||
use itertools::Itertools;
|
use datetime::{HasToday, TimeHM};
|
||||||
use serde::Deserialize;
|
use std::{process::Command, thread, time::Duration};
|
||||||
use std::path::Path;
|
use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
|
||||||
use std::str::FromStr;
|
|
||||||
use std::time::SystemTime;
|
|
||||||
use std::{thread, time::Duration};
|
|
||||||
use sysinfo::{System, SystemExt};
|
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
mod config;
|
||||||
use std::{env, fs, path::PathBuf, process::Command};
|
mod datetime;
|
||||||
|
|
||||||
/// Time with hours (24) and minutes (60)
|
|
||||||
#[derive(Deserialize, Default, PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
|
||||||
#[serde(try_from = "String")]
|
|
||||||
struct TimeHM {
|
|
||||||
/// hour num (0..23)
|
|
||||||
hour: u8,
|
|
||||||
/// minute num (0..59)
|
|
||||||
minute: u8,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Tz: TimeZone> From<DateTime<Tz>> for TimeHM {
|
|
||||||
fn from(value: DateTime<Tz>) -> TimeHM {
|
|
||||||
TimeHM {
|
|
||||||
hour: value.hour() as u8,
|
|
||||||
minute: value.minute() as u8,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for TimeHM {
|
|
||||||
type Err = anyhow::Error;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self> {
|
|
||||||
if let Some((str_hour, str_minute)) = s.split(':').collect_tuple() {
|
|
||||||
let hour: u8 = str_hour.parse()?;
|
|
||||||
let minute: u8 = str_minute.parse()?;
|
|
||||||
Ok(TimeHM { hour, minute })
|
|
||||||
} else {
|
|
||||||
Err(anyhow!("invalid time, must be hh:mm"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TryFrom<String> for TimeHM {
|
|
||||||
type Error = anyhow::Error;
|
|
||||||
|
|
||||||
fn try_from(value: String) -> Result<Self, Self::Error> {
|
|
||||||
value.parse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Application config
|
|
||||||
#[derive(Deserialize, Default, Debug)]
|
|
||||||
struct Config {
|
|
||||||
/// modification time
|
|
||||||
#[serde(skip)]
|
|
||||||
modified: Option<SystemTime>,
|
|
||||||
/// is application active
|
|
||||||
#[serde(default = "default_true")]
|
|
||||||
active: bool,
|
|
||||||
/// need to exit
|
|
||||||
#[serde(default)]
|
|
||||||
exit_now: bool,
|
|
||||||
/// target command
|
|
||||||
command: String,
|
|
||||||
/// target process name
|
|
||||||
#[serde(default)]
|
|
||||||
target: String,
|
|
||||||
/// workdir for launch target command
|
|
||||||
#[serde(default)]
|
|
||||||
workdir: String,
|
|
||||||
/// args for launch target command
|
|
||||||
#[serde(default)]
|
|
||||||
args: Option<Vec<String>>,
|
|
||||||
/// start time
|
|
||||||
start: TimeHM,
|
|
||||||
/// end time
|
|
||||||
end: TimeHM,
|
|
||||||
/// check delay (seconds)
|
|
||||||
delay: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_true() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Config {
|
|
||||||
fn read<P: AsRef<Path>>(path: P) -> Result<Self> {
|
|
||||||
let data = fs::read_to_string(path).map_err(|e| anyhow!("can't read config: {:?}", e))?;
|
|
||||||
toml::from_str(&data).map_err(|e| anyhow!("can't parse config: {:?}", e))
|
|
||||||
}
|
|
||||||
fn file_location() -> Result<PathBuf> {
|
|
||||||
let res = env::current_exe()
|
|
||||||
.map_err(|e| anyhow!("can't get current exe path: {:?}", e))?
|
|
||||||
.with_extension("toml");
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
fn get() -> Self {
|
|
||||||
let path = Config::file_location();
|
|
||||||
if let Err(_e) = path {
|
|
||||||
//println!("{}", _e);
|
|
||||||
return Config::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
let path = path.unwrap();
|
|
||||||
let cfg = Config::read(&path);
|
|
||||||
match cfg {
|
|
||||||
Err(_e) => {
|
|
||||||
//println!("{}", _e);
|
|
||||||
Config::default()
|
|
||||||
}
|
|
||||||
Ok(mut cfg) => {
|
|
||||||
let metadata = fs::metadata(&path);
|
|
||||||
if let Ok(meta) = metadata {
|
|
||||||
if let Ok(time) = meta.modified() {
|
|
||||||
cfg.modified = Some(time);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cfg
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn is_file_modified(&self) -> bool {
|
|
||||||
if self.modified.is_none() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
let path = Config::file_location();
|
|
||||||
if let Err(_e) = path {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let path = path.unwrap();
|
|
||||||
let metadata = fs::metadata(path);
|
|
||||||
let mut result = false;
|
|
||||||
if let Ok(meta) = metadata {
|
|
||||||
if let Ok(time) = meta.modified() {
|
|
||||||
result = self.modified.unwrap() < time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
fn reload(&mut self) {
|
|
||||||
if !self.is_file_modified() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let new_cfg = Self::get();
|
|
||||||
*self = new_cfg;
|
|
||||||
}
|
|
||||||
fn target_name(&self) -> String {
|
|
||||||
if !self.target.is_empty() {
|
|
||||||
self.target.clone()
|
|
||||||
} else {
|
|
||||||
PathBuf::from(self.command.clone())
|
|
||||||
.file_name()
|
|
||||||
.unwrap()
|
|
||||||
.to_os_string()
|
|
||||||
.into_string()
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn is_valid(&self) -> bool {
|
|
||||||
(!self.command.is_empty()) && self.delay > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Laika {
|
struct Laika {
|
||||||
config: Config,
|
config: Config,
|
||||||
@ -179,9 +20,15 @@ impl From<Config> for Laika {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Laika {
|
impl Laika {
|
||||||
fn is_in_time(&self, value: TimeHM) -> bool {
|
fn is_today_enabled(&self) -> bool {
|
||||||
self.config.start <= value && value <= self.config.end
|
(self.config.weekdays.is_today_enabled() && !self.config.exclude_dates.has_today())
|
||||||
|
|| self.config.include_dates.has_today()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_in_time(&self, value: TimeHM) -> bool {
|
||||||
|
self.is_today_enabled() && 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.config.active && self.config.is_valid() && self.is_in_time(now.into())
|
self.config.active && self.config.is_valid() && self.is_in_time(now.into())
|
||||||
@ -194,8 +41,11 @@ impl Laika {
|
|||||||
if name.is_empty() {
|
if name.is_empty() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let sp = System::new_all();
|
let sp = System::new_with_specifics(
|
||||||
let procs = sp.processes_by_exact_name(&name);
|
RefreshKind::nothing()
|
||||||
|
.with_processes(ProcessRefreshKind::nothing().with_exe(UpdateKind::OnlyIfNotSet)),
|
||||||
|
);
|
||||||
|
let procs = sp.processes_by_exact_name(name.as_ref());
|
||||||
procs.count() > 0
|
procs.count() > 0
|
||||||
}
|
}
|
||||||
fn relaunch_target(&self) {
|
fn relaunch_target(&self) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user