session.rs

  1use db::kvp::KeyValueStore;
  2use gpui::{App, AppContext as _, Context, Subscription, Task, WindowId};
  3use util::ResultExt;
  4
  5pub struct Session {
  6    session_id: String,
  7    old_session_id: Option<String>,
  8    old_window_ids: Option<Vec<WindowId>>,
  9}
 10
 11const SESSION_ID_KEY: &str = "session_id";
 12const SESSION_WINDOW_STACK_KEY: &str = "session_window_stack";
 13
 14impl Session {
 15    pub async fn new(session_id: String, db: KeyValueStore) -> Self {
 16        let old_session_id = db.read_kvp(SESSION_ID_KEY).ok().flatten();
 17
 18        db.write_kvp(SESSION_ID_KEY.to_string(), session_id.clone())
 19            .await
 20            .log_err();
 21
 22        let old_window_ids = db
 23            .read_kvp(SESSION_WINDOW_STACK_KEY)
 24            .ok()
 25            .flatten()
 26            .and_then(|json| serde_json::from_str::<Vec<u64>>(&json).ok())
 27            .map(|vec: Vec<u64>| {
 28                vec.into_iter()
 29                    .map(WindowId::from)
 30                    .collect::<Vec<WindowId>>()
 31            });
 32
 33        Self {
 34            session_id,
 35            old_session_id,
 36            old_window_ids,
 37        }
 38    }
 39
 40    #[cfg(any(test, feature = "test-support"))]
 41    pub fn test() -> Self {
 42        Self {
 43            session_id: uuid::Uuid::new_v4().to_string(),
 44            old_session_id: None,
 45            old_window_ids: None,
 46        }
 47    }
 48
 49    #[cfg(any(test, feature = "test-support"))]
 50    pub fn test_with_old_session(old_session_id: String) -> Self {
 51        Self {
 52            session_id: uuid::Uuid::new_v4().to_string(),
 53            old_session_id: Some(old_session_id),
 54            old_window_ids: None,
 55        }
 56    }
 57
 58    pub fn id(&self) -> &str {
 59        &self.session_id
 60    }
 61}
 62
 63pub struct AppSession {
 64    session: Session,
 65    _serialization_task: Task<()>,
 66    _subscriptions: Vec<Subscription>,
 67}
 68
 69impl AppSession {
 70    pub fn new(session: Session, cx: &Context<Self>) -> Self {
 71        let _subscriptions = vec![cx.on_app_quit(Self::app_will_quit)];
 72
 73        #[cfg(not(any(test, feature = "test-support")))]
 74        let _serialization_task = {
 75            let db = KeyValueStore::global(cx);
 76            cx.spawn(async move |_, cx| {
 77                // Disabled in tests: the infinite loop bypasses "parking forbidden" checks,
 78                // causing tests to hang instead of panicking.
 79                {
 80                    let mut current_window_stack = Vec::new();
 81                    loop {
 82                        if let Some(windows) = cx.update(|cx| window_stack(cx))
 83                            && windows != current_window_stack
 84                        {
 85                            store_window_stack(db.clone(), &windows).await;
 86                            current_window_stack = windows;
 87                        }
 88
 89                        cx.background_executor()
 90                            .timer(std::time::Duration::from_millis(500))
 91                            .await;
 92                    }
 93                }
 94            })
 95        };
 96
 97        #[cfg(any(test, feature = "test-support"))]
 98        let _serialization_task = Task::ready(());
 99
100        Self {
101            session,
102            _subscriptions,
103            _serialization_task,
104        }
105    }
106
107    fn app_will_quit(&mut self, cx: &mut Context<Self>) -> Task<()> {
108        if let Some(window_stack) = window_stack(cx) {
109            let db = KeyValueStore::global(cx);
110            cx.background_spawn(async move { store_window_stack(db, &window_stack).await })
111        } else {
112            Task::ready(())
113        }
114    }
115
116    pub fn id(&self) -> &str {
117        self.session.id()
118    }
119
120    pub fn last_session_id(&self) -> Option<&str> {
121        self.session.old_session_id.as_deref()
122    }
123
124    #[cfg(any(test, feature = "test-support"))]
125    pub fn replace_session_for_test(&mut self, session: Session) {
126        self.session = session;
127    }
128
129    pub fn last_session_window_stack(&self) -> Option<Vec<WindowId>> {
130        self.session.old_window_ids.clone()
131    }
132}
133
134fn window_stack(cx: &App) -> Option<Vec<u64>> {
135    Some(
136        cx.window_stack()?
137            .into_iter()
138            .map(|window| window.window_id().as_u64())
139            .collect(),
140    )
141}
142
143async fn store_window_stack(db: KeyValueStore, windows: &[u64]) {
144    if let Ok(window_ids_json) = serde_json::to_string(windows) {
145        db.write_kvp(SESSION_WINDOW_STACK_KEY.to_string(), window_ids_json)
146            .await
147            .log_err();
148    }
149}