1use std::{panic::AssertUnwindSafe, sync::Arc, time::Duration};
2
3use futures::FutureExt;
4use stoat_database::events::client::EventV1;
5use tokio::{
6 select,
7 sync::{Mutex, mpsc},
8};
9
10use crate::{
11 CacheConfig, Context, Error,
12 cache::GlobalCache,
13 context::Events,
14 events::{EventHandler, update_state},
15 http::HttpClient,
16 notifiers::Notifiers,
17 websocket::run,
18};
19
20#[derive(Clone)]
61pub struct Client<H> {
62 pub state: GlobalCache,
63 pub handler: Arc<H>,
64 pub http: HttpClient,
65 pub waiters: Notifiers,
66 pub events: Option<Events>,
67}
68
69impl<H: EventHandler + Clone + Send + Sync + 'static> Client<H> {
70 pub async fn new(handler: H) -> Result<Self, H::Error> {
74 Self::new_with_config(handler, CacheConfig::default()).await
75 }
76
77 pub async fn new_with_config(handler: H, config: CacheConfig) -> Result<Self, H::Error> {
79 Self::new_with_api_url(handler, config, "https://api.stoat.chat").await
80 }
81
82 pub async fn new_with_api_url(
84 handler: H,
85 config: CacheConfig,
86 base_url: impl Into<String>,
87 ) -> Result<Self, H::Error> {
88 let http = HttpClient::new(base_url.into(), None, None).await?;
89
90 Ok(Self {
91 state: GlobalCache::new((*http.api_config).clone(), config),
92 handler: Arc::new(handler),
93 http,
94 waiters: Notifiers::default(),
95 events: None,
96 })
97 }
98
99 pub async fn start(&mut self, token: impl Into<String>) -> Result<(), H::Error> {
103 let token = token.into();
104
105 self.http.token = Some(token.clone());
106 self.http.user_id = Some(self.http.fetch_self().await?.id);
107
108 Ok(())
109 }
110
111 pub async fn run(&mut self, token: impl Into<String>) -> Result<(), H::Error> {
115 let token = token.into();
116
117 self.start(token.clone()).await?;
118
119 let (client_sender, client_receiver) = mpsc::unbounded_channel();
120 self.events = Some(Events(Arc::new(client_sender)));
121
122 let (sender, receiver) = mpsc::unbounded_channel();
123
124 let handle = {
125 let sender = sender.clone();
126 let state = self.state.clone();
127 let token = token.clone();
128 let client_receiver = Arc::new(Mutex::new(client_receiver));
129
130 async move {
131 loop {
132 if let Err(e) = run(
133 sender.clone(),
134 client_receiver.clone(),
135 state.clone(),
136 token.clone(),
137 )
138 .await
139 {
140 log::error!("{e:?}");
141
142 if let Error::Close = e {
143 return Ok(());
144 }
145 }
146
147 log::info!("Disconnected! Reconnecting in 10 seconds.");
148
149 tokio::time::sleep(Duration::from_secs(10)).await;
150 }
151 }
152 };
153
154 let res = select! {
155 e = handle => e,
156 _ = tokio::signal::ctrl_c() => {
157 log::info!("Received ctrl+c. exiting.");
158 Ok(())
159 }
160 _ = self.handle_events(receiver) => {
161 Ok(())
162 }
163 };
164
165 self.cleanup().await;
166
167 res
168 }
169
170 pub async fn cleanup(&mut self) {
172 self.state.cleanup().await;
173 self.waiters.clear_all_waiters().await;
174 self.events = None;
175 }
176
177 async fn handle_events(&self, mut receiver: mpsc::UnboundedReceiver<EventV1>) {
178 while let Some(event) = receiver.recv().await {
179 let this = self.clone();
180
181 tokio::spawn(async move {
182 this.handle_event(event).await;
183 });
184 }
185 }
186
187 pub async fn handle_event(&self, event: EventV1) {
191 let wrapper = AssertUnwindSafe(async {
192 let context = Context {
193 cache: self.state.clone(),
194 http: self.http.clone(),
195 notifiers: self.waiters.clone(),
196 events: self.events.clone().unwrap(),
197 };
198
199 update_state(event.clone(), context.clone(), self.handler.clone()).await
200 });
201
202 if let Err(e) = wrapper.catch_unwind().await {
203 log::error!("{e:?}");
204 }
205 }
206}