Ingest all Claude Code session files into the global DB. Uses offset tracking to only parse new lines since the last run.
(gdb: &GlobalDb)
| 183 | /// Ingest all Claude Code session files into the global DB. |
| 184 | /// Uses offset tracking to only parse new lines since the last run. |
| 185 | pub async fn ingest(gdb: &GlobalDb) -> IngestStats { |
| 186 | let files = find_session_files(); |
| 187 | let mut total_inserted = 0u64; |
| 188 | let mut total_cost = 0.0f64; |
| 189 | let mut total_tokens = 0u64; |
| 190 | |
| 191 | for file_path in &files { |
| 192 | let path_str = file_path.to_string_lossy().to_string(); |
| 193 | |
| 194 | // Check file mtime |
| 195 | let Ok(meta) = fs::metadata(file_path) else { |
| 196 | continue; |
| 197 | }; |
| 198 | let mtime = meta |
| 199 | .modified() |
| 200 | .ok() |
| 201 | .and_then(|t| t.duration_since(std::time::UNIX_EPOCH).ok()) |
| 202 | .map_or(0, |d| d.as_secs()); |
| 203 | |
| 204 | // Check if we've already parsed this file up to this mtime |
| 205 | let prev = gdb.get_parse_offset(&path_str).await.unwrap_or_default(); |
| 206 | let (prev_offset, prev_mtime) = (prev.byte_offset, prev.mtime); |
| 207 | |
| 208 | if mtime == prev_mtime && prev_offset > 0 { |
| 209 | // File hasn't changed since last parse |
| 210 | continue; |
| 211 | } |
| 212 | |
| 213 | let seek_to = if mtime == prev_mtime { |
| 214 | prev_offset |
| 215 | } else if prev_mtime > 0 && mtime > prev_mtime { |
| 216 | // File was appended to -- seek to previous offset |
| 217 | prev_offset |
| 218 | } else { |
| 219 | // File is new or was rewritten -- start from beginning |
| 220 | 0 |
| 221 | }; |
| 222 | |
| 223 | let (project_hash, session_id) = extract_path_parts(file_path); |
| 224 | |
| 225 | let Ok(f) = fs::File::open(file_path) else { |
| 226 | continue; |
| 227 | }; |
| 228 | let mut reader = BufReader::new(f); |
| 229 | |
| 230 | // Seek to the saved offset |
| 231 | if seek_to > 0 && reader.seek(SeekFrom::Start(seek_to)).is_err() { |
| 232 | continue; |
| 233 | } |
| 234 | |
| 235 | let mut line = String::new(); |
| 236 | let mut current_offset = seek_to; |
| 237 | |
| 238 | loop { |
| 239 | line.clear(); |
| 240 | match reader.read_line(&mut line) { |
| 241 | Ok(0) | Err(_) => break, |
| 242 | Ok(n) => { |
no test coverage detected