262 lines
7.5 KiB
Rust
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(())
|
|
}
|
|
}
|