diff --git a/src-tauri/src/commands_v2/legacy_compat.rs b/src-tauri/src/commands_v2/legacy_compat.rs index 3df26210a..e8b0a8720 100644 --- a/src-tauri/src/commands_v2/legacy_compat.rs +++ b/src-tauri/src/commands_v2/legacy_compat.rs @@ -923,8 +923,13 @@ pub async fn v2_cache_track_for_offline( let file_path = cache_state.track_file_path(track_id, "flac"); let file_path_str = file_path.to_string_lossy().to_string(); { + let limit_bytes = *cache_state.limit_bytes.lock().await; let guard = cache_state.db.lock().await; let db = guard.as_ref().ok_or("No active session - please log in")?; + // Pre-flight: refuse new downloads when the cache is already at or + // above the configured limit (Fix #5d). + let cache_root = std::path::PathBuf::from(cache_state.get_cache_path()); + crate::offline_cache::maintenance::check_cache_limit(db, &cache_root, limit_bytes)?; db.insert_track(&track_info, &file_path_str)?; } @@ -995,8 +1000,14 @@ pub async fn v2_cache_tracks_batch_for_offline( // Single transactional batch insert { + let limit_bytes = *cache_state.limit_bytes.lock().await; let guard = cache_state.db.lock().await; let db = guard.as_ref().ok_or("No active session - please log in")?; + // Pre-flight: refuse new batch downloads when the cache is already at + // or above the configured limit (Fix #5d). One check covers the whole + // batch; this is intentionally simple and does not predict batch size. + let cache_root = std::path::PathBuf::from(cache_state.get_cache_path()); + crate::offline_cache::maintenance::check_cache_limit(db, &cache_root, limit_bytes)?; let refs: Vec<(&crate::offline_cache::TrackCacheInfo, String)> = batch .iter() .map(|(info, path)| (info, path.clone())) diff --git a/src-tauri/src/offline_cache/maintenance.rs b/src-tauri/src/offline_cache/maintenance.rs index 05308c539..e1502bb07 100644 --- a/src-tauri/src/offline_cache/maintenance.rs +++ b/src-tauri/src/offline_cache/maintenance.rs @@ -41,6 +41,39 @@ pub fn remove_album_cached_tracks( }) } +/// Pre-flight cache-limit check executed before queuing new downloads. +/// +/// Returns `Ok(())` when: +/// - `limit_bytes` is `None` (no limit configured); OR +/// - the current `total_size_bytes` from the cache stats is strictly below the +/// configured limit. +/// +/// Returns `Err(message)` when the cache has already met or exceeded the +/// configured limit. The message is suitable for surfacing to end users via a +/// toast. +/// +/// Intentionally simple: it does not predict the new track's size. Sufficient +/// for v1 — the user gets feedback the moment they hit the limit and can free +/// space or raise the limit through the offline cache manager. +pub fn check_cache_limit( + db: &OfflineCacheDb, + offline_root: &Path, + limit_bytes: Option, +) -> Result<(), String> { + let limit = match limit_bytes { + Some(value) => value, + None => return Ok(()), + }; + let stats = db.get_stats(&offline_root.to_string_lossy(), Some(limit))?; + if stats.total_size_bytes >= limit { + return Err( + "Offline cache limit reached. Free space or raise the limit in the offline cache manager." + .to_string(), + ); + } + Ok(()) +} + /// Filters tracks targeted by re-download: skip in-flight Downloading, /// optionally restrict to Failed only. pub fn select_redownload_targets(