Ingest one transcript file: load the prior cursor, parse new content, persist the advanced cursor, then upsert the session (merging preserved fields) and its new messages.
(
db: &GlobalDb,
source: &dyn TranscriptSource,
path: &Path,
project_root: &Path,
max_new_bytes: Option<u64>,
)
| 144 | /// the advanced cursor, then upsert the session (merging preserved fields) and |
| 145 | /// its new messages. |
| 146 | async fn ingest_one( |
| 147 | db: &GlobalDb, |
| 148 | source: &dyn TranscriptSource, |
| 149 | path: &Path, |
| 150 | project_root: &Path, |
| 151 | max_new_bytes: Option<u64>, |
| 152 | ) -> TranscriptIngestStats { |
| 153 | let path_str = path.to_string_lossy().to_string(); |
| 154 | let prev_offset = db.get_parse_offset(&path_str).await.unwrap_or_default(); |
| 155 | let prev = StoredCursor { |
| 156 | position: prev_offset.byte_offset, |
| 157 | mtime: prev_offset.mtime, |
| 158 | file_id: prev_offset.file_id, |
| 159 | }; |
| 160 | let Some(parsed) = source.parse_new(path, prev, project_root, max_new_bytes) else { |
| 161 | return TranscriptIngestStats::default(); |
| 162 | }; |
| 163 | |
| 164 | if parsed.messages.is_empty() { |
| 165 | // Non-message append (e.g. blank/undecodable rows) still advances the |
| 166 | // cursor so the next ingest only sees genuinely new content. |
| 167 | db.set_parse_offset( |
| 168 | &path_str, |
| 169 | ParseOffset { |
| 170 | byte_offset: parsed.new_cursor.position, |
| 171 | mtime: parsed.new_cursor.mtime, |
| 172 | file_id: parsed.new_cursor.file_id, |
| 173 | }, |
| 174 | ) |
| 175 | .await; |
| 176 | return TranscriptIngestStats::default(); |
| 177 | } |
| 178 | |
| 179 | let provider = source.provider(); |
| 180 | let draft = parsed.draft; |
| 181 | let existing = db.get_session(provider, &draft.session_id).await; |
| 182 | // Preserve the session's original start time and title across appends; only |
| 183 | // advance ended_at to the latest message seen. |
| 184 | let started_at = existing |
| 185 | .as_ref() |
| 186 | .and_then(|session| session.started_at) |
| 187 | .or_else(|| { |
| 188 | parsed |
| 189 | .messages |
| 190 | .first() |
| 191 | .and_then(|message| message.timestamp) |
| 192 | }); |
| 193 | let title = existing |
| 194 | .as_ref() |
| 195 | .and_then(|session| session.title.clone()) |
| 196 | .or(draft.title); |
| 197 | let ended_at = parsed |
| 198 | .messages |
| 199 | .last() |
| 200 | .and_then(|message| message.timestamp) |
| 201 | .or_else(|| existing.as_ref().and_then(|session| session.ended_at)); |
| 202 | |
| 203 | let session = SessionRecord { |
no test coverage detected