feat: smart thumbnail cleanup and rename-aware file tracking
Delete thumbnails alongside DB rows in all three removal paths (watcher detect, delete-from-disk command, remove-folder command) so orphans no longer accumulate passively. Intercept RenameMode::Both watcher events and handle them as in-place path updates: renames the thumbnail file to the new hash and updates the DB row without touching the embedding, avoiding a full rebuild on move. Falls back to thumbnail regeneration if the file rename fails.
This commit is contained in:
+52
-1
@@ -1676,6 +1676,7 @@ pub fn search_tags_autocomplete(
|
||||
pub struct ImagePathRecord {
|
||||
pub id: i64,
|
||||
pub path: String,
|
||||
pub thumbnail_path: Option<String>,
|
||||
}
|
||||
|
||||
pub fn get_all_image_paths(
|
||||
@@ -1683,19 +1684,69 @@ pub fn get_all_image_paths(
|
||||
folder_id: Option<i64>,
|
||||
) -> Result<Vec<ImagePathRecord>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT id, path FROM images WHERE (?1 IS NULL OR folder_id = ?1) ORDER BY id",
|
||||
"SELECT id, path, thumbnail_path FROM images WHERE (?1 IS NULL OR folder_id = ?1) ORDER BY id",
|
||||
)?;
|
||||
let rows = stmt
|
||||
.query_map(params![folder_id], |row| {
|
||||
Ok(ImagePathRecord {
|
||||
id: row.get(0)?,
|
||||
path: row.get(1)?,
|
||||
thumbnail_path: row.get(2)?,
|
||||
})
|
||||
})?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Returns (image_id, thumbnail_path) for the given path. Used by the watcher
|
||||
/// delete branch so it can clean up the thumbnail file in the same step.
|
||||
pub fn get_image_id_and_thumbnail_by_path(
|
||||
conn: &Connection,
|
||||
path: &str,
|
||||
) -> Result<Option<(i64, Option<String>)>> {
|
||||
let result = conn.query_row(
|
||||
"SELECT id, thumbnail_path FROM images WHERE path = ?1",
|
||||
params![path],
|
||||
|row| Ok((row.get::<_, i64>(0)?, row.get::<_, Option<String>>(1)?)),
|
||||
);
|
||||
match result {
|
||||
Ok(v) => Ok(Some(v)),
|
||||
Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
|
||||
Err(e) => Err(e.into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns all non-null thumbnail_path values for images in a folder.
|
||||
/// Called before folder deletion so callers can remove the files from disk.
|
||||
pub fn get_thumbnail_paths_for_folder(
|
||||
conn: &Connection,
|
||||
folder_id: i64,
|
||||
) -> Result<Vec<String>> {
|
||||
let mut stmt = conn.prepare(
|
||||
"SELECT thumbnail_path FROM images WHERE folder_id = ?1 AND thumbnail_path IS NOT NULL",
|
||||
)?;
|
||||
let rows = stmt
|
||||
.query_map([folder_id], |row| row.get::<_, String>(0))?
|
||||
.collect::<rusqlite::Result<Vec<_>>>()?;
|
||||
Ok(rows)
|
||||
}
|
||||
|
||||
/// Updates a moved/renamed image's path and thumbnail_path in-place.
|
||||
/// Pass `new_thumbnail_path = None` to clear it (triggers regeneration).
|
||||
pub fn update_image_path(
|
||||
conn: &Connection,
|
||||
image_id: i64,
|
||||
new_path: &str,
|
||||
new_filename: &str,
|
||||
new_thumbnail_path: Option<&str>,
|
||||
) -> Result<()> {
|
||||
conn.execute(
|
||||
"UPDATE images SET path = ?2, filename = ?3, thumbnail_path = ?4 WHERE id = ?1",
|
||||
params![image_id, new_path, new_filename, new_thumbnail_path],
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_explore_tags(
|
||||
conn: &Connection,
|
||||
folder_id: Option<i64>,
|
||||
|
||||
Reference in New Issue
Block a user