Refactor websocket
This commit is contained in:
parent
ebac452a86
commit
f3d367e479
22 changed files with 924 additions and 773 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -6,5 +6,5 @@
|
|||
|
||||
/emgauwa-*.sqlite
|
||||
/emgauwa-*.sqlite-*
|
||||
/emgauwa-*.json
|
||||
/emgauwa-*.cfg.*
|
||||
|
||||
|
|
|
|||
151
Cargo.lock
generated
151
Cargo.lock
generated
|
|
@ -1,6 +1,6 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "actix"
|
||||
|
|
@ -89,7 +89,7 @@ dependencies = [
|
|||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"sha1",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
|
|
@ -213,24 +213,6 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-actors"
|
||||
version = "4.3.1+deprecated"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f98c5300b38fd004fe7d2a964f9a90813fdbe8a81fed500587e78b1b71c6f980"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-codec",
|
||||
"actix-http",
|
||||
"actix-web",
|
||||
"bytes",
|
||||
"bytestring",
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-codegen"
|
||||
version = "4.3.0"
|
||||
|
|
@ -243,6 +225,20 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-ws"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3a1fb4f9f2794b0aadaf2ba5f14a6f034c7e86957b458c506a8cb75953f2d99"
|
||||
dependencies = [
|
||||
"actix-codec",
|
||||
"actix-http",
|
||||
"actix-web",
|
||||
"bytestring",
|
||||
"futures-core",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix_derive"
|
||||
version = "0.6.2"
|
||||
|
|
@ -276,7 +272,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
"zerocopy",
|
||||
|
|
@ -558,7 +554,7 @@ version = "0.1.16"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
"once_cell",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
|
@ -813,6 +809,7 @@ dependencies = [
|
|||
"serde_with",
|
||||
"simple_logger",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
|
@ -823,11 +820,12 @@ dependencies = [
|
|||
"actix",
|
||||
"actix-cors",
|
||||
"actix-web",
|
||||
"actix-web-actors",
|
||||
"actix-ws",
|
||||
"emgauwa-common",
|
||||
"futures",
|
||||
"itertools",
|
||||
"log",
|
||||
"rand 0.9.2",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
|
|
@ -1035,7 +1033,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "26145e563e54f2cadc477553f1ec5ee650b00862f0a58bcd12cbdc5f0ea2d2f4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.7+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1539,7 +1549,7 @@ checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
|
|||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
|
|
@ -1555,7 +1565,7 @@ dependencies = [
|
|||
"num-integer",
|
||||
"num-iter",
|
||||
"num-traits",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"smallvec",
|
||||
"zeroize",
|
||||
]
|
||||
|
|
@ -1803,6 +1813,12 @@ dependencies = [
|
|||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
|
|
@ -1810,8 +1826,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_chacha 0.3.1",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.9.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1"
|
||||
dependencies = [
|
||||
"rand_chacha 0.9.0",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1821,7 +1847,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core 0.9.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1830,7 +1866,16 @@ version = "0.6.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38"
|
||||
dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1902,7 +1947,7 @@ dependencies = [
|
|||
"num-traits",
|
||||
"pkcs1",
|
||||
"pkcs8",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
"signature",
|
||||
"spki",
|
||||
"subtle",
|
||||
|
|
@ -2123,7 +2168,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"rand_core",
|
||||
"rand_core 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2305,7 +2350,7 @@ dependencies = [
|
|||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1",
|
||||
|
|
@ -2343,7 +2388,7 @@ dependencies = [
|
|||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"rand",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
|
|
@ -2531,9 +2576,21 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
"signal-hook-registry",
|
||||
"socket2",
|
||||
"tokio-macros",
|
||||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
|
|
@ -2751,7 +2808,7 @@ version = "1.11.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"getrandom 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -2782,6 +2839,24 @@ version = "0.11.0+wasi-snapshot-preview1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.7+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "883478de20367e224c0090af9cf5f9fa85bed63a95c1abf3afc5c083ebc06e8c"
|
||||
dependencies = [
|
||||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasip2"
|
||||
version = "1.0.1+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7"
|
||||
dependencies = [
|
||||
"wit-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
|
|
@ -3036,6 +3111,12 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen"
|
||||
version = "0.46.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59"
|
||||
|
||||
[[package]]
|
||||
name = "write16"
|
||||
version = "1.0.0"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ emgauwa-common = { git = "https://git.serguzim.me/emgauwa/common.git" }
|
|||
|
||||
actix = "0.13"
|
||||
actix-web = "4.4"
|
||||
actix-web-actors = "4.2"
|
||||
actix-ws = "0.3"
|
||||
actix-cors = "0.7"
|
||||
|
||||
utoipa = "5.3"
|
||||
|
|
@ -26,4 +26,5 @@ serde_derive = "1.0"
|
|||
sqlx = { version = "0.8", features = ["sqlite", "runtime-tokio", "macros"] }
|
||||
|
||||
futures = "0.3"
|
||||
tokio = { version = "1.36", features = ["rt", "rt-multi-thread"] }
|
||||
tokio = { version = "1.36", features = ["macros", "rt", "rt-multi-thread"] }
|
||||
rand = "0.9.2"
|
||||
|
|
|
|||
6
Makefile
6
Makefile
|
|
@ -1,14 +1,14 @@
|
|||
build-rpi:
|
||||
cross build --target arm-unknown-linux-gnueabihf
|
||||
|
||||
emgauwa-core.json: core.pkl
|
||||
emgauwa-core.cfg.json: core.pkl
|
||||
pkl eval -f json -o $@ $<
|
||||
|
||||
config:
|
||||
$(MAKE) emgauwa-core.json
|
||||
$(MAKE) emgauwa-core.cfg.json
|
||||
|
||||
clean:
|
||||
rm -f emgauwa-core.json
|
||||
rm -f emgauwa-core.cfg.*
|
||||
rm -f emgauwa-core.sqlite
|
||||
|
||||
emgauwa-core_%:
|
||||
|
|
|
|||
8
core.pkl
8
core.pkl
|
|
@ -1,7 +1,11 @@
|
|||
amends "package://emgauwa.app/pkl/emgauwa@0.2.1#/core.pkl"
|
||||
amends "package://emgauwa.app/pkl/emgauwa@0.2.3#/core.pkl"
|
||||
|
||||
database = "sqlite://emgauwa-core.sqlite"
|
||||
|
||||
server {
|
||||
token = "dev tokenx"
|
||||
host = "127.0.0.1"
|
||||
port = 4419
|
||||
token = "dev token"
|
||||
}
|
||||
|
||||
logging {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
variable "REG" {
|
||||
default = "registry.serguzim.me"
|
||||
default = "rg.nl-ams.scw.cloud/public.serguzim.net"
|
||||
}
|
||||
variable "REPO" {
|
||||
default = "emgauwa/core"
|
||||
|
|
|
|||
14
run-coverage.sh
Executable file
14
run-coverage.sh
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
export RUSTFLAGS="-C instrument-coverage"
|
||||
|
||||
cargo run
|
||||
|
||||
llvm-profdata merge -sparse default_*.profraw -o emgauwa-core.profdata
|
||||
|
||||
llvm-cov report \
|
||||
--object ./target/debug/emgauwa-core \
|
||||
--instr-profile=emgauwa-core.profdata \
|
||||
--ignore-filename-regex="${CARGO_HOME:-.cargo/registry}" \
|
||||
--use-color \
|
||||
--summary-only
|
||||
189
src/app_state.rs
189
src/app_state.rs
|
|
@ -1,189 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use actix::{Actor, Addr, Context, Handler, Message, Recipient};
|
||||
use emgauwa_common::db::DbController;
|
||||
use emgauwa_common::errors::EmgauwaError;
|
||||
use emgauwa_common::models::{convert_db_list, Controller, Relay};
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RelayStates};
|
||||
use futures::executor::block_on;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::handlers::v1::ws::relays::{RelaysWs, SendRelays};
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(), EmgauwaError>")]
|
||||
pub struct DisconnectController {
|
||||
pub controller_uid: EmgauwaUid,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(), EmgauwaError>")]
|
||||
pub struct ConnectController {
|
||||
pub address: Recipient<ControllerWsAction>,
|
||||
pub controller: Controller,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct UpdateRelayStates {
|
||||
pub controller_uid: EmgauwaUid,
|
||||
pub relay_states: RelayStates,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<Vec<Relay>, EmgauwaError>")]
|
||||
pub struct GetRelays {}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "Result<(), EmgauwaError>")]
|
||||
pub struct Action {
|
||||
pub controller_uid: EmgauwaUid,
|
||||
pub action: ControllerWsAction,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct ConnectRelayClient {
|
||||
pub addr: Addr<RelaysWs>,
|
||||
}
|
||||
|
||||
pub struct AppState {
|
||||
pub pool: Pool<Sqlite>,
|
||||
pub connected_controllers: HashMap<EmgauwaUid, (Controller, Recipient<ControllerWsAction>)>,
|
||||
pub connected_relay_clients: Vec<Addr<RelaysWs>>,
|
||||
}
|
||||
|
||||
impl AppState {
|
||||
pub fn new(pool: Pool<Sqlite>) -> AppState {
|
||||
AppState {
|
||||
pool,
|
||||
connected_controllers: HashMap::new(),
|
||||
connected_relay_clients: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_relays(&self) -> Result<Vec<Relay>, EmgauwaError> {
|
||||
let mut pool_conn = block_on(self.pool.acquire())?;
|
||||
let db_controllers = block_on(DbController::get_all(&mut pool_conn))?;
|
||||
let mut controllers: Vec<Controller> = convert_db_list(&mut pool_conn, db_controllers)?;
|
||||
|
||||
self.connected_controllers
|
||||
.iter()
|
||||
.for_each(|(uid, (connected_controller, _))| {
|
||||
if let Some(c) = controllers.iter_mut().find(|c| c.c.uid == *uid) {
|
||||
c.apply_relay_states(&connected_controller.get_relay_states());
|
||||
}
|
||||
});
|
||||
|
||||
let mut relays: Vec<Relay> = Vec::new();
|
||||
controllers.iter().for_each(|c| {
|
||||
relays.extend(c.relays.clone());
|
||||
});
|
||||
|
||||
Ok(relays)
|
||||
}
|
||||
|
||||
fn notify_relay_clients(&mut self) {
|
||||
self.connected_relay_clients.retain(|addr| addr.connected());
|
||||
|
||||
match self.get_relays() {
|
||||
Ok(relays) => match serde_json::to_string(&relays) {
|
||||
Ok(json) => {
|
||||
self.connected_relay_clients.iter_mut().for_each(|addr| {
|
||||
let relays_json = json.clone();
|
||||
addr.do_send(SendRelays { relays_json });
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Failed to serialize relays: {:?}", err);
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
log::error!("Failed to get relays: {:?}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
impl Actor for AppState {
|
||||
type Context = Context<Self>;
|
||||
}
|
||||
|
||||
impl Handler<DisconnectController> for AppState {
|
||||
type Result = Result<(), EmgauwaError>;
|
||||
|
||||
fn handle(&mut self, msg: DisconnectController, _ctx: &mut Self::Context) -> Self::Result {
|
||||
let mut pool_conn = block_on(self.pool.acquire())?;
|
||||
|
||||
if let Some((controller, address)) = self.connected_controllers.remove(&msg.controller_uid)
|
||||
{
|
||||
if let Err(err) = block_on(controller.c.update_active(&mut pool_conn, false)) {
|
||||
log::error!(
|
||||
"Failed to mark controller {} as inactive: {:?}",
|
||||
controller.c.uid,
|
||||
err
|
||||
);
|
||||
}
|
||||
// TODO: why does the block_on(send()) version not return? The AppState will be stuck.
|
||||
//block_on(address.send(ControllerWsAction::Disconnect))??;
|
||||
address.do_send(ControllerWsAction::Disconnect);
|
||||
}
|
||||
self.notify_relay_clients();
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ConnectController> for AppState {
|
||||
type Result = Result<(), EmgauwaError>;
|
||||
|
||||
fn handle(&mut self, msg: ConnectController, _ctx: &mut Self::Context) -> Self::Result {
|
||||
log::debug!("Connecting controller: {}", msg.controller.c.uid);
|
||||
self.connected_controllers
|
||||
.insert(msg.controller.c.uid.clone(), (msg.controller, msg.address));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<UpdateRelayStates> for AppState {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: UpdateRelayStates, _ctx: &mut Self::Context) -> Self::Result {
|
||||
if let Some((controller, _)) = self.connected_controllers.get_mut(&msg.controller_uid) {
|
||||
controller.apply_relay_states(&msg.relay_states);
|
||||
}
|
||||
self.notify_relay_clients();
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<GetRelays> for AppState {
|
||||
type Result = Result<Vec<Relay>, EmgauwaError>;
|
||||
|
||||
fn handle(&mut self, _msg: GetRelays, _ctx: &mut Self::Context) -> Self::Result {
|
||||
self.get_relays()
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<Action> for AppState {
|
||||
type Result = Result<(), EmgauwaError>;
|
||||
|
||||
fn handle(&mut self, msg: Action, _ctx: &mut Self::Context) -> Self::Result {
|
||||
log::debug!("Forwarding action: {:?}", msg.action);
|
||||
if let Some((_, address)) = self.connected_controllers.get(&msg.controller_uid) {
|
||||
// TODO: why does the block_on(send()) version not return? The AppState will be stuck.
|
||||
//block_on(address.send(msg.action))?
|
||||
address.do_send(msg.action);
|
||||
Ok(())
|
||||
} else {
|
||||
Err(EmgauwaError::Connection(msg.controller_uid))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<ConnectRelayClient> for AppState {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: ConnectRelayClient, _ctx: &mut Self::Context) -> Self::Result {
|
||||
self.connected_relay_clients.push(msg.addr);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,12 @@
|
|||
use actix::Addr;
|
||||
use actix_web::{delete, get, put, web, HttpResponse};
|
||||
use emgauwa_common::db::DbController;
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, Controller, FromDbModel};
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestControllerUpdate};
|
||||
use emgauwa_common::types::{ControllersWsAction, EmgauwaUid, RequestControllerUpdate};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::app_state;
|
||||
use crate::app_state::AppState;
|
||||
use tokio::sync::mpsc;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
use crate::server::WsServerAction;
|
||||
|
||||
#[get("/controllers")]
|
||||
pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -17,7 +15,7 @@ pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, Emgauw
|
|||
let db_controllers = DbController::get_all(&mut pool_conn).await?;
|
||||
|
||||
let controllers: Vec<Controller> = convert_db_list(&mut pool_conn, db_controllers)?;
|
||||
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(controllers))
|
||||
}
|
||||
|
||||
|
|
@ -36,13 +34,14 @@ pub async fn show(
|
|||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let return_controller = Controller::from_db_model(&mut pool_conn, controller)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(return_controller))
|
||||
}
|
||||
|
||||
#[put("/controllers/{controller_id}")]
|
||||
pub async fn update(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String,)>,
|
||||
data: web::Json<RequestControllerUpdate>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -60,13 +59,9 @@ pub async fn update(
|
|||
.await?;
|
||||
|
||||
let return_controller = Controller::from_db_model(&mut pool_conn, controller)?;
|
||||
pool_conn.close().await?;
|
||||
|
||||
app_state
|
||||
.send(app_state::Action {
|
||||
controller_uid: uid.clone(),
|
||||
action: ControllerWsAction::Controller(return_controller.clone()),
|
||||
})
|
||||
.await??;
|
||||
ws_server_tx.send(WsServerAction::ForwardToController(uid, ControllersWsAction::Controller(return_controller.clone())))?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(return_controller))
|
||||
}
|
||||
|
|
@ -74,7 +69,7 @@ pub async fn update(
|
|||
#[delete("/controllers/{controller_id}")]
|
||||
pub async fn delete(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
|
@ -82,12 +77,11 @@ pub async fn delete(
|
|||
let (controller_uid,) = path.into_inner();
|
||||
let uid = EmgauwaUid::try_from(controller_uid.as_str())?;
|
||||
|
||||
app_state
|
||||
.send(app_state::DisconnectController {
|
||||
controller_uid: uid.clone(),
|
||||
})
|
||||
.await??;
|
||||
log::debug!("Deleting controller with uid: {}", uid.clone());
|
||||
DbController::delete_by_uid(&mut pool_conn, uid.clone()).await?;
|
||||
pool_conn.close().await?;
|
||||
|
||||
ws_server_tx.send(WsServerAction::DisconnectController(uid.clone(), false))?;
|
||||
|
||||
DbController::delete_by_uid(&mut pool_conn, uid).await?;
|
||||
Ok(HttpResponse::Ok().emgauwa_message("controller got deleted"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,15 @@
|
|||
use actix::Addr;
|
||||
use actix_web::{delete, get, post, put, web, HttpResponse};
|
||||
use emgauwa_common::db::{DbController, DbMacro};
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, FromDbModel, Macro, MacroAction, Relay};
|
||||
use emgauwa_common::types::{
|
||||
ControllerWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
|
||||
ControllersWsAction, EmgauwaUid, RequestMacroCreate, RequestMacroExecute, RequestMacroUpdate,
|
||||
};
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::app_state;
|
||||
use crate::app_state::AppState;
|
||||
use tokio::sync::mpsc;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
use crate::server::WsServerAction;
|
||||
|
||||
#[get("/macros")]
|
||||
pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -19,7 +17,7 @@ pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, Emgauw
|
|||
|
||||
let db_macros = DbMacro::get_all(&mut pool_conn).await?;
|
||||
let macros: Vec<Macro> = convert_db_list(&mut pool_conn, db_macros)?;
|
||||
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(macros))
|
||||
}
|
||||
|
||||
|
|
@ -38,6 +36,7 @@ pub async fn show(
|
|||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(return_macro))
|
||||
}
|
||||
|
||||
|
|
@ -55,6 +54,7 @@ pub async fn add(
|
|||
.await?;
|
||||
|
||||
let return_macro = Macro::from_db_model(&mut pool_conn, new_macro)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Created().json(return_macro))
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +84,7 @@ pub async fn update(
|
|||
}
|
||||
|
||||
let return_macro = Macro::from_db_model(&mut pool_conn, db_macro)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(return_macro))
|
||||
}
|
||||
|
||||
|
|
@ -98,14 +99,15 @@ pub async fn delete(
|
|||
let uid = EmgauwaUid::try_from(macro_uid.as_str())?;
|
||||
|
||||
DbMacro::delete_by_uid(&mut pool_conn, uid).await?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().emgauwa_message("macro got deleted"))
|
||||
}
|
||||
|
||||
#[put("/macros/{macro_id}/execute")]
|
||||
pub async fn execute(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
path: web::Path<(String,)>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
query: web::Query<RequestMacroExecute>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
|
@ -137,14 +139,10 @@ pub async fn execute(
|
|||
let affected_relays =
|
||||
collect_affected_relays(&mut pool_conn, &mut actions, &controller).await?;
|
||||
|
||||
app_state
|
||||
.send(app_state::Action {
|
||||
controller_uid: controller.uid,
|
||||
action: ControllerWsAction::Relays(affected_relays.clone()),
|
||||
})
|
||||
.await??;
|
||||
ws_server_tx.send(WsServerAction::ForwardToController(uid.clone(), ControllersWsAction::Relays(affected_relays.clone())))?;
|
||||
}
|
||||
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().emgauwa_message("macro got executed"))
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,42 +1,55 @@
|
|||
use actix::Addr;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
use crate::server::WsServerAction;
|
||||
use actix_web::{get, post, put, web, HttpResponse};
|
||||
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbTag};
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, FromDbModel, Relay};
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate};
|
||||
use emgauwa_common::types::{
|
||||
ControllersWsAction, EmgauwaUid, RequestRelayPulse, RequestRelayUpdate,
|
||||
};
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
use crate::app_state;
|
||||
use crate::app_state::AppState;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
|
||||
pub async fn get_stated_relays(app_state: &Addr<AppState>) -> Result<Vec<Relay>, EmgauwaError> {
|
||||
app_state.send(app_state::GetRelays {}).await?
|
||||
pub async fn get_stated_relays(
|
||||
ws_server_tx: &web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
) -> Result<Vec<Relay>, EmgauwaError> {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
ws_server_tx.send(WsServerAction::GetRelays(res_tx))?;
|
||||
res_rx.await?
|
||||
}
|
||||
|
||||
pub async fn load_state_for_relay(relay: &mut Relay, app_state: &Addr<AppState>) -> Result<(), EmgauwaError>{
|
||||
let stated_relays = get_stated_relays(app_state).await?;
|
||||
pub async fn load_state_for_relay(
|
||||
relay: &mut Relay,
|
||||
ws_server_tx: &web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
let stated_relays = get_stated_relays(ws_server_tx).await?;
|
||||
relay.find_and_apply_state(&stated_relays);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn load_state_for_relays(relays: &mut [Relay], app_state: &Addr<AppState>) -> Result<(), EmgauwaError>{
|
||||
let stated_relays = get_stated_relays(app_state).await?;
|
||||
relays.iter_mut().for_each(|r| r.find_and_apply_state(&stated_relays));
|
||||
pub async fn load_state_for_relays(
|
||||
relays: &mut [Relay],
|
||||
ws_server_tx: &web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
let stated_relays = get_stated_relays(ws_server_tx).await?;
|
||||
relays
|
||||
.iter_mut()
|
||||
.for_each(|r| r.find_and_apply_state(&stated_relays));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[get("/relays")]
|
||||
pub async fn index(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
||||
let db_relays = DbRelay::get_all(&mut pool_conn).await?;
|
||||
|
||||
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
|
||||
load_state_for_relays(&mut relays, &app_state).await?;
|
||||
pool_conn.close().await?;
|
||||
load_state_for_relays(&mut relays, &ws_server_tx).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(relays))
|
||||
}
|
||||
|
|
@ -44,7 +57,7 @@ pub async fn index(
|
|||
#[get("/relays/tag/{tag}")]
|
||||
pub async fn tagged(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
|
@ -57,7 +70,8 @@ pub async fn tagged(
|
|||
let db_relays = DbRelay::get_by_tag(&mut pool_conn, &tag_db).await?;
|
||||
|
||||
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
|
||||
load_state_for_relays(&mut relays, &app_state).await?;
|
||||
pool_conn.close().await?;
|
||||
load_state_for_relays(&mut relays, &ws_server_tx).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(relays))
|
||||
}
|
||||
|
|
@ -65,7 +79,7 @@ pub async fn tagged(
|
|||
#[get("/controllers/{controller_id}/relays")]
|
||||
pub async fn index_for_controller(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
|
@ -80,7 +94,8 @@ pub async fn index_for_controller(
|
|||
let db_relays = controller.get_relays(&mut pool_conn).await?;
|
||||
|
||||
let mut relays: Vec<Relay> = convert_db_list(&mut pool_conn, db_relays)?;
|
||||
load_state_for_relays(&mut relays, &app_state).await?;
|
||||
pool_conn.close().await?;
|
||||
load_state_for_relays(&mut relays, &ws_server_tx).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(relays))
|
||||
}
|
||||
|
|
@ -88,7 +103,7 @@ pub async fn index_for_controller(
|
|||
#[get("/controllers/{controller_id}/relays/{relay_num}")]
|
||||
pub async fn show_for_controller(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String, i64)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
|
@ -105,7 +120,8 @@ pub async fn show_for_controller(
|
|||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let mut relay = Relay::from_db_model(&mut pool_conn, db_relay)?;
|
||||
load_state_for_relay(&mut relay, &app_state).await?;
|
||||
pool_conn.close().await?;
|
||||
load_state_for_relay(&mut relay, &ws_server_tx).await?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(relay))
|
||||
}
|
||||
|
|
@ -113,7 +129,7 @@ pub async fn show_for_controller(
|
|||
#[put("/controllers/{controller_id}/relays/{relay_num}")]
|
||||
pub async fn update_for_controller(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String, i64)>,
|
||||
data: web::Json<RequestRelayUpdate>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -158,24 +174,25 @@ pub async fn update_for_controller(
|
|||
|
||||
let mut return_relay = Relay::from_db_model(&mut pool_conn, relay)?;
|
||||
|
||||
load_state_for_relay(&mut return_relay, &app_state).await?;
|
||||
load_state_for_relay(&mut return_relay, &ws_server_tx).await?;
|
||||
match &data.override_schedule {
|
||||
Some(Some(s_uid)) => { // We want to set an override schedule
|
||||
Some(Some(s_uid)) => {
|
||||
// We want to set an override schedule
|
||||
let schedule = s_uid.get_schedule(&mut pool_conn).await?;
|
||||
return_relay.override_schedule = Some(schedule);
|
||||
}
|
||||
Some(None) => { // We want to unset the override schedule
|
||||
Some(None) => {
|
||||
// We want to unset the override schedule
|
||||
return_relay.override_schedule = None;
|
||||
}
|
||||
None => {} // We want to keep the override schedule as is
|
||||
}
|
||||
|
||||
app_state
|
||||
.send(app_state::Action {
|
||||
controller_uid: uid,
|
||||
action: ControllerWsAction::Relays(vec![return_relay.clone()]),
|
||||
})
|
||||
.await??;
|
||||
pool_conn.close().await?;
|
||||
ws_server_tx.send(WsServerAction::ForwardToController(
|
||||
uid,
|
||||
ControllersWsAction::Relays(vec![return_relay.clone()]),
|
||||
))?;
|
||||
|
||||
Ok(HttpResponse::Ok().json(return_relay))
|
||||
}
|
||||
|
|
@ -183,7 +200,7 @@ pub async fn update_for_controller(
|
|||
#[post("/controllers/{controller_id}/relays/{relay_num}/pulse")]
|
||||
pub async fn pulse(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String, i64)>,
|
||||
data: web::Json<RequestRelayPulse>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -200,14 +217,13 @@ pub async fn pulse(
|
|||
.await?
|
||||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let duration = data.duration.filter(|&d| d > 0);
|
||||
pool_conn.close().await?;
|
||||
|
||||
app_state
|
||||
.send(app_state::Action {
|
||||
controller_uid: uid,
|
||||
action: ControllerWsAction::RelayPulse((relay.number, duration)),
|
||||
})
|
||||
.await??;
|
||||
let duration = data.duration.filter(|&d| d > 0);
|
||||
ws_server_tx.send(WsServerAction::ForwardToController(
|
||||
uid,
|
||||
ControllersWsAction::RelayPulse((relay.number, duration)),
|
||||
))?;
|
||||
|
||||
Ok(HttpResponse::Ok().emgauwa_message("pulse sent"))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
use actix::Addr;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
use crate::server::WsServerAction;
|
||||
use actix_web::{delete, get, post, put, web, HttpResponse};
|
||||
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbSchedule, DbTag};
|
||||
use emgauwa_common::errors::{ApiError, DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, FromDbModel, Schedule};
|
||||
use emgauwa_common::types::{
|
||||
ControllerWsAction, RequestScheduleCreate, RequestScheduleGetTagged, RequestScheduleUpdate,
|
||||
ControllersWsAction, RequestScheduleCreate, RequestScheduleGetTagged, RequestScheduleUpdate,
|
||||
ScheduleUid,
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
|
||||
use crate::app_state;
|
||||
use crate::app_state::AppState;
|
||||
use crate::handlers::EmgauwaMessage;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
#[get("/schedules")]
|
||||
pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -21,7 +19,7 @@ pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, Emgauw
|
|||
|
||||
let db_schedules = DbSchedule::get_all(&mut pool_conn).await?;
|
||||
let schedules: Vec<Schedule> = convert_db_list(&mut pool_conn, db_schedules)?;
|
||||
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(schedules))
|
||||
}
|
||||
|
||||
|
|
@ -53,7 +51,7 @@ pub async fn tagged(
|
|||
}
|
||||
|
||||
let schedules: Vec<Schedule> = convert_db_list(&mut pool_conn, db_schedules)?;
|
||||
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(schedules))
|
||||
}
|
||||
|
||||
|
|
@ -72,6 +70,7 @@ pub async fn show(
|
|||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let return_schedule = Schedule::from_db_model(&mut pool_conn, schedule)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(return_schedule))
|
||||
}
|
||||
|
||||
|
|
@ -97,6 +96,7 @@ pub async fn add(
|
|||
}
|
||||
|
||||
let return_schedule = Schedule::from_db_model(&mut pool_conn, new_schedule)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Created().json(return_schedule))
|
||||
}
|
||||
|
||||
|
|
@ -133,13 +133,14 @@ pub async fn add_list(
|
|||
}
|
||||
|
||||
let schedules: Vec<Schedule> = convert_db_list(&mut pool_conn, db_schedules)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Created().json(schedules))
|
||||
}
|
||||
|
||||
#[put("/schedules/{schedule_id}")]
|
||||
pub async fn update(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
path: web::Path<(String,)>,
|
||||
data: web::Json<RequestScheduleUpdate>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
|
|
@ -179,15 +180,15 @@ pub async fn update(
|
|||
let controller = DbController::get(&mut pool_conn, controller_id)
|
||||
.await?
|
||||
.ok_or(DatabaseError::NotFound)?;
|
||||
app_state
|
||||
.send(app_state::Action {
|
||||
controller_uid: controller.uid,
|
||||
action: ControllerWsAction::Schedules(vec![schedule.clone()]),
|
||||
})
|
||||
.await??;
|
||||
ws_server_tx
|
||||
.send(WsServerAction::ForwardToController(
|
||||
controller.uid,
|
||||
ControllersWsAction::Schedules(vec![schedule.clone()]),
|
||||
))?;
|
||||
}
|
||||
|
||||
let return_schedule = Schedule::from_db_model(&mut pool_conn, schedule)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(return_schedule))
|
||||
}
|
||||
|
||||
|
|
@ -196,8 +197,6 @@ pub async fn delete(
|
|||
pool: web::Data<Pool<Sqlite>>,
|
||||
path: web::Path<(String,)>,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
|
||||
let (schedule_uid,) = path.into_inner();
|
||||
let uid = ScheduleUid::try_from(schedule_uid.as_str())?;
|
||||
|
||||
|
|
@ -205,7 +204,9 @@ pub async fn delete(
|
|||
ScheduleUid::Off => Err(EmgauwaError::from(ApiError::ProtectedSchedule)),
|
||||
ScheduleUid::On => Err(EmgauwaError::from(ApiError::ProtectedSchedule)),
|
||||
ScheduleUid::Any(_) => {
|
||||
let mut pool_conn = pool.acquire().await?;
|
||||
DbSchedule::delete_by_uid(&mut pool_conn, uid).await?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().emgauwa_message("schedule got deleted"))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ pub async fn index(pool: web::Data<Pool<Sqlite>>) -> Result<HttpResponse, Emgauw
|
|||
let mut pool_conn = pool.acquire().await?;
|
||||
|
||||
let db_tags = DbTag::get_all(&mut pool_conn).await?;
|
||||
pool_conn.close().await?;
|
||||
|
||||
let tags: Vec<String> = db_tags.iter().map(|t| t.tag.clone()).collect();
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ pub async fn show(
|
|||
.ok_or(DatabaseError::NotFound)?;
|
||||
|
||||
let return_tag = Tag::from_db_model(&mut pool_conn, tag)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().json(return_tag))
|
||||
}
|
||||
|
||||
|
|
@ -45,6 +47,7 @@ pub async fn delete(
|
|||
let (tag_name,) = path.into_inner();
|
||||
|
||||
DbTag::delete_by_tag(&mut pool_conn, &tag_name).await?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Ok().emgauwa_message("tag got deleted"))
|
||||
}
|
||||
|
||||
|
|
@ -59,5 +62,6 @@ pub async fn add(
|
|||
|
||||
let cache = (Vec::new(), Vec::new()); // a new tag can't have any relays or schedules
|
||||
let return_tag = Tag::from_db_model_cache(&mut pool_conn, new_tag, cache)?;
|
||||
pool_conn.close().await?;
|
||||
Ok(HttpResponse::Created().json(return_tag))
|
||||
}
|
||||
|
|
|
|||
114
src/handlers/v1/ws/controllers/handle.rs
Normal file
114
src/handlers/v1/ws/controllers/handle.rs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
use std::time::Instant;
|
||||
use actix_ws::{AggregatedMessage, CloseCode, CloseReason};
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::time::interval;
|
||||
use emgauwa_common::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT};
|
||||
use crate::server::{WsConnId, WsControllerAction, WsServerAction};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ControllerWsHandle {
|
||||
pub cmd_tx: mpsc::UnboundedSender<WsServerAction>,
|
||||
}
|
||||
|
||||
impl ControllerWsHandle {
|
||||
/// Register client message sender and obtain connection ID.
|
||||
pub async fn connect(&self, conn_tx: mpsc::UnboundedSender<WsControllerAction>) -> WsConnId {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
|
||||
// unwrap: chat server should not have been dropped
|
||||
self.cmd_tx
|
||||
.send(WsServerAction::ConnectController(conn_tx, res_tx))
|
||||
.unwrap();
|
||||
|
||||
// unwrap: chat server does not drop out the response channel
|
||||
res_rx.await.unwrap()
|
||||
}
|
||||
|
||||
/// Unregister message sender and broadcast disconnection message to current room.
|
||||
pub fn disconnect(&self, conn: WsConnId) {
|
||||
// unwrap: chat server should not have been dropped
|
||||
self.cmd_tx.send(WsServerAction::DisconnectControllerConn(conn, true)).unwrap();
|
||||
}
|
||||
|
||||
pub async fn message(&self, conn: WsConnId, msg: String) {
|
||||
self.cmd_tx.send(WsServerAction::ControllerMessage(conn, msg)).unwrap();
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
&self,
|
||||
mut session: actix_ws::Session,
|
||||
msg_stream: actix_ws::MessageStream,
|
||||
) {
|
||||
log::info!("connected");
|
||||
|
||||
let mut last_heartbeat = Instant::now();
|
||||
let mut interval = interval(HEARTBEAT_INTERVAL);
|
||||
|
||||
let (conn_tx, mut conn_rx) = mpsc::unbounded_channel();
|
||||
|
||||
// unwrap: chat server is not dropped before the HTTP server
|
||||
let conn_id = self.connect(conn_tx).await;
|
||||
|
||||
let mut msg_stream = msg_stream
|
||||
.max_frame_size(128 * 1024)
|
||||
.aggregate_continuations()
|
||||
.max_continuation_size(2 * 1024 * 1024);
|
||||
|
||||
let close_reason = loop {
|
||||
tokio::select! {
|
||||
Some(Ok(msg)) = msg_stream.next() => {
|
||||
match msg {
|
||||
AggregatedMessage::Ping(bytes) => {
|
||||
last_heartbeat = Instant::now();
|
||||
session.pong(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
AggregatedMessage::Pong(_) => {
|
||||
last_heartbeat = Instant::now();
|
||||
}
|
||||
|
||||
AggregatedMessage::Text(text) => {
|
||||
let text_str = text.to_string();
|
||||
log::debug!("ws msg: {}", text_str);
|
||||
self.message(conn_id, text_str).await;
|
||||
}
|
||||
|
||||
AggregatedMessage::Binary(_) => {
|
||||
log::warn!("unexpected binary message");
|
||||
}
|
||||
|
||||
AggregatedMessage::Close(reason) => break reason,
|
||||
}
|
||||
}
|
||||
|
||||
Some(chat_msg) = conn_rx.recv() => {
|
||||
match chat_msg {
|
||||
WsControllerAction::Forward(msg) => {
|
||||
session.text(msg).await.unwrap();
|
||||
}
|
||||
WsControllerAction::Disconnect => {
|
||||
break Some(CloseReason::from((CloseCode::Normal, "Disconnect wanted by server")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = interval.tick() => {
|
||||
if Instant::now().duration_since(last_heartbeat) > HEARTBEAT_TIMEOUT {
|
||||
break None;
|
||||
}
|
||||
let _ = session.ping(b"").await;
|
||||
}
|
||||
|
||||
else => {
|
||||
break None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// attempt to close the connection gracefully
|
||||
log::debug!("Closing a controller connection: {:?}", close_reason);
|
||||
let _ = session.close(close_reason).await;
|
||||
self.disconnect(conn_id);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,114 +0,0 @@
|
|||
use actix::{Actor, AsyncContext};
|
||||
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule};
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{Controller, FromDbModel};
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid, RelayStates};
|
||||
use emgauwa_common::utils;
|
||||
use futures::executor::block_on;
|
||||
use sqlx::pool::PoolConnection;
|
||||
use sqlx::Sqlite;
|
||||
|
||||
use crate::app_state::{Action, ConnectController, UpdateRelayStates};
|
||||
use crate::handlers::v1::ws::controllers::ControllersWs;
|
||||
|
||||
impl ControllersWs {
|
||||
pub fn handle_register(
|
||||
&mut self,
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
ctx: &mut <ControllersWs as Actor>::Context,
|
||||
controller: Controller,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
log::info!(
|
||||
"Registering controller: {} ({})",
|
||||
controller.c.name,
|
||||
controller.c.uid
|
||||
);
|
||||
let c = &controller.c;
|
||||
let controller_db = block_on(DbController::get_by_uid_or_create(
|
||||
conn,
|
||||
&c.uid,
|
||||
&c.name,
|
||||
c.relay_count,
|
||||
))?;
|
||||
block_on(controller_db.update_active(conn, true))?;
|
||||
// update only the relay count
|
||||
block_on(controller_db.update(conn, &controller_db.name, c.relay_count))?;
|
||||
|
||||
for relay in &controller.relays {
|
||||
log::debug!(
|
||||
"Registering relay: {} ({})",
|
||||
relay.r.name,
|
||||
match relay.is_on {
|
||||
Some(true) => "+",
|
||||
Some(false) => "-",
|
||||
None => "?",
|
||||
}
|
||||
);
|
||||
let (new_relay, created) = block_on(DbRelay::get_by_controller_and_num_or_create(
|
||||
conn,
|
||||
&controller_db,
|
||||
relay.r.number,
|
||||
&relay.r.name,
|
||||
))?;
|
||||
if created {
|
||||
let mut relay_schedules = Vec::new();
|
||||
for schedule in &relay.schedules {
|
||||
let (new_schedule, _) = block_on(DbSchedule::get_by_uid_or_create(
|
||||
conn,
|
||||
schedule.uid.clone(),
|
||||
&schedule.name,
|
||||
&schedule.periods,
|
||||
))?;
|
||||
relay_schedules.push(new_schedule);
|
||||
}
|
||||
|
||||
block_on(DbJunctionRelaySchedule::set_schedules(
|
||||
conn,
|
||||
&new_relay,
|
||||
relay_schedules.iter().collect(),
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
let controller_uid = &controller.c.uid;
|
||||
let controller_db = block_on(DbController::get_by_uid(conn, controller_uid))?
|
||||
.ok_or(DatabaseError::InsertGetError)?;
|
||||
let controller = Controller::from_db_model(conn, controller_db)?;
|
||||
|
||||
let addr = ctx.address();
|
||||
self.controller_uid = Some(controller_uid.clone());
|
||||
block_on(self.app_state.send(ConnectController {
|
||||
address: addr.recipient(),
|
||||
controller: controller.clone(),
|
||||
}))??;
|
||||
|
||||
block_on(self.app_state.send(Action {
|
||||
controller_uid: controller_uid.clone(),
|
||||
action: ControllerWsAction::Controller(controller.clone()),
|
||||
}))??;
|
||||
block_on(self.app_state.send(Action {
|
||||
controller_uid: controller_uid.clone(),
|
||||
action: ControllerWsAction::Relays(controller.relays),
|
||||
}))??;
|
||||
|
||||
log::debug!("Done registering controller");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_relay_states(
|
||||
&mut self,
|
||||
controller_uid: EmgauwaUid,
|
||||
relay_states: RelayStates,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
log::debug!(
|
||||
"Received relay states: {} for {}",
|
||||
utils::printable_relay_states(&relay_states),
|
||||
controller_uid
|
||||
);
|
||||
block_on(self.app_state.send(UpdateRelayStates {
|
||||
controller_uid,
|
||||
relay_states,
|
||||
}))?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,153 +1,53 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, StreamHandler};
|
||||
use actix_web_actors::ws;
|
||||
use actix_web_actors::ws::ProtocolError;
|
||||
use futures::executor::block_on;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use sqlx::pool::PoolConnection;
|
||||
use ws::Message;
|
||||
|
||||
use emgauwa_common::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT};
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::spawn_local;
|
||||
use emgauwa_common::constants;
|
||||
use emgauwa_common::errors::EmgauwaError;
|
||||
use emgauwa_common::types::{ControllerWsAction, EmgauwaUid};
|
||||
use crate::handlers::v1::ws::controllers::handle::ControllerWsHandle;
|
||||
use crate::server::WsServerAction;
|
||||
use crate::settings::Settings;
|
||||
|
||||
use crate::app_state::{AppState, DisconnectController};
|
||||
mod handle;
|
||||
|
||||
mod handlers;
|
||||
|
||||
pub struct ControllersWs {
|
||||
pub pool: Pool<Sqlite>,
|
||||
pub controller_uid: Option<EmgauwaUid>,
|
||||
pub app_state: Addr<AppState>,
|
||||
pub hb: Instant,
|
||||
}
|
||||
|
||||
impl Actor for ControllersWs {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
self.hb(ctx);
|
||||
}
|
||||
|
||||
fn stopped(&mut self, _ctx: &mut Self::Context) {
|
||||
if let Some(controller_uid) = &self.controller_uid {
|
||||
if let Err(err) = block_on(self.app_state.send(DisconnectController {
|
||||
controller_uid: controller_uid.clone(),
|
||||
})).unwrap_or_else(|err| Err(EmgauwaError::from(err))) {
|
||||
log::error!("Error disconnecting controller: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControllersWs {
|
||||
pub fn handle_action(
|
||||
&mut self,
|
||||
conn: &mut PoolConnection<Sqlite>,
|
||||
ctx: &mut <ControllersWs as Actor>::Context,
|
||||
action: ControllerWsAction,
|
||||
) {
|
||||
let action_res = match action {
|
||||
ControllerWsAction::Register(controller) => self.handle_register(conn, ctx, controller),
|
||||
ControllerWsAction::RelayStates((controller_uid, relay_states)) => {
|
||||
self.handle_relay_states(controller_uid, relay_states)
|
||||
}
|
||||
_ => Ok(()),
|
||||
pub async fn run_ws_server_handle(ws_server_tx: mpsc::UnboundedSender<WsServerAction>, session: actix_ws::Session, msg_stream: actix_ws::MessageStream) {
|
||||
let ws_server_handle = ControllerWsHandle {
|
||||
cmd_tx: ws_server_tx,
|
||||
};
|
||||
if let Err(e) = action_res {
|
||||
log::error!("Error handling action: {:?}", e);
|
||||
ctx.text(
|
||||
serde_json::to_string(&e).unwrap_or(format!("Error in handling action: {:?}", e)),
|
||||
);
|
||||
}
|
||||
ws_server_handle.run(session, msg_stream).await;
|
||||
}
|
||||
|
||||
// helper method that sends ping to client every 5 seconds (HEARTBEAT_INTERVAL).
|
||||
fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
|
||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||
// check client heartbeats
|
||||
if Instant::now().duration_since(act.hb) > HEARTBEAT_TIMEOUT {
|
||||
log::warn!("Websocket Controller heartbeat failed, disconnecting!");
|
||||
ctx.stop();
|
||||
// don't try to send a ping
|
||||
return;
|
||||
//noinspection DuplicatedCode
|
||||
#[get("/ws/controllers")]
|
||||
pub async fn ws_controllers(
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
settings: web::Data<Settings>,
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let token = req
|
||||
.headers()
|
||||
.get(constants::CONTROLLER_WS_TOKEN_HEADER)
|
||||
.ok_or(EmgauwaError::Unauthorized(
|
||||
String::from("Missing or invalid token header"),
|
||||
))?
|
||||
.to_str()
|
||||
.map_err(|_| EmgauwaError::Unauthorized(String::from("Invalid token header")))?;
|
||||
|
||||
if token.ne(&settings.server.token) {
|
||||
return Err(EmgauwaError::Unauthorized(String::from("Wrong token header")));
|
||||
}
|
||||
|
||||
log::trace!("Sending ping to controller");
|
||||
ctx.ping(&[]);
|
||||
});
|
||||
}
|
||||
}
|
||||
let (res, session, msg_stream) = actix_ws::handle(&req, stream).map_err(|e| {
|
||||
log::error!("Error in websocket handshake: {}", e);
|
||||
EmgauwaError::Internal(String::from("Error in websocket handshake"))
|
||||
})?;
|
||||
|
||||
impl Handler<ControllerWsAction> for ControllersWs {
|
||||
type Result = Result<(), EmgauwaError>;
|
||||
// spawn websocket handler (and don't await it) so that the response is returned immediately
|
||||
spawn_local(run_ws_server_handle(
|
||||
(**ws_server_tx).clone(),
|
||||
session,
|
||||
msg_stream,
|
||||
));
|
||||
|
||||
fn handle(&mut self, action: ControllerWsAction, ctx: &mut Self::Context) -> Self::Result {
|
||||
match action {
|
||||
ControllerWsAction::Disconnect => {
|
||||
ctx.close(None);
|
||||
ctx.stop();
|
||||
}
|
||||
_ => {
|
||||
let action_json = serde_json::to_string(&action)?;
|
||||
ctx.text(action_json);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Result<Message, ProtocolError>> for ControllersWs {
|
||||
fn handle(&mut self, msg: Result<Message, ProtocolError>, ctx: &mut Self::Context) {
|
||||
let mut pool_conn = match block_on(self.pool.acquire()) {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
log::error!("Failed to acquire database connection: {:?}", err);
|
||||
ctx.stop();
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let msg = match msg {
|
||||
Err(_) => {
|
||||
ctx.stop();
|
||||
return;
|
||||
}
|
||||
Ok(msg) => msg,
|
||||
};
|
||||
|
||||
match msg {
|
||||
Message::Ping(msg) => {
|
||||
log::trace!("Received ping from controller: {:?}", msg);
|
||||
self.hb = Instant::now();
|
||||
ctx.pong(&msg)
|
||||
}
|
||||
Message::Pong(_) => {
|
||||
log::trace!("Received pong from controller");
|
||||
self.hb = Instant::now();
|
||||
}
|
||||
Message::Text(text) => match serde_json::from_str(&text) {
|
||||
Ok(action) => {
|
||||
self.handle_action(&mut pool_conn, ctx, action);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error deserializing action: {:?}", e);
|
||||
ctx.text(
|
||||
serde_json::to_string(&EmgauwaError::Serialization(e))
|
||||
.unwrap_or(String::from("Error in deserializing action")),
|
||||
);
|
||||
}
|
||||
},
|
||||
Message::Binary(_) => log::warn!("Received unexpected binary in controller ws"),
|
||||
Message::Close(reason) => {
|
||||
ctx.close(reason);
|
||||
ctx.stop();
|
||||
}
|
||||
Message::Continuation(_) => {
|
||||
ctx.stop();
|
||||
}
|
||||
Message::Nop => (),
|
||||
}
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,68 +1,4 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use actix::Addr;
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use actix_web_actors::ws;
|
||||
use emgauwa_common::errors::EmgauwaError;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use emgauwa_common::constants;
|
||||
use crate::app_state::AppState;
|
||||
use crate::handlers::v1::ws::controllers::ControllersWs;
|
||||
use crate::handlers::v1::ws::relays::RelaysWs;
|
||||
use crate::settings::Settings;
|
||||
|
||||
pub mod controllers;
|
||||
pub mod relays;
|
||||
|
||||
#[get("/ws/controllers")]
|
||||
pub async fn ws_controllers(
|
||||
pool: web::Data<Pool<Sqlite>>,
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
settings: web::Data<Settings>,
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let token = req
|
||||
.headers()
|
||||
.get(constants::CONTROLLER_WS_TOKEN_HEADER)
|
||||
.ok_or(EmgauwaError::Unauthorized(
|
||||
String::from("Missing or invalid token header"),
|
||||
))?
|
||||
.to_str()
|
||||
.map_err(|_| EmgauwaError::Unauthorized(String::from("Invalid token header")))?;
|
||||
|
||||
if token != settings.server.token {
|
||||
return Err(EmgauwaError::Unauthorized(String::from("Wrong token header")));
|
||||
}
|
||||
|
||||
let resp = ws::start(
|
||||
ControllersWs {
|
||||
pool: pool.get_ref().clone(),
|
||||
controller_uid: None,
|
||||
app_state: app_state.get_ref().clone(),
|
||||
hb: Instant::now(),
|
||||
},
|
||||
&req,
|
||||
stream,
|
||||
)
|
||||
.map_err(|_| EmgauwaError::Internal(String::from("error starting websocket")));
|
||||
resp
|
||||
}
|
||||
|
||||
#[get("/ws/relays")]
|
||||
pub async fn ws_relays(
|
||||
app_state: web::Data<Addr<AppState>>,
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let resp = ws::start(
|
||||
RelaysWs {
|
||||
app_state: app_state.get_ref().clone(),
|
||||
hb: Instant::now(),
|
||||
},
|
||||
&req,
|
||||
stream,
|
||||
)
|
||||
.map_err(|_| EmgauwaError::Internal(String::from("error starting websocket")));
|
||||
resp
|
||||
}
|
||||
mod relays;
|
||||
pub use relays::ws_relays;
|
||||
mod controllers;
|
||||
pub use controllers::ws_controllers;
|
||||
|
|
|
|||
108
src/handlers/v1/ws/relays/handle.rs
Normal file
108
src/handlers/v1/ws/relays/handle.rs
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
use std::time::Instant;
|
||||
use actix_ws::{AggregatedMessage, CloseCode, CloseReason};
|
||||
use futures::StreamExt;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use tokio::time::interval;
|
||||
use emgauwa_common::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT};
|
||||
use crate::server::{WsConnId, WsRelayAction, WsServerAction};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct RelayWsHandle {
|
||||
pub cmd_tx: mpsc::UnboundedSender<WsServerAction>,
|
||||
}
|
||||
|
||||
impl RelayWsHandle {
|
||||
/// Register client message sender and obtain connection ID.
|
||||
pub async fn connect(&self, conn_tx: mpsc::UnboundedSender<WsRelayAction>) -> WsConnId {
|
||||
let (res_tx, res_rx) = oneshot::channel();
|
||||
|
||||
// unwrap: chat server should not have been dropped
|
||||
self.cmd_tx
|
||||
.send(WsServerAction::ConnectRelay(conn_tx, res_tx))
|
||||
.unwrap();
|
||||
|
||||
// unwrap: chat server does not drop out the response channel
|
||||
res_rx.await.unwrap()
|
||||
}
|
||||
|
||||
/// Unregister message sender and broadcast disconnection message to current room.
|
||||
pub fn disconnect(&self, conn: WsConnId) {
|
||||
// unwrap: chat server should not have been dropped
|
||||
self.cmd_tx.send(WsServerAction::DisconnectRelayConn(conn)).unwrap();
|
||||
}
|
||||
|
||||
pub async fn run(
|
||||
&self,
|
||||
mut session: actix_ws::Session,
|
||||
msg_stream: actix_ws::MessageStream,
|
||||
) {
|
||||
log::info!("connected");
|
||||
|
||||
let mut last_heartbeat = Instant::now();
|
||||
let mut interval = interval(HEARTBEAT_INTERVAL);
|
||||
|
||||
let (conn_tx, mut conn_rx) = mpsc::unbounded_channel();
|
||||
|
||||
// unwrap: chat server is not dropped before the HTTP server
|
||||
let conn_id = self.connect(conn_tx).await;
|
||||
|
||||
let mut msg_stream = msg_stream
|
||||
.max_frame_size(128 * 1024)
|
||||
.aggregate_continuations()
|
||||
.max_continuation_size(2 * 1024 * 1024);
|
||||
|
||||
let close_reason = loop {
|
||||
tokio::select! {
|
||||
Some(Ok(msg)) = msg_stream.next() => {
|
||||
match msg {
|
||||
AggregatedMessage::Ping(bytes) => {
|
||||
last_heartbeat = Instant::now();
|
||||
session.pong(&bytes).await.unwrap();
|
||||
}
|
||||
|
||||
AggregatedMessage::Pong(_) => {
|
||||
last_heartbeat = Instant::now();
|
||||
}
|
||||
|
||||
AggregatedMessage::Text(_) => {
|
||||
log::warn!("unexpected text message");
|
||||
}
|
||||
|
||||
AggregatedMessage::Binary(_) => {
|
||||
log::warn!("unexpected binary message");
|
||||
}
|
||||
|
||||
AggregatedMessage::Close(reason) => break reason,
|
||||
}
|
||||
}
|
||||
|
||||
Some(chat_msg) = conn_rx.recv() => {
|
||||
match chat_msg {
|
||||
WsRelayAction::Forward(msg) => {
|
||||
session.text(msg).await.unwrap();
|
||||
}
|
||||
WsRelayAction::Disconnect => {
|
||||
break Some(CloseReason::from((CloseCode::Normal, "Disconnect wanted by server")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ = interval.tick() => {
|
||||
if Instant::now().duration_since(last_heartbeat) > HEARTBEAT_TIMEOUT {
|
||||
break None;
|
||||
}
|
||||
let _ = session.ping(b"").await;
|
||||
}
|
||||
|
||||
else => {
|
||||
break None;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
self.disconnect(conn_id);
|
||||
|
||||
// attempt to close the connection gracefully
|
||||
let _ = session.close(close_reason).await;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,109 +1,36 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use actix::{Actor, ActorContext, Addr, AsyncContext, Handler, Message, StreamHandler};
|
||||
use actix_web_actors::ws;
|
||||
use actix_web_actors::ws::ProtocolError;
|
||||
use emgauwa_common::constants::{HEARTBEAT_INTERVAL, HEARTBEAT_TIMEOUT};
|
||||
use actix_web::{get, web, HttpRequest, HttpResponse};
|
||||
use emgauwa_common::errors::EmgauwaError;
|
||||
use futures::executor::block_on;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::task::spawn_local;
|
||||
use crate::server::WsServerAction;
|
||||
|
||||
use crate::app_state::{AppState, ConnectRelayClient};
|
||||
mod handle;
|
||||
|
||||
pub struct RelaysWs {
|
||||
pub app_state: Addr<AppState>,
|
||||
pub hb: Instant,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct SendRelays {
|
||||
pub relays_json: String,
|
||||
}
|
||||
|
||||
impl Actor for RelaysWs {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
// get unique id for ctx
|
||||
match self.get_relays_json() {
|
||||
Ok(relays_json) => {
|
||||
ctx.text(relays_json);
|
||||
self.hb(ctx);
|
||||
|
||||
block_on(self.app_state.send(ConnectRelayClient {
|
||||
addr: ctx.address(),
|
||||
})).map_err(
|
||||
|err| log::error!("Error connecting relay-client: {:?}", err)
|
||||
).ok();
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("Error getting relays: {:?}", err);
|
||||
ctx.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RelaysWs {
|
||||
fn get_relays_json(&self) -> Result<String, EmgauwaError> {
|
||||
let relays = block_on(self.app_state.send(crate::app_state::GetRelays {}))??;
|
||||
serde_json::to_string(&relays).map_err(EmgauwaError::from)
|
||||
}
|
||||
|
||||
// helper method that sends ping to client every 5 seconds (HEARTBEAT_INTERVAL).
|
||||
fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {
|
||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||
// check client heartbeats
|
||||
if Instant::now().duration_since(act.hb) > HEARTBEAT_TIMEOUT {
|
||||
log::debug!("Websocket Relay heartbeat failed, disconnecting!");
|
||||
ctx.stop();
|
||||
// don't try to send a ping
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.ping(&[]);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Result<ws::Message, ProtocolError>> for RelaysWs {
|
||||
fn handle(&mut self, msg: Result<ws::Message, ProtocolError>, ctx: &mut Self::Context) {
|
||||
let msg = match msg {
|
||||
Err(_) => {
|
||||
ctx.stop();
|
||||
return;
|
||||
}
|
||||
Ok(msg) => msg,
|
||||
pub async fn run_ws_server_handle(ws_server_tx: mpsc::UnboundedSender<WsServerAction>, session: actix_ws::Session, msg_stream: actix_ws::MessageStream) {
|
||||
let ws_server_handle = crate::handlers::v1::ws::relays::handle::RelayWsHandle {
|
||||
cmd_tx: ws_server_tx,
|
||||
};
|
||||
|
||||
match msg {
|
||||
ws::Message::Ping(msg) => {
|
||||
log::trace!("Received ping from relay-client: {:?}", msg);
|
||||
self.hb = Instant::now();
|
||||
ctx.pong(&msg)
|
||||
}
|
||||
ws::Message::Pong(_) => {
|
||||
log::trace!("Received pong from relay-client");
|
||||
self.hb = Instant::now();
|
||||
}
|
||||
ws::Message::Text(_) => log::debug!("Received unexpected text in relays ws"),
|
||||
ws::Message::Binary(_) => log::debug!("Received unexpected binary in relays ws"),
|
||||
ws::Message::Close(reason) => {
|
||||
ctx.close(reason);
|
||||
ctx.stop();
|
||||
}
|
||||
ws::Message::Continuation(_) => {
|
||||
ctx.stop();
|
||||
}
|
||||
ws::Message::Nop => (),
|
||||
}
|
||||
}
|
||||
ws_server_handle.run(session, msg_stream).await;
|
||||
}
|
||||
|
||||
impl Handler<SendRelays> for RelaysWs {
|
||||
type Result = ();
|
||||
//noinspection DuplicatedCode
|
||||
#[get("/ws/relays")]
|
||||
pub async fn ws_relays(
|
||||
ws_server_tx: web::Data<mpsc::UnboundedSender<WsServerAction>>,
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
) -> Result<HttpResponse, EmgauwaError> {
|
||||
let (res, session, msg_stream) = actix_ws::handle(&req, stream).map_err(|e| {
|
||||
log::error!("Error in websocket handshake: {}", e);
|
||||
EmgauwaError::Internal(String::from("Error in websocket handshake"))
|
||||
})?;
|
||||
|
||||
fn handle(&mut self, msg: SendRelays, ctx: &mut Self::Context) -> Self::Result {
|
||||
ctx.text(msg.relays_json);
|
||||
}
|
||||
// spawn websocket handler (and don't await it) so that the response is returned immediately
|
||||
spawn_local(run_ws_server_handle(
|
||||
(**ws_server_tx).clone(),
|
||||
session,
|
||||
msg_stream,
|
||||
));
|
||||
|
||||
Ok(res)
|
||||
}
|
||||
|
|
|
|||
29
src/main.rs
29
src/main.rs
|
|
@ -1,6 +1,5 @@
|
|||
use std::net::TcpListener;
|
||||
|
||||
use actix::{Actor, Arbiter};
|
||||
use actix_cors::Cors;
|
||||
use actix_web::middleware::TrailingSlash;
|
||||
use actix_web::{middleware, web, App, HttpServer};
|
||||
|
|
@ -8,13 +7,14 @@ use emgauwa_common::db::DbController;
|
|||
use emgauwa_common::errors::EmgauwaError;
|
||||
use emgauwa_common::utils::{drop_privileges, init_logging};
|
||||
use serde_json::Value;
|
||||
use tokio::spawn;
|
||||
use utoipa_swagger_ui::SwaggerUi;
|
||||
|
||||
use crate::app_state::AppState;
|
||||
use crate::server::WsServer;
|
||||
|
||||
mod app_state;
|
||||
mod handlers;
|
||||
mod settings;
|
||||
mod server;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> Result<(), std::io::Error> {
|
||||
|
|
@ -34,17 +34,8 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
.map_err(EmgauwaError::from)?;
|
||||
conn.close().await.map_err(EmgauwaError::from)?;
|
||||
|
||||
let app_state_arbiter = Arbiter::with_tokio_rt(|| {
|
||||
tokio::runtime::Builder::new_multi_thread()
|
||||
.worker_threads(2)
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap()
|
||||
});
|
||||
let app_state_pool = pool.clone();
|
||||
let app_state = Actor::start_in_arbiter(&app_state_arbiter.handle(), move |_| {
|
||||
AppState::new(app_state_pool)
|
||||
});
|
||||
let (ws_server, ws_server_tx) = WsServer::new(pool.clone());
|
||||
let ws_server = spawn(ws_server.run());
|
||||
|
||||
let api_v1_json: Value =
|
||||
serde_json::from_str(include_str!("../api.v1.json")).map_err(EmgauwaError::from)?;
|
||||
|
|
@ -55,7 +46,7 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
settings.server.port
|
||||
);
|
||||
|
||||
HttpServer::new(move || {
|
||||
let http_server = HttpServer::new(move || {
|
||||
let cors = Cors::default().allow_any_method().allow_any_header();
|
||||
|
||||
let origins = settings.origins.clone();
|
||||
|
|
@ -70,8 +61,8 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
.wrap(cors)
|
||||
.wrap(middleware::Logger::default())
|
||||
.app_data(web::JsonConfig::default().error_handler(handlers::json_error_handler))
|
||||
.app_data(web::Data::new(ws_server_tx.clone()))
|
||||
.app_data(web::Data::new(pool.clone()))
|
||||
.app_data(web::Data::new(app_state.clone()))
|
||||
.app_data(web::Data::new(settings.clone()))
|
||||
.service(
|
||||
SwaggerUi::new("/api/docs/{_:.*}")
|
||||
|
|
@ -115,6 +106,8 @@ async fn main() -> Result<(), std::io::Error> {
|
|||
})
|
||||
.workers((pool_size / 2) as usize)
|
||||
.listen(listener)?
|
||||
.run()
|
||||
.await
|
||||
.run();
|
||||
|
||||
tokio::try_join!(http_server, async move { ws_server.await.unwrap() })?;
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
30
src/server/mod.rs
Normal file
30
src/server/mod.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
use tokio::sync::{mpsc, oneshot};
|
||||
use emgauwa_common::errors::EmgauwaError;
|
||||
use emgauwa_common::models::Relay;
|
||||
use emgauwa_common::types::{ControllersWsAction, EmgauwaUid};
|
||||
|
||||
mod server;
|
||||
|
||||
pub use server::WsServer;
|
||||
pub type WsConnId = u64;
|
||||
|
||||
pub enum WsControllerAction {
|
||||
Forward(String),
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
pub enum WsRelayAction {
|
||||
Forward(String),
|
||||
Disconnect,
|
||||
}
|
||||
|
||||
pub enum WsServerAction {
|
||||
ConnectController(mpsc::UnboundedSender<WsControllerAction>, oneshot::Sender<WsConnId>),
|
||||
DisconnectControllerConn(WsConnId, bool),
|
||||
DisconnectController(EmgauwaUid, bool),
|
||||
ConnectRelay(mpsc::UnboundedSender<WsRelayAction>, oneshot::Sender<WsConnId>),
|
||||
DisconnectRelayConn(WsConnId),
|
||||
ControllerMessage(WsConnId, String),
|
||||
ForwardToController(EmgauwaUid, ControllersWsAction),
|
||||
GetRelays(oneshot::Sender<Result<Vec<Relay>, EmgauwaError>>),
|
||||
}
|
||||
333
src/server/server.rs
Normal file
333
src/server/server.rs
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
use futures::executor::block_on;
|
||||
use rand::random;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tokio::signal;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use emgauwa_common::db::{DbController, DbJunctionRelaySchedule, DbRelay, DbSchedule};
|
||||
use emgauwa_common::errors::{DatabaseError, EmgauwaError};
|
||||
use emgauwa_common::models::{convert_db_list, Controller, FromDbModel, Relay};
|
||||
use emgauwa_common::types::{ControllersWsAction, EmgauwaUid, RelayStates};
|
||||
use emgauwa_common::utils;
|
||||
use crate::server::{WsConnId, WsControllerAction, WsRelayAction, WsServerAction};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WsServer {
|
||||
/// Map of connection IDs to their message receivers.
|
||||
controller_sessions: HashMap<WsConnId, (mpsc::UnboundedSender<WsControllerAction>, Option<EmgauwaUid>)>,
|
||||
controllers: HashMap<EmgauwaUid, (Controller, WsConnId)>,
|
||||
|
||||
relay_sessions: HashMap<WsConnId, mpsc::UnboundedSender<WsRelayAction>>,
|
||||
|
||||
/// Command receiver.
|
||||
cmd_rx: mpsc::UnboundedReceiver<WsServerAction>,
|
||||
|
||||
pub pool: Pool<Sqlite>,
|
||||
}
|
||||
|
||||
impl WsServer {
|
||||
pub fn new(pool: Pool<Sqlite>) -> (Self, mpsc::UnboundedSender<WsServerAction>) {
|
||||
let (cmd_tx, cmd_rx) = mpsc::unbounded_channel();
|
||||
|
||||
let new_self = Self {
|
||||
controller_sessions: HashMap::new(),
|
||||
controllers: HashMap::new(),
|
||||
relay_sessions: HashMap::new(),
|
||||
cmd_rx,
|
||||
pool,
|
||||
};
|
||||
(new_self, cmd_tx)
|
||||
}
|
||||
|
||||
/// Register new session and assign unique ID to this session
|
||||
async fn connect_controller(&mut self, tx: mpsc::UnboundedSender<WsControllerAction>) -> WsConnId {
|
||||
log::info!("Controller connected");
|
||||
|
||||
// register a session with random connection ID
|
||||
let id = random::<WsConnId>();
|
||||
self.controller_sessions.insert(id, (tx, None));
|
||||
|
||||
// send id back
|
||||
id
|
||||
}
|
||||
|
||||
async fn connect_relay(&mut self, tx: mpsc::UnboundedSender<WsRelayAction>) -> Result<WsConnId, EmgauwaError> {
|
||||
log::debug!("Relay client connected");
|
||||
|
||||
let relays_json = serde_json::to_string(&self.get_relays()?)?;
|
||||
tx.send(WsRelayAction::Forward(relays_json.clone()))?;
|
||||
|
||||
// register a session with random connection ID
|
||||
let id = random::<WsConnId>();
|
||||
self.relay_sessions.insert(id, tx);
|
||||
|
||||
// send id back
|
||||
Ok(id)
|
||||
}
|
||||
|
||||
async fn disconnect_controller(&mut self, sender: UnboundedSender<WsControllerAction>, emgauwa_uid: EmgauwaUid, update_db: bool) -> Result<(), EmgauwaError> {
|
||||
if let Some((controller, _)) = self.controllers.remove(&emgauwa_uid) {
|
||||
if update_db {
|
||||
let mut pool_conn = block_on(self.pool.acquire())?;
|
||||
log::debug!("Marking controller {} as inactive", controller.c.uid);
|
||||
if let Err(err) = block_on(controller.c.update_active(&mut pool_conn, false)) {
|
||||
log::error!(
|
||||
"Failed to mark controller {} as inactive: {:?}",
|
||||
controller.c.uid,
|
||||
err
|
||||
);
|
||||
return Err(EmgauwaError::from(err));
|
||||
}
|
||||
}
|
||||
log::debug!("Telling websocket server to disconnect a controller");
|
||||
sender.send(WsControllerAction::Disconnect)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Unregister the connection from the controller map and broadcast a disconnection message.
|
||||
async fn disconnect_controller_conn(&mut self, conn_id: WsConnId, update_db: bool) -> Result<(), EmgauwaError> {
|
||||
log::debug!("Trying to find controller connection for disconnect: {}", conn_id);
|
||||
if let Some((sender, Some(emgauwa_uid))) = self.controller_sessions.remove(&conn_id) {
|
||||
self.disconnect_controller(sender, emgauwa_uid, update_db).await?;
|
||||
}
|
||||
self.notify_relay_clients()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn disconnect_relay_conn(&mut self, conn_id: WsConnId) -> Result<(), EmgauwaError> {
|
||||
if let Some(relay_tx) = self.relay_sessions.remove(&conn_id) {
|
||||
relay_tx.send(WsRelayAction::Disconnect)?;
|
||||
}
|
||||
self.notify_relay_clients()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_relays(&mut self) -> Result<Vec<Relay>, EmgauwaError> {
|
||||
let mut pool_conn = block_on(self.pool.acquire())?;
|
||||
let db_controllers = block_on(DbController::get_all(&mut pool_conn))?;
|
||||
let mut controllers: Vec<Controller> = convert_db_list(&mut pool_conn, db_controllers)?;
|
||||
|
||||
controllers.iter_mut().for_each(|c| {
|
||||
if let Some((cc, _)) = self.controllers.get(&c.c.uid) {
|
||||
c.apply_relay_states(&cc.get_relay_states())
|
||||
}
|
||||
});
|
||||
|
||||
let mut relays: Vec<Relay> = Vec::new();
|
||||
controllers.iter().for_each(|c| {
|
||||
relays.extend(c.relays.clone());
|
||||
});
|
||||
|
||||
Ok(relays)
|
||||
}
|
||||
|
||||
fn notify_relay_clients(&mut self) -> Result<(), EmgauwaError> {
|
||||
let relays_json = serde_json::to_string(&self.get_relays()?)?;
|
||||
|
||||
self.relay_sessions.retain(|_, tx| {
|
||||
tx.send(WsRelayAction::Forward(relays_json.clone())).is_ok()
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// You may want to log errors rather than return them in this function.
|
||||
pub async fn run(self) -> io::Result<()> {
|
||||
tokio::select! {
|
||||
output = self.real_run() => output,
|
||||
_ = signal::ctrl_c() => Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn real_run(mut self) -> io::Result<()> {
|
||||
while let Some(cmd) = self.cmd_rx.recv().await {
|
||||
match cmd {
|
||||
WsServerAction::ConnectController(conn_tx, res_tx) => {
|
||||
let conn_id = self.connect_controller(conn_tx).await;
|
||||
let _ = res_tx.send(conn_id);
|
||||
}
|
||||
|
||||
WsServerAction::DisconnectControllerConn(conn, update_db) => {
|
||||
if let Err(err) = self.disconnect_controller_conn(conn, update_db).await {
|
||||
log::error!("Error disconnecting controller: {:?}", err);
|
||||
}
|
||||
}
|
||||
WsServerAction::DisconnectController(emgauwa_uid, update_db) => {
|
||||
if let Some((_, conn_id)) = self.controllers.get(&emgauwa_uid) {
|
||||
if let Err(err ) = self.disconnect_controller_conn(*conn_id, update_db).await {
|
||||
log::error!("Error disconnecting controller: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WsServerAction::ControllerMessage(conn, msg) => {
|
||||
match serde_json::from_str(&msg) {
|
||||
Ok(action) => {
|
||||
self.handle_action(conn, action);
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Error deserializing action: {:?}", e);
|
||||
self.text_to_controller_conn(
|
||||
conn,
|
||||
serde_json::to_string(&EmgauwaError::Serialization(e))
|
||||
.unwrap_or(String::from("Error in deserializing action")),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
WsServerAction::ForwardToController(uid, action) => {
|
||||
log::debug!("Forwarding action: {:?}", action);
|
||||
if let Some((_, address)) = self.controllers.get(&uid) {
|
||||
self.text_to_controller_conn(*address, serde_json::to_string(&action)?);
|
||||
}
|
||||
}
|
||||
WsServerAction::GetRelays(tx) => {
|
||||
tx.send(self.get_relays()).map_err(|_| io::Error::other("Error sending relays"))?;
|
||||
}
|
||||
WsServerAction::ConnectRelay(conn_tx, res_tx) => {
|
||||
if let Ok(conn_id) = self.connect_relay(conn_tx).await {
|
||||
if let Err(e) = res_tx.send(conn_id) {
|
||||
log::error!("Error sending relay conn id: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
WsServerAction::DisconnectRelayConn(conn) => {
|
||||
if let Err(err) = self.disconnect_relay_conn(conn).await {
|
||||
log::error!("Error disconnecting relay conn: {:?}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_action(
|
||||
&mut self,
|
||||
conn: WsConnId,
|
||||
action: ControllersWsAction,
|
||||
) {
|
||||
let action_res = match action {
|
||||
ControllersWsAction::Register(controller) => self.handle_register(conn, controller),
|
||||
ControllersWsAction::RelayStates((controller_uid, relay_states)) => {
|
||||
self.handle_relay_states(controller_uid, relay_states)
|
||||
}
|
||||
_ => Ok(()),
|
||||
};
|
||||
if let Err(e) = action_res {
|
||||
log::error!("Error handling action: {:?}", e);
|
||||
self.text_to_controller_conn(
|
||||
conn,
|
||||
serde_json::to_string(&e).unwrap_or(format!("Error in handling action: {:?}", e)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn text_to_controller_conn(&self, target: WsConnId, msg: String) {
|
||||
if let Some((tx, _)) = self.controller_sessions.get(&target) {
|
||||
log::debug!("Sending message to controller conn {}", target);
|
||||
// errors if a client has disconnected abruptly and hasn't been timed-out yet
|
||||
let _ = tx.send(WsControllerAction::Forward(msg.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_register(
|
||||
&mut self,
|
||||
conn: WsConnId,
|
||||
controller: Controller,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
log::info!(
|
||||
"Registering controller: {} ({})",
|
||||
controller.c.name,
|
||||
controller.c.uid
|
||||
);
|
||||
let mut pool_conn = block_on(self.pool.acquire())?;
|
||||
|
||||
let c = &controller.c;
|
||||
let controller_db = block_on(DbController::get_by_uid_or_create(
|
||||
&mut pool_conn,
|
||||
&c.uid,
|
||||
&c.name,
|
||||
c.relay_count,
|
||||
))?;
|
||||
block_on(controller_db.update_active(&mut pool_conn, true))?;
|
||||
// update only the relay count
|
||||
block_on(controller_db.update(&mut pool_conn, &controller_db.name, c.relay_count))?;
|
||||
|
||||
for relay in &controller.relays {
|
||||
log::debug!(
|
||||
"Registering relay: {} ({})",
|
||||
relay.r.name,
|
||||
match relay.is_on {
|
||||
Some(true) => "+",
|
||||
Some(false) => "-",
|
||||
None => "?",
|
||||
}
|
||||
);
|
||||
let (new_relay, created) = block_on(DbRelay::get_by_controller_and_num_or_create(
|
||||
&mut pool_conn,
|
||||
&controller_db,
|
||||
relay.r.number,
|
||||
&relay.r.name,
|
||||
))?;
|
||||
if created {
|
||||
let mut relay_schedules = Vec::new();
|
||||
for schedule in &relay.schedules {
|
||||
let (new_schedule, _) = block_on(DbSchedule::get_by_uid_or_create(
|
||||
&mut pool_conn,
|
||||
schedule.uid.clone(),
|
||||
&schedule.name,
|
||||
&schedule.periods,
|
||||
))?;
|
||||
relay_schedules.push(new_schedule);
|
||||
}
|
||||
|
||||
block_on(DbJunctionRelaySchedule::set_schedules(
|
||||
&mut pool_conn,
|
||||
&new_relay,
|
||||
relay_schedules.iter().collect(),
|
||||
))?;
|
||||
}
|
||||
}
|
||||
|
||||
let relay_states = controller.get_relay_states();
|
||||
|
||||
let controller_uid = &controller.c.uid;
|
||||
let controller_db = block_on(DbController::get_by_uid(&mut pool_conn, controller_uid))?
|
||||
.ok_or(DatabaseError::InsertGetError)?;
|
||||
let mut controller = Controller::from_db_model(&mut pool_conn, controller_db)?;
|
||||
controller.apply_relay_states(&relay_states);
|
||||
|
||||
self.controllers.insert(controller_uid.clone(), (controller, conn));
|
||||
|
||||
// Update uid in controller session
|
||||
if let Some((tx, _)) = self.controller_sessions.remove(&conn) {
|
||||
log::debug!("Updating controller uid for conn {}", conn);
|
||||
self.controller_sessions.insert(conn, (tx, Some(controller_uid.clone())));
|
||||
}
|
||||
|
||||
log::debug!("Done registering controller");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_relay_states(
|
||||
&mut self,
|
||||
controller_uid: EmgauwaUid,
|
||||
relay_states: RelayStates,
|
||||
) -> Result<(), EmgauwaError> {
|
||||
log::debug!(
|
||||
"Received relay states: {} for {}",
|
||||
utils::printable_relay_states(&relay_states),
|
||||
controller_uid
|
||||
);
|
||||
|
||||
if let Some((controller, _)) = self.controllers.get_mut(&controller_uid) {
|
||||
controller.apply_relay_states(&relay_states);
|
||||
}
|
||||
self.notify_relay_clients()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue