framesmith 0.1.0

A Rust library for controlling Samsung Frame TVs over the local network
Documentation
use std::net::IpAddr;
use std::time::Duration;

use crate::Error;
use crate::auth_token_store::AuthTokenStore;
use crate::frame_tv::FrameTv;
use crate::frame_tv_impl::{FrameTvImpl, Transport};
use crate::transport::persistent_ws_transport::PersistentWsTransport;
use crate::transport::rest_client::RestClient;
use crate::transport::tls_config;
use crate::transport::ws_transport::WsTransport;

const DEFAULT_TIMEOUT: Duration = Duration::from_secs(5);

pub struct FrameTvConnectionBuilder<S: AuthTokenStore = NoAuthTokenStore> {
    host: IpAddr,
    auth_token_store: S,
    connection_timeout: Duration,
    persistent: bool,
}

impl FrameTv {
    pub fn connection_builder(host: IpAddr) -> FrameTvConnectionBuilder<NoAuthTokenStore> {
        FrameTvConnectionBuilder {
            host,
            auth_token_store: NoAuthTokenStore,
            connection_timeout: DEFAULT_TIMEOUT,
            persistent: false,
        }
    }
}

impl<S: AuthTokenStore> FrameTvConnectionBuilder<S> {
    pub fn connection_timeout(mut self, timeout: Duration) -> Self {
        self.connection_timeout = timeout;
        self
    }

    /// Use persistent (reusable) WebSocket connections instead of opening
    /// a fresh connection for every request. Recommended for long-lived
    /// processes like the framesmith server.
    pub fn persistent(mut self) -> Self {
        self.persistent = true;
        self
    }

    pub async fn connect(self) -> Result<FrameTv, Error> {
        let token = self
            .auth_token_store
            .load_token()
            .await
            .map_err(Error::AuthTokenStore)?;

        let transport = if self.persistent {
            let t =
                PersistentWsTransport::connect(self.host, token, self.connection_timeout).await?;

            // Save any new token
            if let Some(new_token) = t.get_token().await {
                self.auth_token_store
                    .save_token(&new_token)
                    .await
                    .map_err(Error::AuthTokenStore)?;
            }

            Transport::Persistent(Box::new(t))
        } else {
            let t = WsTransport::connect(self.host, token, self.connection_timeout).await?;

            // Save any new token
            if let Some(new_token) = t.get_token().await {
                self.auth_token_store
                    .save_token(&new_token)
                    .await
                    .map_err(Error::AuthTokenStore)?;
            }

            Transport::OneShot(t)
        };

        let rest_client = RestClient::new(self.host, tls_config::make_reqwest_client());

        Ok(FrameTv(FrameTvImpl {
            transport,
            rest_client,
        }))
    }
}

impl FrameTvConnectionBuilder<NoAuthTokenStore> {
    pub fn auth_token_store<S: AuthTokenStore>(self, store: S) -> FrameTvConnectionBuilder<S> {
        FrameTvConnectionBuilder {
            host: self.host,
            auth_token_store: store,
            connection_timeout: self.connection_timeout,
            persistent: self.persistent,
        }
    }
}

pub struct NoAuthTokenStore;

impl AuthTokenStore for NoAuthTokenStore {
    async fn load_token(&self) -> Result<Option<String>, Box<dyn std::error::Error + Send + Sync>> {
        Ok(None)
    }

    async fn save_token(
        &self,
        _token: &str,
    ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
        Ok(())
    }
}