1use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
38use tokio::time::{Duration, Instant};
39
40use std::collections::HashMap;
41use std::sync::{Arc, Mutex, RwLock, atomic::{AtomicBool, AtomicU32, Ordering}};
42
43use rpfm_ipc::helpers::SessionInfo;
44use rpfm_ipc::messages::{Command, Response};
45use rpfm_telemetry::info;
46
47use crate::background_thread;
48
49pub const SESSION_SENDER_ERROR: &str = "Error in session communication system. Sender failed to send message.";
51
52pub const DEFAULT_SESSION_TIMEOUT_SECS: u64 = 300;
54
55pub type SessionId = u64;
61
62pub struct SessionManager {
67
68 sessions: Mutex<HashMap<SessionId, ManagedSession>>,
70
71 next_id: Mutex<SessionId>,
73
74 timeout: Duration,
76}
77
78struct ManagedSession {
80
81 session: Arc<Session>,
83
84 disconnected_at: Option<Instant>,
86}
87
88pub struct Session {
93
94 id: SessionId,
96
97 sender: UnboundedSender<(UnboundedSender<Response>, Command)>,
99
100 connection_count: AtomicU32,
102
103 shutdown_requested: AtomicBool,
105
106 pack_names: RwLock<Vec<String>>,
108}
109
110impl Session {
115
116 pub fn new(id: SessionId) -> Arc<Self> {
118 let (sender, receiver) = unbounded_channel();
119
120 let session = Arc::new(Self {
121 id,
122 sender,
123 connection_count: AtomicU32::new(0),
124 shutdown_requested: AtomicBool::new(false),
125 pack_names: RwLock::new(Vec::new()),
126 });
127
128 let session_clone = session.clone();
130 tokio::spawn(async move {
131 info!("Session {} background thread starting...", id);
132 background_thread::background_loop(receiver, session_clone).await;
133 info!("Session {} background thread terminated.", id);
134 });
135
136 session
137 }
138
139 pub fn id(&self) -> SessionId {
141 self.id
142 }
143
144 pub fn connect(&self) {
146 self.connection_count.fetch_add(1, Ordering::SeqCst);
147 }
148
149 pub fn disconnect(&self) {
151 self.connection_count.fetch_sub(1, Ordering::SeqCst);
152 }
153
154 pub fn connection_count(&self) -> u32 {
156 self.connection_count.load(Ordering::SeqCst)
157 }
158
159 pub fn is_shutdown_requested(&self) -> bool {
161 self.shutdown_requested.load(Ordering::SeqCst)
162 }
163
164 pub fn pack_names(&self) -> Vec<String> {
166 self.pack_names.read().unwrap().clone()
167 }
168
169 pub fn add_pack_name(&self, name: &str) {
171 let mut names = self.pack_names.write().unwrap();
172 if !names.contains(&name.to_string()) {
173 names.push(name.to_string());
174 }
175 }
176
177 pub fn remove_pack_name(&self, name: &str) {
179 let mut names = self.pack_names.write().unwrap();
180 names.retain(|n| n != name);
181 }
182
183 pub fn shutdown(&self) {
185 info!("Session {} shutting down...", self.id);
186
187 if self.shutdown_requested.swap(true, Ordering::SeqCst) {
188 info!("Session {} already marked for shutdown before...", self.id);
189 return;
190 }
191
192 let (sender_back, _) = unbounded_channel();
194 let _ = self.sender.send((sender_back, Command::Exit));
195 }
196
197 pub fn send(&self, command: Command) -> UnboundedReceiver<Response> {
201 let (sender_back, receiver_back) = unbounded_channel();
202 if let Err(error) = self.sender.send((sender_back, command)) {
203 let message = format!("{SESSION_SENDER_ERROR}: {error}");
204 info!("{message}");
205 let (sender_back, _) = error.0;
206 let _ = sender_back.send(Response::Error(message));
207 }
208 receiver_back
209 }
210}
211
212impl Default for SessionManager {
213 fn default() -> Self {
214 Self {
215 sessions: Mutex::new(HashMap::new()),
216 next_id: Mutex::new(1),
217 timeout: Duration::from_secs(DEFAULT_SESSION_TIMEOUT_SECS),
218 }
219 }
220}
221
222impl SessionManager {
223
224 pub fn create_session(&self) -> Arc<Session> {
226 let id = {
227 let mut next_id = self.next_id.lock().unwrap();
228 let id = *next_id;
229 *next_id += 1;
230 id
231 };
232
233 let session = Session::new(id);
234 session.connect();
235
236 self.sessions.lock().unwrap().insert(id, ManagedSession {
237 session: session.clone(),
238 disconnected_at: None,
239 });
240
241 info!("Created new session with ID: {}", id);
242 session
243 }
244
245 pub fn get_or_create_session(&self, session_id: Option<SessionId>) -> (Arc<Session>, bool) {
252 if let Some(id) = session_id {
253 let mut sessions = self.sessions.lock().unwrap();
254 if let Some(managed) = sessions.get_mut(&id) {
255
256 if !managed.session.is_shutdown_requested() {
258 managed.session.connect();
259 managed.disconnected_at = None;
260 info!("Client reconnected to existing session {}", id);
261 return (managed.session.clone(), false);
262 }
263 }
264 }
265
266 (self.create_session(), true)
269 }
270
271 pub fn get_session(&self, id: SessionId) -> Option<Arc<Session>> {
273 let sessions = self.sessions.lock().unwrap();
274 sessions.get(&id).map(|m| m.session.clone())
275 }
276
277 pub fn client_disconnected(manager: Arc<Self>, id: SessionId) {
282 let should_schedule_cleanup = {
283 let mut sessions = manager.sessions.lock().unwrap();
284 if let Some(managed) = sessions.get_mut(&id) {
285 managed.session.disconnect();
286
287 if managed.session.connection_count() == 0 {
288 managed.disconnected_at = Some(Instant::now());
289 info!("Session {} has no active connections, will timeout in {:?}", id, manager.timeout);
290 true
291 } else {
292 false
293 }
294 } else {
295 false
296 }
297 };
298
299 if should_schedule_cleanup {
300 Self::schedule_cleanup(manager.clone(), id);
301 }
302 }
303
304 fn schedule_cleanup(manager: Arc<Self>, id: SessionId) {
306 let timeout = manager.timeout;
307 let manager = manager.clone();
308
309 tokio::spawn(async move {
310 tokio::time::sleep(timeout).await;
311 info!("Session {} timeout check triggered (cleanup handled by manager)", id);
312 manager.remove_session(id);
313
314 if manager.session_count() == 0 {
316 info!("No more active sessions, shutting down server...");
317 std::process::exit(0);
318 }
319 });
320 }
321
322 pub fn cleanup_expired_sessions(&self) {
326 let now = Instant::now();
327 let mut to_remove = Vec::new();
328
329 {
330 let sessions = self.sessions.lock().unwrap();
331 for (id, managed) in sessions.iter() {
332 if let Some(disconnected_at) = managed.disconnected_at {
333 if now.duration_since(disconnected_at) >= self.timeout
334 && managed.session.connection_count() == 0
335 {
336 to_remove.push(*id);
337 }
338 }
339 }
340 }
341
342 for id in to_remove {
343 self.remove_session(id);
344 }
345 }
346
347 pub fn remove_session(&self, id: SessionId) -> Option<Arc<Session>> {
349 let mut sessions = self.sessions.lock().unwrap();
350 if let Some(managed) = sessions.remove(&id) {
351 info!("Removing session {}", id);
352 managed.session.shutdown();
353 Some(managed.session)
354 } else {
355 None
356 }
357 }
358
359 pub fn session_count(&self) -> usize {
361 let sessions = self.sessions.lock().unwrap();
362 sessions.len()
363 }
364
365 pub fn session_ids(&self) -> Vec<SessionId> {
367 let sessions = self.sessions.lock().unwrap();
368 sessions.keys().cloned().collect()
369 }
370
371 pub fn get_sessions_info(&self) -> Vec<SessionInfo> {
376 let sessions = self.sessions.lock().unwrap();
377 let now = Instant::now();
378
379 sessions.values().map(|managed| {
380 let timeout_remaining_secs = managed.disconnected_at.map(|disconnected_at| {
381 let elapsed = now.duration_since(disconnected_at);
382 if elapsed < self.timeout {
383 (self.timeout - elapsed).as_secs()
384 } else {
385 0
386 }
387 });
388
389 SessionInfo::new(
390 managed.session.id(),
391 managed.session.connection_count(),
392 timeout_remaining_secs,
393 managed.session.is_shutdown_requested(),
394 managed.session.pack_names(),
395 )
396 }).collect()
397 }
398
399 pub fn start_cleanup_task(manager: Arc<Self>) {
401 let cleanup_interval = manager.timeout / 2; tokio::spawn(async move {
404 loop {
405 tokio::time::sleep(cleanup_interval).await;
406 manager.cleanup_expired_sessions();
407 }
408 });
409 }
410}
411
412pub async fn recv_response(receiver: &mut UnboundedReceiver<Response>) -> Response {
416 match receiver.recv().await {
417 Some(response) => response,
418 None => {
419 info!("Session response channel closed unexpectedly.");
420 Response::Error("Session response channel closed unexpectedly".to_owned())
421 },
422 }
423}