feat: add Qobuz playlist caching with versioning and infinite scroll UI
- Add qobuz_debug to .gitignore - Bump PMOMusic version to 0.3.26 - Add .cargo/ copy in Dockerfile for registry config - Implement infinite scroll in ServerDrawer.vue with IntersectionObserver and sentinel element - Add source/source_version fields to playlists for cache invalidation - Update pmocache and pmoplaylist DB schemas with versioning (SCHEMA_VERSION) - Add Qobuz API metadata fetching and pagination for playlist tracks - Implement album/playlist cache invalidation based on released_at/updated_at timestamps - Refactor adapt_playlist_items_to_qobuz → adapt_items_to_qobuz with cleaner logic - Add debug mode to save Qobuz API responses when QOBUZ_DEBUG_DIR is set
This commit is contained in:
@@ -404,7 +404,20 @@ impl<C: CacheConfig + 'static> Cache<C> {
|
||||
) -> Result<Self> {
|
||||
let directory = PathBuf::from(dir);
|
||||
std::fs::create_dir_all(&directory)?;
|
||||
let db = DB::init(&directory.join("cache.db"))?;
|
||||
let (db, was_reset) = DB::init(&directory.join("cache.db"))?;
|
||||
|
||||
// Si le schéma a changé, effacer tous les fichiers du cache (cohérence DB/fichiers)
|
||||
if was_reset {
|
||||
tracing::warn!("Cache schema changed, clearing all cache files in {:?}", directory);
|
||||
if let Ok(entries) = std::fs::read_dir(&directory) {
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_file() && path.file_name().map_or(false, |n| n != "cache.db") {
|
||||
std::fs::remove_file(&path).ok();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Créer un channel pour les events (capacité de 100 events en buffer)
|
||||
let (served_tx, _) = broadcast::channel(100);
|
||||
|
||||
@@ -12,6 +12,13 @@ use tracing::{trace, warn};
|
||||
use std::path::Path;
|
||||
use std::str::FromStr;
|
||||
use std::sync::{Mutex, MutexGuard};
|
||||
|
||||
/// Version du schéma de la base de données du cache.
|
||||
///
|
||||
/// Incrémenter cette constante à chaque modification incompatible du schéma.
|
||||
/// Cela provoquera la suppression de la DB **et de tous les fichiers du cache**
|
||||
/// au prochain démarrage.
|
||||
pub const SCHEMA_VERSION: u32 = 1;
|
||||
use std::time::Instant;
|
||||
|
||||
#[cfg(feature = "openapi")]
|
||||
@@ -118,7 +125,33 @@ impl DB {
|
||||
///
|
||||
/// let db = DB::init(Path::new("cache.db")).unwrap();
|
||||
/// ```
|
||||
pub fn init(path: &Path) -> Result<Self, rusqlite::Error> {
|
||||
/// Initialise la DB. Retourne `(db, was_reset)` où `was_reset` indique si la DB
|
||||
/// a été supprimée et recréée suite à un changement de version de schéma.
|
||||
/// Dans ce cas, l'appelant doit aussi effacer les fichiers du cache.
|
||||
pub fn init(path: &Path) -> Result<(Self, bool), rusqlite::Error> {
|
||||
let was_reset = if path.exists() {
|
||||
if let Ok(conn) = Connection::open(path) {
|
||||
let version: u32 = conn
|
||||
.query_row("PRAGMA user_version", [], |r| r.get(0))
|
||||
.unwrap_or(0);
|
||||
if version != SCHEMA_VERSION {
|
||||
drop(conn);
|
||||
warn!(
|
||||
"Cache DB schema version mismatch (found {}, expected {}), recreating",
|
||||
version, SCHEMA_VERSION
|
||||
);
|
||||
std::fs::remove_file(path).ok();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
let conn = Connection::open(path)?;
|
||||
conn.execute("PRAGMA foreign_keys = ON", [])?;
|
||||
|
||||
@@ -184,9 +217,10 @@ impl DB {
|
||||
[],
|
||||
)?;
|
||||
|
||||
Ok(Self {
|
||||
conn: Mutex::new(conn),
|
||||
})
|
||||
// Inscrire la version du schéma
|
||||
conn.execute_batch(&format!("PRAGMA user_version = {}", SCHEMA_VERSION))?;
|
||||
|
||||
Ok((Self { conn: Mutex::new(conn) }, was_reset))
|
||||
}
|
||||
|
||||
/// Ajoute ou met à jour une entrée dans la base de données
|
||||
|
||||
Reference in New Issue
Block a user