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}