Skip to main content

pipedash_web/
state.rs

1use std::sync::{
2    Arc,
3    PoisonError,
4    RwLock as StdRwLock,
5};
6
7use pipedash_core::application::{
8    LogService,
9    PipelineService,
10    PipelineViewService,
11    ProviderService,
12};
13use pipedash_core::domain::{
14    DomainError,
15    PairedDevice,
16};
17use pipedash_core::infrastructure::{
18    ConfigKey,
19    ConfigState,
20    StorageManager,
21};
22use pipedash_core::CoreContext;
23use tokio::sync::{
24    watch,
25    Mutex,
26    RwLock,
27};
28
29use crate::download_tickets::DownloadTickets;
30use crate::error::{
31    ApiResult,
32    AppError,
33};
34use crate::pairing_codes::PairingCodes;
35use crate::push::normalize_relay_url;
36use crate::ws::WebSocketEventBus;
37
38pub struct AppStateInner {
39    pub core: Option<CoreContext>,
40    pub storage_manager: Option<StorageManager>,
41    pub setup_required: bool,
42    pub config_error: Option<String>,
43    pub token_store_ready: bool,
44}
45
46#[derive(Clone)]
47pub struct CurrentDevice(pub String);
48
49#[derive(Clone)]
50pub struct AppState {
51    pub inner: Arc<RwLock<AppStateInner>>,
52    pub ws_event_bus: Arc<WebSocketEventBus>,
53    pub setup_lock: Arc<Mutex<()>>,
54    pub download_tickets: Arc<DownloadTickets>,
55    pub pairing_codes: Arc<PairingCodes>,
56    pub desktop_host: Option<Arc<str>>,
57    pub push_relay: Option<Arc<str>>,
58    pub closing: Arc<watch::Sender<bool>>,
59    api_token: Arc<StdRwLock<Option<Arc<str>>>>,
60}
61
62impl AppState {
63    fn new(
64        ws_event_bus: Arc<WebSocketEventBus>, inner: AppStateInner, desktop_host: Option<Arc<str>>,
65    ) -> Self {
66        Self {
67            inner: Arc::new(RwLock::new(inner)),
68            ws_event_bus,
69            setup_lock: Arc::default(),
70            download_tickets: Arc::default(),
71            pairing_codes: Arc::default(),
72            desktop_host,
73            push_relay: None,
74            closing: Arc::new(watch::channel(false).0),
75            api_token: Arc::default(),
76        }
77    }
78
79    pub fn setup_mode(ws_event_bus: Arc<WebSocketEventBus>) -> Self {
80        Self::new(
81            ws_event_bus,
82            AppStateInner {
83                core: None,
84                storage_manager: None,
85                setup_required: true,
86                config_error: None,
87                token_store_ready: false,
88            },
89            None,
90        )
91    }
92
93    pub fn config_error(ws_event_bus: Arc<WebSocketEventBus>, error: String) -> Self {
94        Self::new(
95            ws_event_bus,
96            AppStateInner {
97                core: None,
98                storage_manager: None,
99                setup_required: false,
100                config_error: Some(error),
101                token_store_ready: false,
102            },
103            None,
104        )
105    }
106
107    pub fn initialized(
108        ws_event_bus: Arc<WebSocketEventBus>, core: CoreContext, storage_manager: StorageManager,
109    ) -> Self {
110        Self::new(
111            ws_event_bus,
112            AppStateInner {
113                core: Some(core),
114                storage_manager: Some(storage_manager),
115                setup_required: false,
116                config_error: None,
117                token_store_ready: false,
118            },
119            None,
120        )
121    }
122
123    pub fn hosted(
124        ws_event_bus: Arc<WebSocketEventBus>, core: Option<CoreContext>, name: &str,
125    ) -> Self {
126        Self::new(
127            ws_event_bus,
128            AppStateInner {
129                token_store_ready: core.is_some(),
130                core,
131                storage_manager: None,
132                setup_required: false,
133                config_error: None,
134            },
135            Some(name.into()),
136        )
137    }
138
139    pub async fn set_core(&self, core: Option<CoreContext>) {
140        let mut inner = self.inner.write().await;
141        inner.token_store_ready = core.is_some();
142        inner.core = core;
143    }
144
145    pub fn api_token(&self) -> Option<Arc<str>> {
146        self.api_token
147            .read()
148            .unwrap_or_else(PoisonError::into_inner)
149            .clone()
150    }
151
152    pub fn set_api_token(&self, token: &str) {
153        *self
154            .api_token
155            .write()
156            .unwrap_or_else(PoisonError::into_inner) = Some(token.into());
157    }
158
159    pub fn requires_auth(&self) -> bool {
160        self.api_token().is_some() || self.desktop_host.is_some()
161    }
162
163    pub async fn paired_device(&self, token: &str) -> Result<Option<PairedDevice>, DomainError> {
164        let Some(core) = self.inner.read().await.core.clone() else {
165            return Ok(None);
166        };
167
168        core.devices()
169            .find_by_token(token)
170            .await
171            .inspect_err(|error| tracing::warn!(%error, "Failed to look up the paired device"))
172    }
173
174    pub fn with_push_relay(mut self, url: &str) -> Self {
175        self.push_relay = Some(url.into());
176        self
177    }
178
179    pub async fn push_relay_url(&self) -> Option<String> {
180        if self.desktop_host.is_some() {
181            return self.push_relay.as_deref().and_then(normalize_relay_url);
182        }
183
184        let config = self.config_state().await.ok()?;
185
186        normalize_relay_url(&config.get_string(ConfigKey::PushRelayUrl).await)
187    }
188
189    pub async fn core(&self) -> ApiResult<CoreContext> {
190        self.inner
191            .read()
192            .await
193            .core
194            .clone()
195            .ok_or_else(AppError::not_initialized)
196    }
197
198    pub async fn provider_service(&self) -> ApiResult<Arc<ProviderService>> {
199        let inner = self.inner.read().await;
200        let core = inner.core.as_ref().ok_or_else(AppError::not_initialized)?;
201        Ok(Arc::clone(&core.provider_service))
202    }
203
204    pub async fn pipeline_service(&self) -> ApiResult<Arc<PipelineService>> {
205        let inner = self.inner.read().await;
206        let core = inner.core.as_ref().ok_or_else(AppError::not_initialized)?;
207        Ok(Arc::clone(&core.pipeline_service))
208    }
209
210    pub async fn pipeline_view_service(&self) -> ApiResult<Arc<PipelineViewService>> {
211        let inner = self.inner.read().await;
212        let core = inner.core.as_ref().ok_or_else(AppError::not_initialized)?;
213        Ok(Arc::clone(&core.pipeline_view_service))
214    }
215
216    pub async fn log_service(&self) -> ApiResult<Arc<LogService>> {
217        let inner = self.inner.read().await;
218        let core = inner.core.as_ref().ok_or_else(AppError::not_initialized)?;
219        Ok(Arc::clone(&core.log_service))
220    }
221
222    pub async fn config_state(&self) -> ApiResult<Arc<ConfigState>> {
223        let inner = self.inner.read().await;
224        let core = inner.core.as_ref().ok_or_else(AppError::not_initialized)?;
225        Ok(Arc::clone(&core.config_state))
226    }
227}