+ weekdays + includes/excludes + upd deps
This commit is contained in:
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"]
|
||||
|
||||
use chrono::prelude::*;
|
||||
use chrono::{DateTime, Local, TimeZone};
|
||||
use itertools::Itertools;
|
||||
use serde::Deserialize;
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::time::SystemTime;
|
||||
use std::{thread, time::Duration};
|
||||
use sysinfo::{System, SystemExt};
|
||||
use chrono::{DateTime, Local};
|
||||
use config::Config;
|
||||
use datetime::{HasToday, TimeHM};
|
||||
use std::{process::Command, thread, time::Duration};
|
||||
use sysinfo::{ProcessRefreshKind, RefreshKind, System, UpdateKind};
|
||||
|
||||
use anyhow::{anyhow, Result};
|
||||
use std::{env, fs, path::PathBuf, process::Command};
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
mod config;
|
||||
mod datetime;
|
||||
|
||||
struct Laika {
|
||||
config: Config,
|
||||
@@ -179,9 +20,15 @@ impl From<Config> for Laika {
|
||||
}
|
||||
|
||||
impl Laika {
|
||||
fn is_in_time(&self, value: TimeHM) -> bool {
|
||||
self.config.start <= value && value <= self.config.end
|
||||
fn is_today_enabled(&self) -> bool {
|
||||
(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 {
|
||||
let now: DateTime<Local> = Local::now();
|
||||
self.config.active && self.config.is_valid() && self.is_in_time(now.into())
|
||||
@@ -194,8 +41,11 @@ impl Laika {
|
||||
if name.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let sp = System::new_all();
|
||||
let procs = sp.processes_by_exact_name(&name);
|
||||
let sp = System::new_with_specifics(
|
||||
RefreshKind::nothing()
|
||||
.with_processes(ProcessRefreshKind::nothing().with_exe(UpdateKind::OnlyIfNotSet)),
|
||||
);
|
||||
let procs = sp.processes_by_exact_name(name.as_ref());
|
||||
procs.count() > 0
|
||||
}
|
||||
fn relaunch_target(&self) {
|
||||
|
Reference in New Issue
Block a user