wards 0.1.10

Библиотека для камеры Beward B2530RZQ-LP
Documentation
use std::{
    fmt::{self},
    str::FromStr,
    thread, time,
};

use chrono::{DateTime, Local, NaiveDate};

use crate::{
    camera::{Camera, CameraFeatures},
    errors::WardError,
    parse,
};

/// Разрешенные символы в номере: ABCEHKMOPTXY0-9.
///
/// Формат даты : 2023-11-22.
#[derive(Clone)]
pub struct Car {
    pub number: String,
    pub begin: Option<NaiveDate>,
    pub end: Option<NaiveDate>,
    pub notify: Notify,
    pub note: Option<String>,
}

#[derive(Debug, Clone)]
pub struct Event {
    pub time: Option<DateTime<Local>>,
    pub car_number: String,
    pub status: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub enum Notify {
    On,
    Off,
}

impl FromStr for Notify {
    type Err = String;

    fn from_str(input: &str) -> Result<Notify, Self::Err> {
        match input.to_lowercase().as_str() {
            "on" => Ok(Notify::On),
            "off" => Ok(Notify::Off),
            &_ => Ok(Notify::Off),
        }
    }
}

impl fmt::Display for Notify {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            &Notify::On => write!(f, "on"),
            &Notify::Off => write!(f, "off"),
        }
    }
}

pub trait CarFeatures {
    /// Получает список всех автомобильных номеров в виде вектора объектов Car.
    fn list_cars(&self) -> Result<Vec<Car>, WardError>;

    /// Добавление номера автомобиля в камеру.
    fn add_car(&self, car: &Car) -> Result<(), WardError>;

    /// Добвляет нескольно автомобилей в камеру, возвращает вектор добвленых номеров.
    fn add_cars<'a>(&'a self, cars: &'a Vec<Car>) -> Result<Vec<&str>, WardError>;

    /// Редактирование номера автомобиля в камере.
    fn edit_car(&self, car: &Car) -> Result<(), WardError>;

    /// Удаление номера автомобиля из камеры.
    fn remove_car(&self, number: &str) -> Result<(), WardError>;

    /// Удаление всех автомоибильных номеров за указанную дату.
    ///
    /// Если дата отсуствует, удалются все не сегодняшние номера.
    ///
    /// Возвращает список упешно удаленных автомобильных номеров.
    fn remove_cars(&self, date: Option<NaiveDate>) -> Result<Vec<String>, WardError>;

    /// Получает события считывания номеров камерой.
    /// Формат даты: 2023-11-22 00:00:00.
    fn events(
        &self,
        begin_date: &str,
        end_date: &str,
        stauts: Option<bool>,
    ) -> Result<Vec<Event>, WardError>;
}

impl CarFeatures for Camera {
    fn list_cars(&self) -> Result<Vec<Car>, WardError> {
        let path = "/lnpr_cgi?action=list";

        let response = &self.send_request(&path)?;
        response.check_status_code()?;

        Ok(parse::raw_numbers(&response.body))
    }

    fn add_car(&self, car: &Car) -> Result<(), WardError> {
        let path = build_path("add", car);

        Ok(self.send_request(&path)?.check_status_code()?)
    }

    fn add_cars<'a>(&'a self, cars: &'a Vec<Car>) -> Result<Vec<&str>, WardError> {
        let mut successfully_added_cars = vec![];

        cars.into_iter().for_each(|car| {
            if self.add_car(&car).is_ok() {
                successfully_added_cars.push(car.number.as_str())
            }
        });

        Ok(successfully_added_cars)
    }

    fn edit_car(&self, car: &Car) -> Result<(), WardError> {
        let path = build_path("edit", car);

        Ok(self.send_request(&path)?.check_status_code()?)
    }

    fn remove_car(&self, number: &str) -> Result<(), WardError> {
        let path = format!("/lnpr_cgi?action=remove&Number={}", number);

        Ok(self.send_request(&path)?.check_status_code()?)
    }

    fn remove_cars(&self, date: Option<NaiveDate>) -> Result<Vec<String>, WardError> {
        let mut successfully_removed_cars = vec![];
        let mut cars_to_remove = vec![];

        let cars = self.list_cars()?;

        // Номера авто с датой окончания
        let cars_with_end_date = cars.iter().filter(|car| car.end.is_some());

        cars_with_end_date.into_iter().for_each(|car| match date {
            Some(naive_date) => {
                if Some(naive_date) == car.end {
                    cars_to_remove.push(car);
                }
            }
            None => {
                let now = Local::now();
                let now_date = now.date_naive();
                if Some(now_date) != car.end {
                    cars_to_remove.push(car);
                }
            }
        });

        cars_to_remove.into_iter().for_each(|car| {
            if self.remove_car(&car.number).is_ok() {
                successfully_removed_cars.push(car.clone().number);
                thread::sleep(time::Duration::from_millis(100));
            }
        });

        Ok(successfully_removed_cars)
    }

    fn events(
        &self,
        begin_date: &str,
        end_date: &str,
        status: Option<bool>,
    ) -> Result<Vec<Event>, WardError> {
        let path = format!("/lnprevent_cgi?action=export&Begin={begin_date}&End={end_date}");

        let response = &self.send_request(&path)?;
        response.check_status_code()?;

        Ok(parse::events(&response.body, status)?)
    }
}

/// Создает путь на основе данных объекта Car
///
/// # Examples
///
/// ```rust
/// let path = &Car {
///     number: "111".to_string(),
///     begin: Some(NaiveDate::default()),
///     end: None,
///     notify: Notify::Off,
///     note: Some("Some Comment"),
/// };
///
/// assert_eq!(build_path(path), "/lnpr_cgi?action=add&Number=111&Begin=1970-01-01&End=&Notify=off&Note=Some Comment");
/// ```
fn build_path(action: &str, car: &Car) -> String {
    format!(
        "/lnpr_cgi?action={}&Number={}&Begin={}&End={}&Notify={}&Note={}",
        action,
        car.number,
        parse::str_from_date(car.begin),
        parse::str_from_date(car.end),
        car.notify,
        car.note.as_ref().unwrap_or(&"".to_string())
    )
}

#[cfg(test)]
mod tests {
    use chrono::NaiveDate;

    use super::{build_path, Car, Notify};

    #[test]
    fn building_path() {
        let path = build_path(
            "add",
            &Car {
                number: "111".to_string(),
                begin: Some(NaiveDate::default()),
                end: None,
                notify: Notify::Off,
                note: Some("Some Comment".to_string()),
            },
        );

        assert_eq!(
            path,
            "/lnpr_cgi?action=add&Number=111&Begin=1970-01-01&End=&Notify=off&Note=Some Comment"
        );
    }
}