streamdeck/src/obs.rs
2021-05-05 18:55:47 -05:00

262 lines
7.5 KiB
Rust

use crate::{
message::{Command, ObsMessage, Query, State},
store::Store,
};
use obws::Client;
use tokio::sync::mpsc::{Receiver, Sender};
pub(crate) struct Obs {
store: Store,
inner: Inner,
}
enum Inner {
Disconnected(Disconnected),
Unauthenticated(Unauthenticated),
Connected(Connected),
}
#[derive(Debug)]
struct Disconnected;
struct Unauthenticated {
client: Option<Client>,
}
struct Connected {
client: Option<Client>,
}
pub(crate) async fn task(store: Store, tx: Sender<ObsMessage>, mut rx: Receiver<ObsMessage>) {
let store2 = store.clone();
tokio::task::spawn(async move {
if let Err(e) = connect_obs(store2, tx).await {
log::info!("Failed to connect to OBS: {}", e);
}
});
let mut obs = Obs {
store,
inner: Inner::Disconnected(Disconnected),
};
while let Some(msg) = rx.recv().await {
obs.handle(msg).await;
}
}
async fn connect_obs(store: Store, tx: Sender<ObsMessage>) -> Result<(), anyhow::Error> {
let host = store
.setting("obs-host")
.await?
.ok_or_else(|| anyhow::anyhow!("No key 'obs-host'"))?;
let port = store
.setting("obs-port")
.await?
.ok_or_else(|| anyhow::anyhow!("No key 'obs-port'"))?;
if tx.send(ObsMessage::Connect(host, port)).await.is_err() {
return Err(anyhow::anyhow!("Failed to send connect message"));
}
let (sender, rx) = tokio::sync::oneshot::channel();
if tx.send(ObsMessage::State(sender)).await.is_err() {
return Err(anyhow::anyhow!("Failed to send state message"));
}
match rx.await? {
State::Disconnected => return Err(anyhow::anyhow!("Failed to connect to obs")),
State::Connected => return Ok(()),
State::Unauthenticated => {
let password = store
.setting("obs-password")
.await?
.ok_or_else(|| anyhow::anyhow!("No stored password"))?;
if tx.send(ObsMessage::Authenticate(password)).await.is_err() {
return Err(anyhow::anyhow!("Failed to send authenticate message"));
}
}
}
let (sender, rx) = tokio::sync::oneshot::channel();
if tx.send(ObsMessage::State(sender)).await.is_err() {
return Err(anyhow::anyhow!("Failed to send state message"));
}
if !matches!(rx.await?, State::Connected) {
return Err(anyhow::anyhow!("Failed to authenticate with OBS"));
}
Ok(())
}
impl Obs {
async fn handle(&mut self, message: ObsMessage) {
match message {
ObsMessage::State(sender) => {
let state = match &self.inner {
Inner::Disconnected(_) => State::Disconnected,
Inner::Unauthenticated(_) => State::Unauthenticated,
Inner::Connected(_) => State::Connected,
};
let _ = sender.send(state);
}
message => {
self.inner.handle(message, &self.store).await;
}
}
}
}
impl Inner {
async fn handle(&mut self, message: ObsMessage, store: &Store) {
let new_state = match self {
Inner::Disconnected(disconnected) => disconnected.handle(message, store).await,
Inner::Unauthenticated(unauthenticated) => unauthenticated.handle(message, store).await,
Inner::Connected(connected) => connected.handle(message).await,
};
if let Some(state) = new_state {
let _ = std::mem::replace(self, state);
}
}
}
impl Disconnected {
async fn handle(&mut self, message: ObsMessage, store: &Store) -> Option<Inner> {
match message {
ObsMessage::Connect(host, port) => self
.connect(host, port, store)
.await
.map_err(|e| log::error!("{}", e))
.ok(),
_ => None,
}
}
async fn connect(
&mut self,
host: String,
port: u16,
store: &Store,
) -> Result<Inner, anyhow::Error> {
log::info!("Connecting to {}:{}", host, port);
let client = Client::connect(host.clone(), port).await?;
let auth_required = client.general().get_auth_required().await?;
store.save_setting("obs-host", host).await?;
store.save_setting("obs-port", port).await?;
if auth_required.auth_required {
return Ok(Inner::Unauthenticated(Unauthenticated {
client: Some(client),
}));
}
Ok(Inner::Connected(Connected {
client: Some(client),
}))
}
}
impl Unauthenticated {
async fn handle(&mut self, message: ObsMessage, store: &Store) -> Option<Inner> {
match message {
ObsMessage::Authenticate(password) => self.authenticate(password, store).await.ok(),
_ => None,
}
}
async fn authenticate(
&mut self,
password: String,
store: &Store,
) -> Result<Inner, anyhow::Error> {
if let Some(client) = self.client.as_ref() {
if let Err(e) = client.login(Some(password.clone())).await {
if client.general().get_version().await.is_ok() {
return Err(e.into());
} else {
return Ok(Inner::Disconnected(Disconnected));
}
}
}
store.save_setting("obs-password", password).await?;
Ok(Inner::Connected(Connected {
client: self.client.take(),
}))
}
}
impl Connected {
async fn handle(&mut self, message: ObsMessage) -> Option<Inner> {
match message {
ObsMessage::Command(command) => {
let res = self.command(command).await;
self.check_err(res).await
}
ObsMessage::Query(query) => {
let res = self.query(query).await;
self.check_err(res).await
}
ObsMessage::Disconnect => {
let _ = self.disconnect().await;
Some(Inner::Disconnected(Disconnected))
}
_ => None,
}
}
async fn check_err(&mut self, res: Result<(), anyhow::Error>) -> Option<Inner> {
match res {
Ok(()) => None,
Err(_) => {
if let Some(client) = self.client.as_ref() {
if client.general().get_version().await.is_ok() {
return None;
}
}
Some(Inner::Disconnected(Disconnected))
}
}
}
async fn command(&self, command: Command) -> Result<(), anyhow::Error> {
match command {
Command::SwitchScene { name } => {
if let Some(client) = self.client.as_ref() {
client.scenes().set_current_scene(&name).await?;
}
Ok(())
}
}
}
async fn query(&self, query: Query) -> Result<(), anyhow::Error> {
match query {
Query::GetScenes(sender) => {
if let Some(client) = self.client.as_ref() {
let scene_list = client.scenes().get_scene_list().await?;
let _ = sender.send(scene_list.scenes.into_iter().map(|s| s.name).collect());
}
Ok(())
}
}
}
async fn disconnect(&mut self) -> Result<(), anyhow::Error> {
if let Some(client) = self.client.as_mut() {
client.disconnect().await;
}
Ok(())
}
}