1use crate::error::{CsmError, Result};
6use crate::models::{ChatSession, ChatSessionIndex, ChatSessionIndexEntry};
7use crate::workspace::{get_empty_window_sessions_path, get_workspace_storage_path};
8use rusqlite::Connection;
9use std::path::{Path, PathBuf};
10use sysinfo::System;
11
12pub fn get_workspace_storage_db(workspace_id: &str) -> Result<PathBuf> {
14 let storage_path = get_workspace_storage_path()?;
15 Ok(storage_path.join(workspace_id).join("state.vscdb"))
16}
17
18pub fn read_chat_session_index(db_path: &Path) -> Result<ChatSessionIndex> {
20 let conn = Connection::open(db_path)?;
21
22 let result: std::result::Result<String, rusqlite::Error> = conn.query_row(
23 "SELECT value FROM ItemTable WHERE key = ?",
24 ["chat.ChatSessionStore.index"],
25 |row| row.get(0),
26 );
27
28 match result {
29 Ok(json_str) => serde_json::from_str(&json_str)
30 .map_err(|e| CsmError::InvalidSessionFormat(e.to_string())),
31 Err(rusqlite::Error::QueryReturnedNoRows) => Ok(ChatSessionIndex::default()),
32 Err(e) => Err(CsmError::SqliteError(e)),
33 }
34}
35
36pub fn write_chat_session_index(db_path: &Path, index: &ChatSessionIndex) -> Result<()> {
38 let conn = Connection::open(db_path)?;
39 let json_str = serde_json::to_string(index)?;
40
41 let exists: bool = conn.query_row(
43 "SELECT COUNT(*) > 0 FROM ItemTable WHERE key = ?",
44 ["chat.ChatSessionStore.index"],
45 |row| row.get(0),
46 )?;
47
48 if exists {
49 conn.execute(
50 "UPDATE ItemTable SET value = ? WHERE key = ?",
51 [&json_str, "chat.ChatSessionStore.index"],
52 )?;
53 } else {
54 conn.execute(
55 "INSERT INTO ItemTable (key, value) VALUES (?, ?)",
56 ["chat.ChatSessionStore.index", &json_str],
57 )?;
58 }
59
60 Ok(())
61}
62
63pub fn add_session_to_index(
65 db_path: &Path,
66 session_id: &str,
67 title: &str,
68 last_message_date_ms: i64,
69 is_imported: bool,
70 initial_location: &str,
71 is_empty: bool,
72) -> Result<()> {
73 let mut index = read_chat_session_index(db_path)?;
74
75 index.entries.insert(
76 session_id.to_string(),
77 ChatSessionIndexEntry {
78 session_id: session_id.to_string(),
79 title: title.to_string(),
80 last_message_date: last_message_date_ms,
81 is_imported,
82 initial_location: initial_location.to_string(),
83 is_empty,
84 },
85 );
86
87 write_chat_session_index(db_path, &index)
88}
89
90pub fn register_all_sessions_from_directory(
92 workspace_id: &str,
93 chat_sessions_dir: &Path,
94 force: bool,
95) -> Result<usize> {
96 let db_path = get_workspace_storage_db(workspace_id)?;
97
98 if !db_path.exists() {
99 return Err(CsmError::WorkspaceNotFound(format!(
100 "Database not found: {}",
101 db_path.display()
102 )));
103 }
104
105 if !force && is_vscode_running() {
107 return Err(CsmError::VSCodeRunning);
108 }
109
110 let mut registered = 0;
111
112 for entry in std::fs::read_dir(chat_sessions_dir)? {
113 let entry = entry?;
114 let path = entry.path();
115
116 if path.extension().map(|e| e == "json").unwrap_or(false) {
117 if let Ok(content) = std::fs::read_to_string(&path) {
118 if let Ok(session) = serde_json::from_str::<ChatSession>(&content) {
119 let session_id = session.session_id.clone().unwrap_or_else(|| {
121 path.file_stem()
122 .map(|s| s.to_string_lossy().to_string())
123 .unwrap_or_else(|| uuid::Uuid::new_v4().to_string())
124 });
125
126 let title = session.title();
127 let is_empty = session.is_empty();
128 let last_message_date = session.last_message_date;
129 let initial_location = session.initial_location.clone();
130
131 add_session_to_index(
132 &db_path,
133 &session_id,
134 &title,
135 last_message_date,
136 session.is_imported,
137 &initial_location,
138 is_empty,
139 )?;
140
141 println!(
142 "[OK] Registered: {} ({}...)",
143 title,
144 &session_id[..12.min(session_id.len())]
145 );
146 registered += 1;
147 }
148 }
149 }
150 }
151
152 Ok(registered)
153}
154
155pub fn is_vscode_running() -> bool {
157 let mut sys = System::new();
158 sys.refresh_processes();
159
160 for process in sys.processes().values() {
161 let name = process.name().to_lowercase();
162 if name.contains("code") && !name.contains("codec") {
163 return true;
164 }
165 }
166
167 false
168}
169
170pub fn backup_workspace_sessions(workspace_dir: &Path) -> Result<Option<PathBuf>> {
172 let chat_sessions_dir = workspace_dir.join("chatSessions");
173
174 if !chat_sessions_dir.exists() {
175 return Ok(None);
176 }
177
178 let timestamp = std::time::SystemTime::now()
179 .duration_since(std::time::UNIX_EPOCH)
180 .unwrap()
181 .as_secs();
182
183 let backup_dir = workspace_dir.join(format!("chatSessions-backup-{}", timestamp));
184
185 copy_dir_all(&chat_sessions_dir, &backup_dir)?;
187
188 Ok(Some(backup_dir))
189}
190
191fn copy_dir_all(src: &Path, dst: &Path) -> Result<()> {
193 std::fs::create_dir_all(dst)?;
194
195 for entry in std::fs::read_dir(src)? {
196 let entry = entry?;
197 let src_path = entry.path();
198 let dst_path = dst.join(entry.file_name());
199
200 if src_path.is_dir() {
201 copy_dir_all(&src_path, &dst_path)?;
202 } else {
203 std::fs::copy(&src_path, &dst_path)?;
204 }
205 }
206
207 Ok(())
208}
209
210pub fn read_empty_window_sessions() -> Result<Vec<ChatSession>> {
217 let sessions_path = get_empty_window_sessions_path()?;
218
219 if !sessions_path.exists() {
220 return Ok(Vec::new());
221 }
222
223 let mut sessions = Vec::new();
224
225 for entry in std::fs::read_dir(&sessions_path)? {
226 let entry = entry?;
227 let path = entry.path();
228
229 if path.extension().is_some_and(|e| e == "json") {
230 if let Ok(content) = std::fs::read_to_string(&path) {
231 if let Ok(session) = serde_json::from_str::<ChatSession>(&content) {
232 sessions.push(session);
233 }
234 }
235 }
236 }
237
238 sessions.sort_by(|a, b| b.last_message_date.cmp(&a.last_message_date));
240
241 Ok(sessions)
242}
243
244#[allow(dead_code)]
246pub fn get_empty_window_session(session_id: &str) -> Result<Option<ChatSession>> {
247 let sessions_path = get_empty_window_sessions_path()?;
248 let session_path = sessions_path.join(format!("{}.json", session_id));
249
250 if !session_path.exists() {
251 return Ok(None);
252 }
253
254 let content = std::fs::read_to_string(&session_path)?;
255 let session: ChatSession = serde_json::from_str(&content)
256 .map_err(|e| CsmError::InvalidSessionFormat(e.to_string()))?;
257
258 Ok(Some(session))
259}
260
261#[allow(dead_code)]
263pub fn write_empty_window_session(session: &ChatSession) -> Result<PathBuf> {
264 let sessions_path = get_empty_window_sessions_path()?;
265
266 std::fs::create_dir_all(&sessions_path)?;
268
269 let session_id = session.session_id.as_deref().unwrap_or("unknown");
270 let session_path = sessions_path.join(format!("{}.json", session_id));
271 let content = serde_json::to_string_pretty(session)?;
272 std::fs::write(&session_path, content)?;
273
274 Ok(session_path)
275}
276
277#[allow(dead_code)]
279pub fn delete_empty_window_session(session_id: &str) -> Result<bool> {
280 let sessions_path = get_empty_window_sessions_path()?;
281 let session_path = sessions_path.join(format!("{}.json", session_id));
282
283 if session_path.exists() {
284 std::fs::remove_file(&session_path)?;
285 Ok(true)
286 } else {
287 Ok(false)
288 }
289}
290
291pub fn count_empty_window_sessions() -> Result<usize> {
293 let sessions_path = get_empty_window_sessions_path()?;
294
295 if !sessions_path.exists() {
296 return Ok(0);
297 }
298
299 let count = std::fs::read_dir(&sessions_path)?
300 .filter_map(|e| e.ok())
301 .filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
302 .count();
303
304 Ok(count)
305}