use chrono::{DateTime, Duration, NaiveTime, Timelike, Utc};
use crate::controllers::Controller;
use crate::output::Output;
use crate::scheduler::Scheduler;
use crate::types::Message;
#[derive(Debug)]
pub struct TimedOutput<F>
where F: FnMut(bool) {
name: Option<String>,
output: Output<F>,
start_time: NaiveTime,
duration: Duration,
scheduler: Scheduler,
}
impl<F> TimedOutput<F>
where F: FnMut(bool) {
pub fn new(output: Output<F>, start_time: NaiveTime, duration: Duration) -> Self {
Self {
name: None,
output,
start_time,
duration,
scheduler: Scheduler::new(),
}.schedule_first(None)
}
pub fn new_without_scheduled(output: Output<F>, start_time: NaiveTime, duration: Duration) -> Self {
Self {
name: None,
output,
start_time,
duration,
scheduler: Scheduler::new(),
}
}
pub fn schedule_first<T>(mut self, time: T) -> Self
where T: Into<Option<DateTime<Utc>>>
{
self.schedule_on(time);
self
}
fn schedule_on<T>(&mut self, time: T)
where T: Into<Option<DateTime<Utc>>>
{
let mut time= time.into().unwrap_or_else(|| Utc::now());
let current_time = time.naive_utc().time();
time = time.with_hour(self.start_time.hour()).unwrap();
time = time.with_minute(self.start_time.minute()).unwrap();
time = time.with_second(self.start_time.second()).unwrap();
time = time.with_nanosecond(0).unwrap();
let start_time = if current_time < self.start_time {
time
} else {
time + Duration::days(1)
};
self.scheduler.schedule_on(start_time);
}
fn schedule_off<T>(&mut self, time: T)
where T: Into<Option<DateTime<Utc>>>
{
let mut time= time.into().unwrap_or_else(|| Utc::now());
time = time.with_hour(self.start_time.hour()).unwrap();
time = time.with_minute(self.start_time.minute()).unwrap();
time = time.with_second(self.start_time.second()).unwrap();
time = time.with_nanosecond(0).unwrap();
let end_time = time + self.duration;
self.scheduler.schedule_off(end_time);
}
}
impl<F> Controller for TimedOutput<F>
where F: FnMut(bool) {
fn set_name(&mut self, name: String) {
self.name = Some(name);
}
fn get_name(&self) -> Option<String> {
self.name.clone()
}
fn poll(&mut self, time: DateTime<Utc>) -> Option<Message> {
if let Some(event) = self.scheduler.attempt_execution(time) {
let msg = match event.get_action() {
crate::types::Action::On => {
self.output.activate();
self.schedule_off(time);
"Activated"
},
crate::types::Action::Off => {
self.output.deactivate();
self.schedule_on(time);
"Deactivated"
},
_ => {
panic!("Invalid action for timed output")
}
};
return Some(Message::new(
self.get_name().unwrap_or_default(),
String::from(msg),
time,
None,
))
}
None
}
}
impl Default for TimedOutput<fn(bool)> {
fn default() -> Self {
Self::new_without_scheduled(Output::default(), NaiveTime::from_hms_opt(0, 0, 0).unwrap(), Duration::seconds(0))
}
}
#[cfg(test)]
mod tests {
use chrono::TimeZone;
use super::*;
#[test]
fn test_new() {
let time = NaiveTime::from_hms_opt(5, 0, 0).unwrap();
let duration = Duration::hours(8);
let output = TimedOutput::new_without_scheduled(
Output::default(),
time,
duration,
);
assert_eq!(output.output.get_state(), None);
}
#[test]
fn test_get_set_name() {
let mut controller = TimedOutput::default();
assert_eq!(controller.get_name(), None);
controller.set_name(String::from("test"));
assert_eq!(controller.get_name(), Some(String::from("test")));
}
#[test]
fn test_poll() {
let time = Utc.with_ymd_and_hms(2021, 1, 1, 4, 59, 59).unwrap();
let start_time = NaiveTime::from_hms_opt(5, 0, 0).unwrap();
let duration = Duration::hours(12);
let mut output = TimedOutput::new_without_scheduled(
Output::default(),
start_time,
duration,
).schedule_first(time);
assert_eq!(output.output.get_state(), None);
output.output.deactivate();
let message = output.poll(time);
assert_eq!(output.output.get_state().unwrap(), false);
assert!(message.is_none());
let time = time + Duration::seconds(1);
let message = output.poll(time);
assert_eq!(output.output.get_state().unwrap(), true);
assert!(message.is_some());
assert!(message.as_ref().unwrap().get_read_state().is_none());
assert_eq!(message.as_ref().unwrap().get_content(), "Activated");
let time = time + Duration::hours(6);
let message = output.poll(time);
assert_eq!(output.output.get_state().unwrap(), true);
assert!(message.is_none());
let time = time + Duration::hours(6) - Duration::seconds(1);
let message = output.poll(time);
assert_eq!(output.output.get_state().unwrap(), true);
assert!(message.is_none());
let time = time + Duration::seconds(1);
let message = output.poll(time);
assert_eq!(output.output.get_state().unwrap(), false);
assert!(message.is_some());
assert!(message.as_ref().unwrap().get_read_state().is_none());
assert_eq!(message.as_ref().unwrap().get_content(), "Deactivated");
let time = time + Duration::seconds(1);
let message = output.poll(time);
assert_eq!(output.output.get_state().unwrap(), false);
assert!(message.is_none());
}
}