use chrono::{Datelike, NaiveTime};
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use utoipa::ToSchema;
#[derive(
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, ToSchema,
)]
#[serde(transparent)]
pub struct RuleId(pub i64);
impl RuleId {
pub fn new(id: i64) -> Self {
Self(id)
}
pub fn get(&self) -> i64 {
self.0
}
}
impl From<i64> for RuleId {
fn from(id: i64) -> Self {
Self(id)
}
}
impl From<RuleId> for i64 {
fn from(id: RuleId) -> Self {
id.0
}
}
impl PartialEq<i64> for RuleId {
fn eq(&self, other: &i64) -> bool {
self.0 == *other
}
}
impl fmt::Display for RuleId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for RuleId {
type Err = std::num::ParseIntError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
pub struct ScheduleRule {
pub id: RuleId,
pub name: String,
pub days: Vec<Weekday>,
#[serde(with = "time_format")]
pub start_time: NaiveTime,
#[serde(with = "time_format")]
pub end_time: NaiveTime,
pub action: ScheduleAction,
pub enabled: bool,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
#[serde(tag = "type", content = "value")]
pub enum ScheduleAction {
SpeedLimit(u64),
Unlimited,
Pause,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
impl Weekday {
pub fn from_chrono(wd: chrono::Weekday) -> Self {
use chrono::Weekday as ChronoWd;
match wd {
ChronoWd::Mon => Weekday::Monday,
ChronoWd::Tue => Weekday::Tuesday,
ChronoWd::Wed => Weekday::Wednesday,
ChronoWd::Thu => Weekday::Thursday,
ChronoWd::Fri => Weekday::Friday,
ChronoWd::Sat => Weekday::Saturday,
ChronoWd::Sun => Weekday::Sunday,
}
}
pub fn to_chrono(self) -> chrono::Weekday {
use chrono::Weekday as ChronoWd;
match self {
Weekday::Monday => ChronoWd::Mon,
Weekday::Tuesday => ChronoWd::Tue,
Weekday::Wednesday => ChronoWd::Wed,
Weekday::Thursday => ChronoWd::Thu,
Weekday::Friday => ChronoWd::Fri,
Weekday::Saturday => ChronoWd::Sat,
Weekday::Sunday => ChronoWd::Sun,
}
}
}
#[derive(Clone, Debug)]
pub struct Scheduler {
rules: Vec<ScheduleRule>,
}
impl Scheduler {
pub fn new(rules: Vec<ScheduleRule>) -> Self {
Self { rules }
}
pub fn rules(&self) -> &[ScheduleRule] {
&self.rules
}
pub fn set_rules(&mut self, rules: Vec<ScheduleRule>) {
self.rules = rules;
}
pub fn add_rule(&mut self, rule: ScheduleRule) {
self.rules.push(rule);
}
pub fn remove_rule(&mut self, id: RuleId) -> bool {
let original_len = self.rules.len();
self.rules.retain(|r| r.id != id);
self.rules.len() < original_len
}
pub fn update_rule(&mut self, rule: ScheduleRule) -> bool {
if let Some(existing) = self.rules.iter_mut().find(|r| r.id == rule.id) {
*existing = rule;
true
} else {
false
}
}
pub fn get_current_action(
&self,
now: chrono::DateTime<chrono::Local>,
) -> Option<ScheduleAction> {
let weekday = Weekday::from_chrono(now.weekday());
let time = now.time();
self.rules
.iter()
.find(|r| {
if !r.enabled {
return false;
}
if !r.days.is_empty() && !r.days.contains(&weekday) {
return false;
}
if r.start_time <= r.end_time {
time >= r.start_time && time < r.end_time
} else {
time >= r.start_time || time < r.end_time
}
})
.map(|r| r.action.clone())
}
}
impl Default for Scheduler {
fn default() -> Self {
Self { rules: Vec::new() }
}
}
mod time_format {
use chrono::NaiveTime;
use serde::{Deserialize, Deserializer, Serializer};
pub fn serialize<S>(time: &NaiveTime, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let s = time.format("%H:%M:%S").to_string();
serializer.serialize_str(&s)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<NaiveTime, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
NaiveTime::parse_from_str(&s, "%H:%M:%S").map_err(serde::de::Error::custom)
}
}
#[allow(clippy::unwrap_used, clippy::expect_used)]
#[cfg(test)]
mod tests;