session.rs

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