Read the current turn's data for `session_id` from OpenCode's store. Returns `None` when the database is missing or unreadable, the session is unknown, or the session belongs to a different directory than `expected_dir` (a guard against reading an unrelated project's session with a colliding ID).
(session_id: &str, expected_dir: &Path)
| 114 | /// `expected_dir` (a guard against reading an unrelated project's session |
| 115 | /// with a colliding ID). |
| 116 | pub(crate) fn read_turn(session_id: &str, expected_dir: &Path) -> Option<TurnData> { |
| 117 | let db_path = locate_db()?; |
| 118 | |
| 119 | let conn = match Connection::open_with_flags( |
| 120 | &db_path, |
| 121 | OpenFlags::SQLITE_OPEN_READ_ONLY | OpenFlags::SQLITE_OPEN_NO_MUTEX, |
| 122 | ) { |
| 123 | Ok(c) => c, |
| 124 | Err(e) => { |
| 125 | log::debug!("opencode store: cannot open {}: {}", db_path.display(), e); |
| 126 | return None; |
| 127 | } |
| 128 | }; |
| 129 | let _ = conn.busy_timeout(std::time::Duration::from_millis(DB_BUSY_TIMEOUT_MS)); |
| 130 | |
| 131 | // The session row carries the project directory; refuse to read a |
| 132 | // session that belongs to a different checkout. |
| 133 | let directory: String = match conn.query_row( |
| 134 | "SELECT directory FROM session WHERE id = ?1", |
| 135 | [session_id], |
| 136 | |row| row.get(0), |
| 137 | ) { |
| 138 | Ok(d) => d, |
| 139 | Err(e) => { |
| 140 | log::debug!( |
| 141 | "opencode store: session {} not found in {}: {}", |
| 142 | session_id, |
| 143 | db_path.display(), |
| 144 | e |
| 145 | ); |
| 146 | return None; |
| 147 | } |
| 148 | }; |
| 149 | if !same_dir(Path::new(&directory), expected_dir) { |
| 150 | log::warn!( |
| 151 | "opencode store: session {} belongs to '{}', not '{}' — skipping", |
| 152 | session_id, |
| 153 | directory, |
| 154 | expected_dir.display() |
| 155 | ); |
| 156 | return None; |
| 157 | } |
| 158 | |
| 159 | let messages = read_messages(&conn, session_id)?; |
| 160 | let parts = read_parts(&conn, session_id)?; |
| 161 | Some(assemble(&messages, &parts)) |
| 162 | } |
| 163 | |
| 164 | /// Compare two directories, canonicalizing when possible. |
| 165 | fn same_dir(a: &Path, b: &Path) -> bool { |
no test coverage detected