use crate::models;
use crate::utilities::get_allowed_states;
use actix::{Actor, Handler, Message, SyncContext};
use actix_web::{error, Error as actixError};
use chrono::Local;
use diesel::prelude::*;
use diesel::r2d2::{ConnectionManager, Pool};
pub struct DbExecutor(pub Pool<ConnectionManager<SqliteConnection>>);
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
pub struct GpioId {
pub gpio_id: i32,
}
impl Message for GpioId {
type Result = Result<models::Gpio, actixError>;
}
pub struct CheckGpioLevel {
pub gpio_id: i32,
pub gpio_level: String,
}
impl Message for CheckGpioLevel {
type Result = Result<models::Gpio, actixError>;
}
pub struct SetGpioLevel {
pub gpio_id: i32,
pub gpio_level: String,
}
impl Message for SetGpioLevel {
type Result = Result<models::Gpio, actixError>;
}
impl Handler<GpioId> for DbExecutor {
type Result = Result<models::Gpio, actixError>;
fn handle(&mut self, msg: GpioId, _: &mut Self::Context) -> Self::Result {
use crate::schema::gpio_state::dsl::*;
let connection = &self
.0
.get()
.map_err(|_| error::ErrorInternalServerError("Error obtaining database connection"))?;
let mut gpio_vec = gpio_state
.filter(gpio_id.eq(msg.gpio_id))
.load::<models::Gpio>(connection)
.map_err(|_| error::ErrorInternalServerError("Error loading from database"))?;
gpio_vec.pop().ok_or({
error::ErrorNotFound(format!(
"raspberry-web has not been configured to work with GPIO #{}",
msg.gpio_id
))
})
}
}
impl Handler<CheckGpioLevel> for DbExecutor {
type Result = Result<models::Gpio, actixError>;
fn handle(&mut self, msg: CheckGpioLevel, _: &mut Self::Context) -> Self::Result {
let required_gpio_mode = "output";
use crate::schema::gpio_state::dsl::*;
let connection = &self
.0
.get()
.map_err(|_| error::ErrorInternalServerError("Error obtaining database connection"))?;
let gpio_before = gpio_state
.filter(gpio_id.eq(msg.gpio_id))
.load::<models::Gpio>(connection)
.map_err(|_| error::ErrorInternalServerError("Error loading from database"))?
.pop()
.ok_or({
error::ErrorNotFound(format!(
"raspberry-web has not been configured to work with GPIO #{}",
msg.gpio_id
))
})?;
let bool_in_use = gpio_before.in_use == 1;
if !bool_in_use {
info!("GPIO #{} is not in use.", msg.gpio_id);
return Err(error::ErrorForbidden(format!(
"GPIO #{} is not in use.",
msg.gpio_id
)));
}
let none_replacement = "".to_string();
let gpio_mode_before = gpio_before.gpio_mode.as_ref().unwrap_or(&none_replacement);
if gpio_mode_before != required_gpio_mode {
let message = format!(
"Level '{}' is not allowed for mode '{}'",
msg.gpio_level, gpio_mode_before
);
info!("{}", message);
return Err(error::ErrorForbidden(message));
}
let desired_level = msg.gpio_level.to_lowercase();
let state_map = get_allowed_states(connection, "level")
.map_err(|_| error::ErrorInternalServerError("Error loading from database"))?;
let allowed = state_map
.get::<str>(&desired_level)
.ok_or(error::ErrorNotFound(format!(
"Level '{}' is not a recognized GPIO state'",
desired_level
)))?;
if !allowed {
info!(
"Level '{}' is not an allowed state for GPIO #{}",
desired_level, msg.gpio_id
);
Err(error::ErrorForbidden("State not allowed"))?
}
Ok(gpio_before)
}
}
impl Handler<SetGpioLevel> for DbExecutor {
type Result = Result<models::Gpio, actixError>;
fn handle(&mut self, msg: SetGpioLevel, _: &mut Self::Context) -> Self::Result {
use crate::schema::gpio_state::dsl::*;
let connection = &self
.0
.get()
.map_err(|_| error::ErrorInternalServerError("Error obtaining database connection"))?;
let target = gpio_state.filter(gpio_id.eq(msg.gpio_id));
let _result = diesel::update(target)
.set((
last_change.eq(Local::now().naive_local().to_string()),
gpio_level.eq(msg.gpio_level.to_lowercase()),
))
.execute(connection);
let mut gpio_vec_after = gpio_state
.filter(gpio_id.eq(msg.gpio_id))
.load::<models::Gpio>(connection)
.map_err(|_| error::ErrorInternalServerError("Error loading from database"))?;
gpio_vec_after.pop().ok_or(
error::ErrorInternalServerError("Could not connect to database"),
)
}
}