1#[cfg(not(feature = "std"))]
8extern crate alloc;
9
10use crate::core::{EditorDocument, EditorError, Result};
11
12#[cfg(feature = "arena")]
13use bumpalo::Bump;
14
15#[cfg(feature = "std")]
16use std::collections::HashMap;
17
18#[cfg(not(feature = "std"))]
19use alloc::collections::BTreeMap as HashMap;
20
21#[cfg(not(feature = "std"))]
22use alloc::{
23 string::{String, ToString},
24 vec::Vec,
25};
26
27#[cfg(feature = "multi-thread")]
28use std::sync::Arc;
29
30#[cfg(all(feature = "plugins", not(feature = "multi-thread")))]
31use std::sync::Arc;
32
33#[cfg(not(feature = "multi-thread"))]
34use core::cell::RefCell;
35
36#[cfg(feature = "multi-thread")]
37use parking_lot::Mutex;
38
39#[derive(Debug, Clone)]
41pub struct SessionConfig {
42 pub max_sessions: usize,
44
45 pub max_memory_per_session: usize,
47
48 pub total_memory_limit: usize,
50
51 pub auto_cleanup: bool,
53
54 pub arena_reset_interval: usize,
56
57 pub share_extensions: bool,
59}
60
61impl Default for SessionConfig {
62 fn default() -> Self {
63 Self {
64 max_sessions: 50,
65 max_memory_per_session: 100 * 1024 * 1024, total_memory_limit: 1024 * 1024 * 1024, auto_cleanup: true,
68 arena_reset_interval: 1000, share_extensions: true,
70 }
71 }
72}
73
74#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct SessionStats {
77 pub active_sessions: usize,
79
80 pub total_memory_usage: usize,
82
83 pub operations_since_cleanup: usize,
85
86 pub arena_resets: usize,
88}
89
90#[derive(Debug)]
92pub struct EditorSession {
93 pub document: EditorDocument,
95
96 pub id: String,
98
99 #[cfg(feature = "std")]
101 pub last_accessed: std::time::Instant,
102
103 pub memory_usage: usize,
105
106 pub operation_count: usize,
108
109 pub metadata: HashMap<String, String>,
111}
112
113impl EditorSession {
114 pub fn new(id: String, document: EditorDocument) -> Self {
116 Self {
117 id,
118 document,
119 #[cfg(feature = "std")]
120 last_accessed: std::time::Instant::now(),
121 memory_usage: 0,
122 operation_count: 0,
123 metadata: HashMap::new(),
124 }
125 }
126
127 #[cfg(feature = "std")]
129 pub fn touch(&mut self) {
130 self.last_accessed = std::time::Instant::now();
131 }
132
133 #[cfg(feature = "std")]
135 pub fn is_stale(&self, max_age: std::time::Duration) -> bool {
136 self.last_accessed.elapsed() > max_age
137 }
138
139 #[must_use]
141 pub fn get_metadata(&self, key: &str) -> Option<&str> {
142 self.metadata.get(key).map(|s| s.as_str())
143 }
144
145 pub fn set_metadata(&mut self, key: String, value: String) {
147 self.metadata.insert(key, value);
148 }
149
150 pub fn increment_operations(&mut self) {
152 self.operation_count += 1;
153 }
154}
155
156struct EditorSessionManagerInner {
162 config: SessionConfig,
164
165 sessions: HashMap<String, EditorSession>,
167
168 active_session_id: Option<String>,
170
171 #[cfg(feature = "arena")]
173 shared_arena: Bump,
174
175 #[cfg(feature = "plugins")]
177 extension_registry: Option<Arc<ass_core::plugin::ExtensionRegistry>>,
178
179 stats: SessionStats,
181
182 #[cfg(feature = "arena")]
184 ops_since_arena_reset: usize,
185}
186
187pub struct EditorSessionManager {
189 #[cfg(feature = "multi-thread")]
190 inner: Arc<Mutex<EditorSessionManagerInner>>,
191 #[cfg(not(feature = "multi-thread"))]
192 inner: RefCell<EditorSessionManagerInner>,
193}
194
195#[cfg(feature = "multi-thread")]
197impl Clone for EditorSessionManager {
198 fn clone(&self) -> Self {
199 Self {
200 inner: self.inner.clone(),
201 }
202 }
203}
204
205impl std::fmt::Debug for EditorSessionManager {
209 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
210 #[cfg(feature = "multi-thread")]
211 {
212 let inner = self.inner.lock();
213 f.debug_struct("EditorSessionManager")
214 .field("config", &inner.config)
215 .field("active_session_id", &inner.active_session_id)
216 .field("sessions", &inner.sessions.keys().collect::<Vec<_>>())
217 .field("stats", &inner.stats)
218 .finish()
219 }
220 #[cfg(not(feature = "multi-thread"))]
221 {
222 let inner = self.inner.borrow();
223 f.debug_struct("EditorSessionManager")
224 .field("config", &inner.config)
225 .field("active_session_id", &inner.active_session_id)
226 .field("sessions", &inner.sessions.keys().collect::<Vec<_>>())
227 .field("stats", &inner.stats)
228 .finish()
229 }
230 }
231}
232
233impl EditorSessionManagerInner {
234 fn new(config: SessionConfig) -> Self {
236 Self {
237 config,
238 sessions: HashMap::new(),
239 active_session_id: None,
240 #[cfg(feature = "arena")]
241 shared_arena: Bump::new(),
242 #[cfg(feature = "plugins")]
243 extension_registry: None,
244 stats: SessionStats {
245 active_sessions: 0,
246 total_memory_usage: 0,
247 operations_since_cleanup: 0,
248 arena_resets: 0,
249 },
250 #[cfg(feature = "arena")]
251 ops_since_arena_reset: 0,
252 }
253 }
254}
255
256impl EditorSessionManager {
257 #[cfg(feature = "multi-thread")]
259 fn with_inner_mut<F, R>(&self, f: F) -> R
260 where
261 F: FnOnce(&mut EditorSessionManagerInner) -> R,
262 {
263 let mut inner = self.inner.lock();
264 f(&mut inner)
265 }
266
267 #[cfg(feature = "multi-thread")]
269 fn with_inner<F, R>(&self, f: F) -> R
270 where
271 F: FnOnce(&EditorSessionManagerInner) -> R,
272 {
273 let inner = self.inner.lock();
274 f(&inner)
275 }
276
277 #[cfg(not(feature = "multi-thread"))]
279 fn with_inner_mut<F, R>(&self, f: F) -> R
280 where
281 F: FnOnce(&mut EditorSessionManagerInner) -> R,
282 {
283 let mut inner = self.inner.borrow_mut();
284 f(&mut inner)
285 }
286
287 #[cfg(not(feature = "multi-thread"))]
289 fn with_inner<F, R>(&self, f: F) -> R
290 where
291 F: FnOnce(&EditorSessionManagerInner) -> R,
292 {
293 let inner = self.inner.borrow();
294 f(&inner)
295 }
296
297 pub fn new() -> Self {
299 Self::with_config(SessionConfig::default())
300 }
301
302 pub fn with_config(config: SessionConfig) -> Self {
304 #[cfg(feature = "multi-thread")]
305 {
306 Self {
307 inner: Arc::new(Mutex::new(EditorSessionManagerInner::new(config))),
308 }
309 }
310 #[cfg(not(feature = "multi-thread"))]
311 {
312 Self {
313 inner: RefCell::new(EditorSessionManagerInner::new(config)),
314 }
315 }
316 }
317
318 pub fn create_session(&mut self, session_id: String) -> Result<()> {
320 self.create_session_with_document(session_id, EditorDocument::new())
321 }
322
323 pub fn create_session_with_document(
325 &mut self,
326 session_id: String,
327 document: EditorDocument,
328 ) -> Result<()> {
329 self.with_inner_mut(|inner| {
330 if inner.sessions.len() >= inner.config.max_sessions {
332 return Err(EditorError::SessionLimitExceeded {
333 current: inner.sessions.len(),
334 limit: inner.config.max_sessions,
335 });
336 }
337
338 let session = EditorSession::new(session_id.clone(), document);
340
341 inner.sessions.insert(session_id.clone(), session);
343
344 inner.stats.active_sessions += 1;
346
347 if inner.active_session_id.is_none() {
349 inner.active_session_id = Some(session_id);
350 }
351
352 Ok(())
353 })
354 }
355
356 pub fn switch_session(&mut self, session_id: &str) -> Result<()> {
358 self.with_inner_mut(|inner| {
359 if !inner.sessions.contains_key(session_id) {
361 return Err(EditorError::DocumentNotFound {
362 id: session_id.to_string(),
363 });
364 }
365
366 inner.active_session_id = Some(session_id.to_string());
368
369 #[cfg(feature = "std")]
371 if let Some(session) = inner.sessions.get_mut(session_id) {
372 session.touch();
373 }
374
375 Ok(())
376 })
377 }
378
379 pub fn active_session(&self) -> Result<Option<String>> {
381 Ok(self.with_inner(|inner| inner.active_session_id.clone()))
382 }
383
384 pub fn with_document<F, R>(&self, session_id: &str, f: F) -> Result<R>
386 where
387 F: FnOnce(&EditorDocument) -> Result<R>,
388 {
389 self.with_inner(|inner| {
390 inner
391 .sessions
392 .get(session_id)
393 .ok_or_else(|| EditorError::DocumentNotFound {
394 id: session_id.to_string(),
395 })
396 .and_then(|session| f(&session.document))
397 })
398 }
399
400 pub fn with_document_mut<F, R>(&mut self, session_id: &str, f: F) -> Result<R>
402 where
403 F: FnOnce(&mut EditorDocument) -> Result<R>,
404 {
405 self.with_inner_mut(|inner| {
406 let session = inner.sessions.get_mut(session_id).ok_or_else(|| {
407 EditorError::DocumentNotFound {
408 id: session_id.to_string(),
409 }
410 })?;
411
412 let result = f(&mut session.document)?;
413 session.increment_operations();
414
415 Ok(result)
416 })
417 }
418
419 pub fn remove_session(&mut self, session_id: &str) -> Result<EditorSession> {
421 self.with_inner_mut(|inner| {
422 let session =
423 inner
424 .sessions
425 .remove(session_id)
426 .ok_or_else(|| EditorError::DocumentNotFound {
427 id: session_id.to_string(),
428 })?;
429
430 inner.stats.active_sessions -= 1;
432 inner.stats.total_memory_usage -= session.memory_usage;
433
434 if inner.active_session_id.as_ref() == Some(&session_id.to_string()) {
436 inner.active_session_id = None;
437 }
438
439 Ok(session)
440 })
441 }
442
443 pub fn list_sessions(&self) -> Result<Vec<String>> {
445 Ok(self.with_inner(|inner| inner.sessions.keys().cloned().collect()))
446 }
447
448 pub fn stats(&self) -> SessionStats {
450 self.with_inner(|inner| inner.stats.clone())
451 }
452
453 #[cfg(feature = "std")]
455 pub fn cleanup_stale_sessions(&mut self, max_age: std::time::Duration) -> Result<usize> {
456 let sessions_to_remove = self.with_inner(|inner| {
458 if !inner.config.auto_cleanup {
459 return vec![];
460 }
461
462 inner
463 .sessions
464 .iter()
465 .filter(|(_, session)| session.is_stale(max_age))
466 .map(|(id, _)| id.clone())
467 .collect::<Vec<_>>()
468 });
469
470 let mut removed_count = 0;
472 for session_id in sessions_to_remove {
473 if self.remove_session(&session_id).is_ok() {
474 removed_count += 1;
475 }
476 }
477
478 Ok(removed_count)
479 }
480
481 #[cfg(feature = "arena")]
483 pub fn reset_shared_arena(&mut self) {
484 self.with_inner_mut(|inner| {
485 inner.shared_arena.reset();
486 inner.stats.arena_resets += 1;
487 inner.ops_since_arena_reset = 0;
488 });
489 }
490
491 #[cfg(feature = "plugins")]
493 pub fn set_extension_registry(&mut self, registry: Arc<ass_core::plugin::ExtensionRegistry>) {
494 self.with_inner_mut(|inner| {
495 inner.extension_registry = Some(registry);
496 });
497 }
498
499 #[cfg(feature = "plugins")]
501 #[must_use]
502 pub fn extension_registry(&self) -> Option<Arc<ass_core::plugin::ExtensionRegistry>> {
503 self.with_inner(|inner| inner.extension_registry.clone())
504 }
505}
506
507impl Default for EditorSessionManager {
508 fn default() -> Self {
509 Self::new()
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516 #[cfg(not(feature = "std"))]
517 use alloc::{string::ToString, vec};
518
519 #[test]
520 fn session_manager_creation() {
521 let manager = EditorSessionManager::new();
522 assert_eq!(manager.stats().active_sessions, 0);
523 assert!(manager.active_session().unwrap().is_none());
524 }
525
526 #[test]
527 fn session_creation_and_switching() {
528 let mut manager = EditorSessionManager::new();
529
530 manager.create_session("session1".to_string()).unwrap();
532 assert_eq!(manager.stats().active_sessions, 1);
533 assert_eq!(
534 manager.active_session().unwrap(),
535 Some("session1".to_string())
536 );
537
538 manager.create_session("session2".to_string()).unwrap();
540 assert_eq!(manager.stats().active_sessions, 2);
541
542 manager.switch_session("session2").unwrap();
544 assert_eq!(
545 manager.active_session().unwrap(),
546 Some("session2".to_string())
547 );
548
549 let sessions = manager.list_sessions().unwrap();
551 assert_eq!(sessions.len(), 2);
552 assert!(sessions.contains(&"session1".to_string()));
553 assert!(sessions.contains(&"session2".to_string()));
554 }
555
556 #[test]
557 fn session_document_access() {
558 let mut manager = EditorSessionManager::new();
559 let doc = EditorDocument::from_content("[Script Info]\nTitle: Test").unwrap();
560
561 manager
562 .create_session_with_document("test".to_string(), doc)
563 .unwrap();
564
565 manager
567 .with_document("test", |doc| {
568 assert!(doc.text().contains("Title: Test"));
569 Ok(())
570 })
571 .unwrap();
572
573 manager
575 .with_document_mut("test", |doc| {
576 doc.insert(crate::core::Position::new(0), "Hello ")?;
577 Ok(())
578 })
579 .unwrap();
580
581 manager
582 .with_document("test", |doc| {
583 assert!(doc.text().starts_with("Hello "));
584 Ok(())
585 })
586 .unwrap();
587 }
588
589 #[test]
590 fn session_removal() {
591 let mut manager = EditorSessionManager::new();
592
593 manager.create_session("test".to_string()).unwrap();
594 assert_eq!(manager.stats().active_sessions, 1);
595
596 let removed_session = manager.remove_session("test").unwrap();
597 assert_eq!(removed_session.id, "test");
598 assert_eq!(manager.stats().active_sessions, 0);
599 assert!(manager.active_session().unwrap().is_none());
600 }
601
602 #[test]
603 fn session_limits() {
604 let config = SessionConfig {
605 max_sessions: 2,
606 ..Default::default()
607 };
608 let mut manager = EditorSessionManager::with_config(config);
609
610 manager.create_session("session1".to_string()).unwrap();
612 manager.create_session("session2".to_string()).unwrap();
613
614 let result = manager.create_session("session3".to_string());
616 assert!(matches!(
617 result,
618 Err(EditorError::SessionLimitExceeded { .. })
619 ));
620 }
621
622 #[test]
623 fn session_metadata() {
624 let mut session = EditorSession::new("test".to_string(), EditorDocument::new());
625
626 assert_eq!(session.get_metadata("key"), None);
627
628 session.set_metadata("key".to_string(), "value".to_string());
629 assert_eq!(session.get_metadata("key"), Some("value"));
630 }
631}