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