Fix for offline mode
🏗️ Build and Test JellyTau / Run Tests (pull_request) Successful in 4m21s
Traceability Validation / Check Requirement Traces (pull_request) Successful in 20s
🏗️ Build and Test JellyTau / Android Compile Check (pull_request) Successful in 4m54s

This commit is contained in:
2026-07-03 19:37:34 +02:00
parent c58cc0cf46
commit 2d141e5bf4
13 changed files with 790 additions and 143 deletions
+61 -18
View File
@@ -64,7 +64,8 @@ impl ThumbnailCache {
image_type: &str,
tag: &str,
) -> Option<PathBuf> {
let query = Query::with_params(
// Primary lookup: exact (item_id, image_type, tag) match.
let exact = Query::with_params(
"SELECT file_path FROM thumbnails
WHERE item_id = ? AND image_type = ? AND image_tag = ?",
vec![
@@ -74,12 +75,60 @@ impl ThumbnailCache {
],
);
let path_str: String = db.query_optional(query, |row| row.get(0)).await.ok()??;
let path = PathBuf::from(&path_str);
if let Ok(Some(path_str)) = db.query_optional(exact, |row| row.get::<_, String>(0)).await {
let path = PathBuf::from(&path_str);
if path.exists() {
self.touch(&db, item_id, image_type, Some(tag)).await;
return Some(path);
}
// File gone — drop the stale row and fall through to the tag-agnostic
// lookup below (another cached image for this item may still exist).
let _ = db.execute(Query::with_params(
"DELETE FROM thumbnails WHERE item_id = ? AND image_type = ? AND image_tag = ?",
vec![
QueryParam::String(item_id.to_string()),
QueryParam::String(image_type.to_string()),
QueryParam::String(tag.to_string()),
],
)).await;
}
// Fallback: any cached image for this item + type, newest first. The
// `image_tag` is a cache-busting version, and callers don't always pass
// the same tag the image was cached under — e.g. the mini player asks for
// the album image using the *track's* primary_image_tag. Ignoring the tag
// here lets those still resolve offline instead of hitting the server.
let any_tag = Query::with_params(
"SELECT file_path FROM thumbnails
WHERE item_id = ? AND image_type = ?
ORDER BY cached_at DESC LIMIT 1",
vec![
QueryParam::String(item_id.to_string()),
QueryParam::String(image_type.to_string()),
],
);
let path_str: String = db.query_optional(any_tag, |row| row.get(0)).await.ok()??;
let path = PathBuf::from(&path_str);
if path.exists() {
// Update last_accessed for LRU tracking
let update_query = Query::with_params(
self.touch(&db, item_id, image_type, None).await;
Some(path)
} else {
None
}
}
/// Update `last_accessed` for LRU tracking. When `tag` is `Some`, scope to
/// that exact row; when `None`, touch every row for the item + type.
async fn touch(
&self,
db: &Arc<RusqliteService>,
item_id: &str,
image_type: &str,
tag: Option<&str>,
) {
let query = match tag {
Some(tag) => Query::with_params(
"UPDATE thumbnails SET last_accessed = CURRENT_TIMESTAMP
WHERE item_id = ? AND image_type = ? AND image_tag = ?",
vec![
@@ -87,23 +136,17 @@ impl ThumbnailCache {
QueryParam::String(image_type.to_string()),
QueryParam::String(tag.to_string()),
],
);
let _ = db.execute(update_query).await;
Some(path)
} else {
// Clean up stale database entry
let delete_query = Query::with_params(
"DELETE FROM thumbnails
WHERE item_id = ? AND image_type = ? AND image_tag = ?",
),
None => Query::with_params(
"UPDATE thumbnails SET last_accessed = CURRENT_TIMESTAMP
WHERE item_id = ? AND image_type = ?",
vec![
QueryParam::String(item_id.to_string()),
QueryParam::String(image_type.to_string()),
QueryParam::String(tag.to_string()),
],
);
let _ = db.execute(delete_query).await;
None
}
),
};
let _ = db.execute(query).await;
}
/// Save thumbnail to cache