From ae062fd7423da339eb02132d50ea9eb156a68445 Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:34:43 +0200 Subject: [PATCH 1/2] Fix: delete stale Download objects when clearing cache When "Delete downloaded songs and podcast episodes" clears the cache, `deleteCacheFinalStep` sets `relFilePath` to nil but leaves the associated `DownloadMO` relationship intact. `getSongsForCompleteLibraryDownload` requires BOTH `relFilePath == nil` AND `download == nil` to consider a song eligible for download. The stale `DownloadMO` causes every song to be filtered out, making "Download all songs in library" silently do nothing after a cache clear. This follows the same pattern used when deleting songs (lines 185-186) and podcast episodes (lines 217-218), which already delete the associated `DownloadMO` via `context.delete(download)`. Fixes #662 --- AmperfyKit/Storage/LibraryStorage.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/AmperfyKit/Storage/LibraryStorage.swift b/AmperfyKit/Storage/LibraryStorage.swift index 148ce1e0..e03b1f7b 100755 --- a/AmperfyKit/Storage/LibraryStorage.swift +++ b/AmperfyKit/Storage/LibraryStorage.swift @@ -690,6 +690,9 @@ public class LibraryStorage: PlayableFileCachable { private func deleteCacheFinalStep(playable: AbstractPlayable) { playable.contentTypeTranscoded = nil playable.relFilePath = nil + if let download = playable.playableManagedObject.download { + context.delete(download) + } playable.deleteCache() } From 3bb9d04eb0016e7e5a890dc434b961051eabb3bb Mon Sep 17 00:00:00 2001 From: GeiserX <9169332+GeiserX@users.noreply.github.com> Date: Wed, 1 Apr 2026 15:59:48 +0200 Subject: [PATCH 2/2] Also self-heal stale DownloadMO state from older builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous commit prevents new stale DownloadMOs from being created during cache clear. However, users upgrading from older builds may already have stale DownloadMOs (download != nil but relFilePath == nil), which still block getSongsForCompleteLibraryDownload. Remove the download == nil predicate from the fetch. This is safe because DownloadRequestManager.addLowPrio already handles existing downloads correctly: if the song is not cached (!object.isCached), it resets the download and re-queues it (line 106). Songs that are already cached are skipped (line 111). So the predicate was redundant with addLowPrio's own deduplication logic. This makes the fix both preventive (commit 1) and migratory (this commit) — existing broken accounts self-heal on next "Download all songs in library" without requiring manual cleanup. --- AmperfyKit/Storage/LibraryStorage.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/AmperfyKit/Storage/LibraryStorage.swift b/AmperfyKit/Storage/LibraryStorage.swift index e03b1f7b..6b9d933f 100755 --- a/AmperfyKit/Storage/LibraryStorage.swift +++ b/AmperfyKit/Storage/LibraryStorage.swift @@ -1677,7 +1677,6 @@ public class LibraryStorage: PlayableFileCachable { getFetchPredicate(forAccount: account), SongMO.excludeServerDeleteUncachedSongsFetchPredicate, NSPredicate(format: "%K == nil", #keyPath(SongMO.relFilePath)), - NSPredicate(format: "%K == nil", #keyPath(SongMO.download)), ]) let foundSongs = try? context.fetch(fetchRequest) let songs = foundSongs?.compactMap { Song(managedObject: $0) }