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 pub fn id(&self) -> &str {
51 &self.session_id
52 }
53}
54
55pub struct AppSession {
56 session: Session,
57 _serialization_task: Task<()>,
58 _subscriptions: Vec<Subscription>,
59}
60
61impl AppSession {
62 pub fn new(session: Session, cx: &Context<Self>) -> Self {
63 let _subscriptions = vec![cx.on_app_quit(Self::app_will_quit)];
64
65 #[cfg(not(any(test, feature = "test-support")))]
66 let _serialization_task = cx.spawn(async move |_, cx| {
67 // Disabled in tests: the infinite loop bypasses "parking forbidden" checks,
68 // causing tests to hang instead of panicking.
69 {
70 let mut current_window_stack = Vec::new();
71 loop {
72 if let Some(windows) = cx.update(|cx| window_stack(cx))
73 && windows != current_window_stack
74 {
75 store_window_stack(&windows).await;
76 current_window_stack = windows;
77 }
78
79 cx.background_executor()
80 .timer(std::time::Duration::from_millis(500))
81 .await;
82 }
83 }
84 });
85
86 #[cfg(any(test, feature = "test-support"))]
87 let _serialization_task = Task::ready(());
88
89 Self {
90 session,
91 _subscriptions,
92 _serialization_task,
93 }
94 }
95
96 fn app_will_quit(&mut self, cx: &mut Context<Self>) -> Task<()> {
97 if let Some(window_stack) = window_stack(cx) {
98 cx.background_spawn(async move { store_window_stack(&window_stack).await })
99 } else {
100 Task::ready(())
101 }
102 }
103
104 pub fn id(&self) -> &str {
105 self.session.id()
106 }
107
108 pub fn last_session_id(&self) -> Option<&str> {
109 self.session.old_session_id.as_deref()
110 }
111
112 pub fn last_session_window_stack(&self) -> Option<Vec<WindowId>> {
113 self.session.old_window_ids.clone()
114 }
115}
116
117fn window_stack(cx: &App) -> Option<Vec<u64>> {
118 Some(
119 cx.window_stack()?
120 .into_iter()
121 .map(|window| window.window_id().as_u64())
122 .collect(),
123 )
124}
125
126async fn store_window_stack(windows: &[u64]) {
127 if let Ok(window_ids_json) = serde_json::to_string(windows) {
128 KEY_VALUE_STORE
129 .write_kvp(SESSION_WINDOW_STACK_KEY.to_string(), window_ids_json)
130 .await
131 .log_err();
132 }
133}