Implement updating program from MQTT
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
25cf94abad
commit
1cc4caae60
@ -1,5 +1,5 @@
|
||||
use eyre::Result;
|
||||
use sprinklers_core::model::{ProgramId, ProgramRef, ProgramUpdateData, Programs};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::{mpsc, oneshot, watch};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -17,6 +17,20 @@ pub struct StateManager {
|
||||
programs_watch: watch::Receiver<Programs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum StateError {
|
||||
#[error("no such program: {0}")]
|
||||
NoSuchProgram(ProgramId),
|
||||
#[error("internal error: {0}")]
|
||||
Other(
|
||||
#[from]
|
||||
#[source]
|
||||
eyre::Report,
|
||||
),
|
||||
}
|
||||
|
||||
pub type Result<T, E = StateError> = std::result::Result<T, E>;
|
||||
|
||||
impl StateManager {
|
||||
pub fn new(
|
||||
request_tx: mpsc::Sender<Request>,
|
||||
@ -40,8 +54,8 @@ impl StateManager {
|
||||
update,
|
||||
resp_tx,
|
||||
})
|
||||
.await?;
|
||||
resp_rx.await?
|
||||
.await.map_err(eyre::Report::from)?;
|
||||
resp_rx.await.map_err(eyre::Report::from)?
|
||||
}
|
||||
|
||||
pub fn get_programs(&self) -> watch::Receiver<Programs> {
|
||||
|
@ -9,6 +9,7 @@ use sprinklers_core::{
|
||||
|
||||
use eyre::Result;
|
||||
use rusqlite::{params, Row, ToSql, Transaction, NO_PARAMS};
|
||||
use thiserror::Error;
|
||||
|
||||
type SqlProgramSequence = SqlJson<ProgramSequence>;
|
||||
type SqlSchedule = SqlJson<Schedule>;
|
||||
@ -99,6 +100,10 @@ fn sequence_as_sql<'a>(
|
||||
.map(move |(seq_num, item)| item_as_sql(item, program_id, seq_num))
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Error)]
|
||||
#[error("no such program id: {0}")]
|
||||
pub struct NoSuchProgram(pub ProgramId);
|
||||
|
||||
pub fn query_programs(conn: &DbConn) -> Result<Programs> {
|
||||
let query_sql = "\
|
||||
SELECT p.id, p.name, p.enabled, p.schedule, ps.sequence
|
||||
@ -121,7 +126,12 @@ FROM programs AS p
|
||||
INNER JOIN program_sequences AS ps ON ps.program_id = p.id
|
||||
WHERE p.id = ?1;";
|
||||
let mut statement = conn.prepare_cached(query_sql)?;
|
||||
Ok(statement.query_row(params![id], from_sql)?)
|
||||
statement
|
||||
.query_row(params![id], from_sql)
|
||||
.map_err(|err| match err {
|
||||
rusqlite::Error::QueryReturnedNoRows => NoSuchProgram(id).into(),
|
||||
e => e.into(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn update_program(
|
||||
@ -137,8 +147,12 @@ UPDATE programs
|
||||
enabled = ifnull(?3, enabled),
|
||||
schedule = ifnull(?4, schedule)
|
||||
WHERE id = ?1;";
|
||||
conn.prepare_cached(update_sql)?
|
||||
let updated = conn
|
||||
.prepare_cached(update_sql)?
|
||||
.execute(&update_as_sql(id, prog))?;
|
||||
if updated == 0 {
|
||||
return Err(NoSuchProgram(id).into());
|
||||
}
|
||||
if let Some(sequence) = &prog.sequence {
|
||||
let clear_seq_sql = "\
|
||||
DELETE
|
||||
|
@ -14,7 +14,7 @@ actix = { version = "0.10.0", default-features = false }
|
||||
eyre = "0.6.0"
|
||||
rumqttc = "0.1.0"
|
||||
tracing = "0.1.19"
|
||||
serde = { version = "1.0.116", features = ["derive"] }
|
||||
serde = { version = "1.0.116", features = ["derive", "rc"] }
|
||||
serde_json = "1.0.57"
|
||||
chrono = "0.4.15"
|
||||
num-traits = "0.2.12"
|
||||
|
@ -36,7 +36,7 @@ impl MqttActor {
|
||||
};
|
||||
let rid = request_value.rid;
|
||||
let request_fut =
|
||||
serde_json::from_value::<request::Request>(request_value.rest).map(|mut request| {
|
||||
serde_json::from_value::<request::Request>(request_value.rest).map(|request| {
|
||||
debug!(rid, "about to execute request: {:?}", request);
|
||||
request.execute(&mut self.request_context)
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
use sprinklers_actors::{program_runner::ProgramRunner, section_runner::SectionRunner};
|
||||
use sprinklers_actors::{ProgramRunner, SectionRunner, StateManager};
|
||||
use sprinklers_core::model::Sections;
|
||||
|
||||
use futures_util::{ready, FutureExt};
|
||||
@ -13,6 +13,7 @@ pub struct RequestContext {
|
||||
pub sections: Sections,
|
||||
pub section_runner: SectionRunner,
|
||||
pub program_runner: ProgramRunner,
|
||||
pub state_manager: StateManager,
|
||||
}
|
||||
|
||||
type BoxFuture<Output> = Pin<Box<dyn Future<Output = Output>>>;
|
||||
@ -190,11 +191,12 @@ type RequestFuture<Ok = ResponseValue> = BoxFuture<RequestResult<Ok>>;
|
||||
trait IRequest {
|
||||
type Response: Serialize;
|
||||
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response>;
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response>;
|
||||
|
||||
fn exec_erased(&mut self, ctx: &mut RequestContext) -> RequestFuture
|
||||
fn exec_erased(self, ctx: &mut RequestContext) -> RequestFuture
|
||||
where
|
||||
Self::Response: 'static,
|
||||
Self: Sized,
|
||||
{
|
||||
// TODO: figure out how to get rid of this nested box
|
||||
Box::pin(ErasedRequestFuture(self.exec(ctx)))
|
||||
@ -263,12 +265,13 @@ pub enum Request {
|
||||
PauseSectionRunner(sections::PauseSectionRunnerRequest),
|
||||
RunProgram(programs::RunProgramRequest),
|
||||
CancelProgram(programs::CancelProgramRequest),
|
||||
UpdateProgram(programs::UpdateProgramRequest),
|
||||
}
|
||||
|
||||
impl IRequest for Request {
|
||||
type Response = ResponseValue;
|
||||
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture {
|
||||
match self {
|
||||
Request::RunSection(req) => req.exec_erased(ctx),
|
||||
Request::CancelSection(req) => req.exec_erased(ctx),
|
||||
@ -276,12 +279,13 @@ impl IRequest for Request {
|
||||
Request::PauseSectionRunner(req) => req.exec_erased(ctx),
|
||||
Request::RunProgram(req) => req.exec_erased(ctx),
|
||||
Request::CancelProgram(req) => req.exec_erased(ctx),
|
||||
Request::UpdateProgram(req) => req.exec_erased(ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Request {
|
||||
pub fn execute(&mut self, ctx: &mut RequestContext) -> impl Future<Output = Response> {
|
||||
pub fn execute(self, ctx: &mut RequestContext) -> impl Future<Output = Response> {
|
||||
self.exec(ctx).map(Response::from)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use sprinklers_actors::program_runner::ProgramRunnerError;
|
||||
use sprinklers_core::model::ProgramId;
|
||||
use sprinklers_actors::{program_runner::Error, state_manager::StateError};
|
||||
use sprinklers_core::model::{ProgramId, ProgramRef, ProgramUpdateData};
|
||||
|
||||
use eyre::WrapErr;
|
||||
|
||||
@ -13,7 +13,7 @@ pub struct RunProgramRequest {
|
||||
impl IRequest for RunProgramRequest {
|
||||
type Response = ResponseMessage;
|
||||
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut program_runner = ctx.program_runner.clone();
|
||||
let program_id = self.program_id;
|
||||
Box::pin(async move {
|
||||
@ -22,7 +22,7 @@ impl IRequest for RunProgramRequest {
|
||||
"running program '{}'",
|
||||
program.name
|
||||
))),
|
||||
Err(e @ ProgramRunnerError::InvalidProgramId(_)) => Err(RequestError::with_name(
|
||||
Err(e @ Error::InvalidProgramId(_)) => Err(RequestError::with_name(
|
||||
ErrorCode::NoSuchProgram,
|
||||
e,
|
||||
"program",
|
||||
@ -42,7 +42,7 @@ pub struct CancelProgramRequest {
|
||||
impl IRequest for CancelProgramRequest {
|
||||
type Response = ResponseMessage;
|
||||
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut program_runner = ctx.program_runner.clone();
|
||||
let program_id = self.program_id;
|
||||
Box::pin(async move {
|
||||
@ -64,3 +64,43 @@ impl IRequest for CancelProgramRequest {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateProgramRequest {
|
||||
program_id: ProgramId,
|
||||
data: ProgramUpdateData,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct UpdateProgramResponse {
|
||||
message: String,
|
||||
data: ProgramRef,
|
||||
}
|
||||
|
||||
impl IRequest for UpdateProgramRequest {
|
||||
type Response = UpdateProgramResponse;
|
||||
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut state_manager = ctx.state_manager.clone();
|
||||
Box::pin(async move {
|
||||
let new_program = state_manager
|
||||
.update_program(self.program_id, self.data)
|
||||
.await
|
||||
.map_err(|err| match err {
|
||||
e @ StateError::NoSuchProgram(_) => RequestError::with_name_and_cause(
|
||||
ErrorCode::NoSuchProgram,
|
||||
"could not update program",
|
||||
"program",
|
||||
e,
|
||||
),
|
||||
e => RequestError::from(eyre::Report::from(e)),
|
||||
})?;
|
||||
Ok(UpdateProgramResponse {
|
||||
message: format!("updated program '{}'", new_program.name),
|
||||
data: new_program,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ pub struct RunSectionResponse {
|
||||
|
||||
impl IRequest for RunSectionRequest {
|
||||
type Response = RunSectionResponse;
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut section_runner = ctx.section_runner.clone();
|
||||
let section = self.section_id.get_section(&ctx.sections);
|
||||
let duration = self.duration;
|
||||
@ -69,7 +69,7 @@ pub struct CancelSectionResponse {
|
||||
|
||||
impl IRequest for CancelSectionRequest {
|
||||
type Response = CancelSectionResponse;
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut section_runner = ctx.section_runner.clone();
|
||||
let section = self.section_id.get_section(&ctx.sections);
|
||||
Box::pin(async move {
|
||||
@ -104,12 +104,11 @@ pub struct CancelSectionRunIdResponse {
|
||||
|
||||
impl IRequest for CancelSectionRunIdRequest {
|
||||
type Response = ResponseMessage;
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut section_runner = ctx.section_runner.clone();
|
||||
let run_id = self.run_id.clone();
|
||||
Box::pin(async move {
|
||||
let cancelled = section_runner
|
||||
.cancel_run(run_id)
|
||||
.cancel_run(self.run_id)
|
||||
.await
|
||||
.wrap_err("could not cancel section run")?;
|
||||
if cancelled {
|
||||
@ -140,7 +139,7 @@ pub struct PauseSectionRunnerResponse {
|
||||
|
||||
impl IRequest for PauseSectionRunnerRequest {
|
||||
type Response = PauseSectionRunnerResponse;
|
||||
fn exec(&mut self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
fn exec(self, ctx: &mut RequestContext) -> RequestFuture<Self::Response> {
|
||||
let mut section_runner = ctx.section_runner.clone();
|
||||
let paused = self.paused;
|
||||
Box::pin(async move {
|
||||
|
@ -5,11 +5,22 @@ use sprinklers_actors::{
|
||||
};
|
||||
|
||||
use actix::{fut::wrap_future, Actor, ActorContext, Addr, AsyncContext, Handler, StreamHandler};
|
||||
use tokio::sync::broadcast;
|
||||
use sprinklers_core::model::Programs;
|
||||
use tokio::sync::{broadcast, watch};
|
||||
use tracing::{trace, warn};
|
||||
|
||||
struct UpdateListenerActor {
|
||||
mqtt_interface: MqttInterface,
|
||||
has_published_program_states: bool,
|
||||
}
|
||||
|
||||
impl UpdateListenerActor {
|
||||
fn new(mqtt_interface: MqttInterface) -> Self {
|
||||
Self {
|
||||
mqtt_interface,
|
||||
has_published_program_states: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for UpdateListenerActor {
|
||||
@ -101,6 +112,35 @@ impl StreamHandler<SecRunnerState> for UpdateListenerActor {
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Programs> for UpdateListenerActor {
|
||||
fn handle(&mut self, programs: Programs, ctx: &mut Self::Context) {
|
||||
let mut mqtt_interface = self.mqtt_interface.clone();
|
||||
|
||||
let has_published_program_states = self.has_published_program_states;
|
||||
self.has_published_program_states = true;
|
||||
|
||||
let fut = async move {
|
||||
if let Err(err) = mqtt_interface.publish_programs(&programs).await {
|
||||
warn!("could not publish programs: {:?}", err);
|
||||
}
|
||||
// Some what of a hack
|
||||
// Initialize program running states to false the first time we
|
||||
// receive programs
|
||||
if !has_published_program_states {
|
||||
for program_id in programs.keys() {
|
||||
if let Err(err) = mqtt_interface
|
||||
.publish_program_running(*program_id, false)
|
||||
.await
|
||||
{
|
||||
warn!("could not publish program running: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
ctx.spawn(wrap_future(fut));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(actix::Message)]
|
||||
#[rtype(result = "()")]
|
||||
struct Quit;
|
||||
@ -143,13 +183,19 @@ impl Listenable<UpdateListenerActor> for SectionEventRecv {
|
||||
}
|
||||
}
|
||||
|
||||
impl Listenable<UpdateListenerActor> for ProgramEventRecv {
|
||||
impl Listenable<UpdateListenerActor> for SecRunnerStateRecv {
|
||||
fn listen(self, ctx: &mut <UpdateListenerActor as Actor>::Context) {
|
||||
ctx.add_stream(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Listenable<UpdateListenerActor> for SecRunnerStateRecv {
|
||||
impl Listenable<UpdateListenerActor> for watch::Receiver<Programs> {
|
||||
fn listen(self, ctx: &mut <UpdateListenerActor as Actor>::Context) {
|
||||
ctx.add_stream(self);
|
||||
}
|
||||
}
|
||||
|
||||
impl Listenable<UpdateListenerActor> for ProgramEventRecv {
|
||||
fn listen(self, ctx: &mut <UpdateListenerActor as Actor>::Context) {
|
||||
ctx.add_stream(self);
|
||||
}
|
||||
@ -161,7 +207,7 @@ pub struct UpdateListener {
|
||||
|
||||
impl UpdateListener {
|
||||
pub fn start(mqtt_interface: MqttInterface) -> Self {
|
||||
let addr = UpdateListenerActor { mqtt_interface }.start();
|
||||
let addr = UpdateListenerActor::new(mqtt_interface).start();
|
||||
Self { addr }
|
||||
}
|
||||
|
||||
@ -176,14 +222,18 @@ impl UpdateListener {
|
||||
self.listen(section_events);
|
||||
}
|
||||
|
||||
pub fn listen_program_events(&mut self, program_events: ProgramEventRecv) {
|
||||
self.listen(program_events);
|
||||
}
|
||||
|
||||
pub fn listen_section_runner(&mut self, sec_runner_state_recv: SecRunnerStateRecv) {
|
||||
self.listen(sec_runner_state_recv);
|
||||
}
|
||||
|
||||
pub fn listen_programs(&mut self, programs: watch::Receiver<Programs>) {
|
||||
self.listen(programs);
|
||||
}
|
||||
|
||||
pub fn listen_program_events(&mut self, program_events: ProgramEventRecv) {
|
||||
self.listen(program_events);
|
||||
}
|
||||
|
||||
pub async fn quit(self) -> eyre::Result<()> {
|
||||
Ok(self.addr.send(Quit).await?)
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ tokio = "0.2.22"
|
||||
tracing = { version = "0.1.19", features = ["log"] }
|
||||
actix = { version = "0.10.0", default-features = false }
|
||||
actix-rt = "1.1.1"
|
||||
chrono = "0.4.19"
|
||||
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.2.11"
|
||||
|
@ -2,6 +2,7 @@
|
||||
#![warn(clippy::print_stdout)]
|
||||
|
||||
// mod option_future;
|
||||
mod state_manager;
|
||||
|
||||
use sprinklers_actors as actors;
|
||||
use sprinklers_core::section_interface::MockSectionInterface;
|
||||
@ -24,9 +25,9 @@ async fn main() -> Result<()> {
|
||||
info!("Starting sprinklers_rs...");
|
||||
color_eyre::install()?;
|
||||
|
||||
let conn = database::setup_db()?;
|
||||
let db_conn = database::setup_db()?;
|
||||
|
||||
let sections = database::query_sections(&conn)?;
|
||||
let sections = database::query_sections(&db_conn)?;
|
||||
for sec in sections.values() {
|
||||
debug!(section = debug(&sec), "read section");
|
||||
}
|
||||
@ -36,11 +37,9 @@ async fn main() -> Result<()> {
|
||||
let mut section_runner = actors::SectionRunner::new(section_interface);
|
||||
let mut program_runner = actors::ProgramRunner::new(section_runner.clone());
|
||||
|
||||
let programs = database::query_programs(&conn)?;
|
||||
let state_manager = crate::state_manager::StateManagerThread::start(db_conn);
|
||||
|
||||
for prog in programs.values() {
|
||||
debug!(program = debug(&prog), "read program");
|
||||
}
|
||||
program_runner.listen_programs(state_manager.get_programs());
|
||||
|
||||
let mqtt_options = mqtt::Options {
|
||||
broker_host: "localhost".into(),
|
||||
@ -53,12 +52,14 @@ async fn main() -> Result<()> {
|
||||
sections: sections.clone(),
|
||||
section_runner: section_runner.clone(),
|
||||
program_runner: program_runner.clone(),
|
||||
state_manager: state_manager.clone(),
|
||||
};
|
||||
let mut mqtt_interface = mqtt::MqttInterfaceTask::start(mqtt_options, request_context);
|
||||
|
||||
let mut update_listener = mqtt::UpdateListener::start(mqtt_interface.clone());
|
||||
update_listener.listen_section_events(section_runner.subscribe().await?);
|
||||
update_listener.listen_section_runner(section_runner.get_state_recv());
|
||||
update_listener.listen_programs(state_manager.get_programs());
|
||||
update_listener.listen_program_events(program_runner.subscribe().await?);
|
||||
|
||||
program_runner.update_sections(sections.clone()).await?;
|
||||
@ -69,13 +70,6 @@ async fn main() -> Result<()> {
|
||||
.publish_section_state(*section_id, false)
|
||||
.await?;
|
||||
}
|
||||
program_runner.update_programs(programs.clone()).await?;
|
||||
for program_id in programs.keys() {
|
||||
mqtt_interface
|
||||
.publish_program_running(*program_id, false)
|
||||
.await?;
|
||||
}
|
||||
mqtt_interface.publish_programs(&programs).await?;
|
||||
|
||||
info!("sprinklers_rs initialized");
|
||||
|
||||
@ -84,6 +78,7 @@ async fn main() -> Result<()> {
|
||||
|
||||
update_listener.quit().await?;
|
||||
mqtt_interface.quit().await?;
|
||||
drop(state_manager);
|
||||
program_runner.quit().await?;
|
||||
section_runner.quit().await?;
|
||||
actix::System::current().stop();
|
||||
|
103
sprinklers_rs/src/state_manager.rs
Normal file
103
sprinklers_rs/src/state_manager.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use sprinklers_actors::{state_manager, StateManager};
|
||||
use sprinklers_database::{self as database, DbConn};
|
||||
|
||||
use eyre::{eyre, WrapErr};
|
||||
use sprinklers_core::model::{ProgramRef, Programs};
|
||||
use tokio::{
|
||||
runtime,
|
||||
sync::{mpsc, watch},
|
||||
};
|
||||
use tracing::warn;
|
||||
|
||||
pub struct StateManagerThread {
|
||||
db_conn: DbConn,
|
||||
request_rx: mpsc::Receiver<state_manager::Request>,
|
||||
programs_tx: watch::Sender<Programs>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
programs: Programs,
|
||||
}
|
||||
|
||||
impl StateManagerThread {
|
||||
pub fn start(db_conn: DbConn) -> StateManager {
|
||||
let (request_tx, request_rx) = mpsc::channel(8);
|
||||
let (programs_tx, programs_rx) = watch::channel(Programs::default());
|
||||
let task = StateManagerThread {
|
||||
db_conn,
|
||||
request_rx,
|
||||
programs_tx,
|
||||
};
|
||||
let runtime_handle = runtime::Handle::current();
|
||||
std::thread::Builder::new()
|
||||
.name("sprinklers_rs::state_manager".into())
|
||||
.spawn(move || task.run(runtime_handle))
|
||||
.expect("could not start state_manager thread");
|
||||
StateManager::new(request_tx, programs_rx)
|
||||
}
|
||||
|
||||
fn broadcast_programs(&mut self, programs: Programs) {
|
||||
if let Err(err) = self.programs_tx.broadcast(programs) {
|
||||
warn!("could not broadcast programs: {}", err);
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_request(
|
||||
&mut self,
|
||||
request: state_manager::Request,
|
||||
state: &mut State,
|
||||
) -> eyre::Result<()> {
|
||||
use state_manager::Request;
|
||||
|
||||
match request {
|
||||
Request::UpdateProgram {
|
||||
id,
|
||||
update,
|
||||
resp_tx,
|
||||
} => {
|
||||
// HACK: would really like stable try notation
|
||||
let res = (|| -> state_manager::Result<ProgramRef> {
|
||||
let mut trans = self
|
||||
.db_conn
|
||||
.transaction()
|
||||
.wrap_err("failed to start transaction")?;
|
||||
database::update_program(&mut trans, id, &update).map_err(|err| {
|
||||
if let Some(e) = err.downcast_ref::<database::NoSuchProgram>() {
|
||||
state_manager::StateError::NoSuchProgram(e.0)
|
||||
} else {
|
||||
err.into()
|
||||
}
|
||||
})?;
|
||||
let new_program: ProgramRef = database::query_program_by_id(&trans, id)?.into();
|
||||
state.programs.insert(new_program.id, new_program.clone());
|
||||
trans.commit().wrap_err("could not commit transaction")?;
|
||||
self.broadcast_programs(state.programs.clone());
|
||||
Ok(new_program)
|
||||
})();
|
||||
resp_tx
|
||||
.send(res)
|
||||
.map_err(|_| eyre!("could not respond to UpdateProgram"))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn load_state(&mut self) -> eyre::Result<State> {
|
||||
let programs =
|
||||
database::query_programs(&self.db_conn).wrap_err("could not query programs")?;
|
||||
|
||||
Ok(State { programs })
|
||||
}
|
||||
|
||||
fn run(mut self, runtime_handle: runtime::Handle) {
|
||||
let mut state = self.load_state().expect("could not load initial state");
|
||||
|
||||
self.broadcast_programs(state.programs.clone());
|
||||
|
||||
while let Some(request) = runtime_handle.block_on(self.request_rx.recv()) {
|
||||
if let Err(err) = self.handle_request(request, &mut state) {
|
||||
warn!("error handling request: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user