feat: expand media discovery and AI workflows #8
@@ -263,6 +263,13 @@ pub fn migrate(conn: &Connection) -> Result<()> {
|
||||
groups_json TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS app_kv (
|
||||
key TEXT PRIMARY KEY,
|
||||
value INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
INSERT OR IGNORE INTO app_kv (key, value) VALUES ('embedding_revision', 0);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_images_folder_id ON images(folder_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_images_modified_at ON images(modified_at);
|
||||
CREATE INDEX IF NOT EXISTS idx_embedding_jobs_status ON embedding_jobs(status);
|
||||
@@ -915,6 +922,13 @@ pub fn mark_embedding_ready(conn: &Connection, image_id: i64, model: &str) -> Re
|
||||
params![image_id, model],
|
||||
)?;
|
||||
conn.execute("DELETE FROM embedding_jobs WHERE image_id = ?1", [image_id])?;
|
||||
// Advance the monotonic revision so the HNSW cache is always rebuilt after
|
||||
// any embedding change, regardless of clock resolution.
|
||||
conn.execute(
|
||||
"INSERT INTO app_kv (key, value) VALUES ('embedding_revision', 1)
|
||||
ON CONFLICT(key) DO UPDATE SET value = value + 1",
|
||||
[],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -373,6 +373,11 @@ fn do_index(app: AppHandle, pool: &DbPool, folder_id: i64, folder_path: PathBuf)
|
||||
let _ = db::backfill_embedding_jobs(&conn)?;
|
||||
db::update_folder_count(&conn, folder_id)?;
|
||||
let _ = db::clear_folder_scan_error(&conn, folder_id);
|
||||
// Invalidate duplicate scan cache — any reindex can change file contents
|
||||
// or the set of files, making cached duplicate groups stale.
|
||||
let folder_scope = format!("folder:{}", folder_id);
|
||||
let _ = db::clear_duplicate_scan_cache(&conn, &folder_scope);
|
||||
let _ = db::clear_duplicate_scan_cache(&conn, "all");
|
||||
}
|
||||
|
||||
emit_progress(
|
||||
|
||||
+11
-7
@@ -215,13 +215,17 @@ pub fn get_image_embedding(conn: &Connection, image_id: i64) -> Result<Option<Ve
|
||||
}
|
||||
|
||||
pub fn get_embedding_revision(conn: &Connection) -> Result<String> {
|
||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM image_vec", [], |row| row.get(0))?;
|
||||
let max_updated_at: Option<String> = conn.query_row(
|
||||
"SELECT MAX(embedding_updated_at) FROM images WHERE embedding_status = 'ready'",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)?;
|
||||
Ok(format!("{}:{}", count, max_updated_at.unwrap_or_default()))
|
||||
// Use the monotonically incremented app_kv counter so that two embeddings
|
||||
// saved within the same clock second still advance the revision, preventing
|
||||
// the HNSW cache from serving stale vectors.
|
||||
let revision: i64 = conn
|
||||
.query_row(
|
||||
"SELECT COALESCE((SELECT value FROM app_kv WHERE key = 'embedding_revision'), 0)",
|
||||
[],
|
||||
|row| row.get(0),
|
||||
)
|
||||
.unwrap_or(0);
|
||||
Ok(revision.to_string())
|
||||
}
|
||||
|
||||
// fn image_ids_for_folder(
|
||||
|
||||
Reference in New Issue
Block a user