use std::path::Path;
use rusqlite::{OptionalExtension, params, params_from_iter};
use super::Storage;
use super::decode::decode_thread_row;
use crate::bridge_protocol::ThreadSummary;
use crate::directory::{directory_contains, normalize_absolute_directory};
impl Storage {
pub fn upsert_thread_index(&self, thread: &ThreadSummary) -> anyhow::Result<()> {
let conn = self.connect()?;
conn.execute(
"INSERT INTO thread_index (
thread_id, runtime_id, name, note, preview, cwd, status,
model_provider, source, created_at_ms, updated_at_ms, is_loaded, is_active,
archived, raw_json
)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15)
ON CONFLICT(thread_id) DO UPDATE SET
runtime_id = excluded.runtime_id,
name = excluded.name,
note = COALESCE(excluded.note, thread_index.note),
preview = excluded.preview,
cwd = excluded.cwd,
status = excluded.status,
model_provider = excluded.model_provider,
source = excluded.source,
created_at_ms = excluded.created_at_ms,
updated_at_ms = excluded.updated_at_ms,
is_loaded = excluded.is_loaded,
is_active = excluded.is_active,
archived = excluded.archived,
raw_json = excluded.raw_json",
params![
thread.id,
thread.runtime_id,
thread.name,
thread.note,
thread.preview,
thread.cwd,
thread.status,
thread.model_provider,
thread.source,
thread.created_at,
thread.updated_at,
if thread.is_loaded { 1_i64 } else { 0_i64 },
if thread.is_active { 1_i64 } else { 0_i64 },
if thread.archived { 1_i64 } else { 0_i64 },
serde_json::to_string(thread)?
],
)?;
Ok(())
}
pub fn get_thread_index(&self, thread_id: &str) -> anyhow::Result<Option<ThreadSummary>> {
let conn = self.connect()?;
let record = conn
.query_row(
"SELECT raw_json, note, archived FROM thread_index WHERE thread_id = ?1",
params![thread_id],
|row| {
decode_thread_row(
row.get::<_, String>(0)?,
row.get::<_, Option<String>>(1)?,
row.get::<_, i64>(2)?,
)
},
)
.optional()?;
Ok(record)
}
pub fn list_thread_index(
&self,
directory_prefix: Option<&str>,
runtime_id: Option<&str>,
archived: Option<bool>,
search_term: Option<&str>,
) -> anyhow::Result<Vec<ThreadSummary>> {
let conn = self.connect()?;
let mut sql = String::from(
"SELECT raw_json, note, archived
FROM thread_index",
);
let mut clauses = Vec::new();
let mut values = Vec::new();
if let Some(runtime_id) = runtime_id {
clauses.push("runtime_id = ?");
values.push(rusqlite::types::Value::from(runtime_id.to_string()));
}
if let Some(archived) = archived {
clauses.push("archived = ?");
values.push(rusqlite::types::Value::from(if archived {
1_i64
} else {
0_i64
}));
}
if let Some(search_term) = search_term.filter(|value| !value.trim().is_empty()) {
clauses.push(
"(LOWER(COALESCE(name, '')) LIKE ? OR LOWER(preview) LIKE ? OR \
LOWER(cwd) LIKE ? OR LOWER(COALESCE(note, '')) LIKE ?)",
);
let pattern = format!("%{}%", search_term.trim().to_lowercase());
values.push(rusqlite::types::Value::from(pattern.clone()));
values.push(rusqlite::types::Value::from(pattern.clone()));
values.push(rusqlite::types::Value::from(pattern.clone()));
values.push(rusqlite::types::Value::from(pattern));
}
if !clauses.is_empty() {
sql.push_str(" WHERE ");
sql.push_str(&clauses.join(" AND "));
}
sql.push_str(" ORDER BY updated_at_ms DESC");
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query_map(params_from_iter(values), |row| {
decode_thread_row(
row.get::<_, String>(0)?,
row.get::<_, Option<String>>(1)?,
row.get::<_, i64>(2)?,
)
})?;
let mut threads = rows.collect::<rusqlite::Result<Vec<_>>>()?;
if let Some(directory_prefix) = directory_prefix {
let prefix = normalize_absolute_directory(Path::new(directory_prefix))?;
threads.retain(|thread| directory_contains(&prefix, Path::new(&thread.cwd)));
}
Ok(threads)
}
pub fn save_thread_note(&self, thread_id: &str, note: Option<&str>) -> anyhow::Result<()> {
let conn = self.connect()?;
conn.execute(
"UPDATE thread_index
SET note = ?2
WHERE thread_id = ?1",
params![thread_id, note],
)?;
Ok(())
}
pub fn set_thread_archived(&self, thread_id: &str, archived: bool) -> anyhow::Result<()> {
let conn = self.connect()?;
conn.execute(
"UPDATE thread_index
SET archived = ?2
WHERE thread_id = ?1",
params![thread_id, if archived { 1_i64 } else { 0_i64 }],
)?;
Ok(())
}
}