From 8b58f357cb479b2150f450d313eb60289b21d24d Mon Sep 17 00:00:00 2001 From: Alexander Capehart Date: Fri, 27 Dec 2024 10:06:04 -0500 Subject: [PATCH] musikr: introduce cover cleanup Helps reduce overall memory use. --- .../oxycblt/auxio/music/MusicRepository.kt | 2 +- .../oxycblt/auxio/music/RevisionedCovers.kt | 36 +++++++++++-------- .../org/oxycblt/musikr/cover/CoverFiles.kt | 8 +++++ .../org/oxycblt/musikr/cover/StoredCovers.kt | 9 +++++ 4 files changed, 40 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index 4c5c631b7..1e57a5390 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -432,7 +432,7 @@ constructor( // Old cover revisions may be lying around, even during a normal refresh due // to really lucky cancellations. Clean those up now that it's impossible for // the rest of the app to be using them. - covers.cleanup(context) + covers.cleanup(newLibrary) } private suspend fun emitIndexingProgress(progress: IndexingProgress) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/RevisionedCovers.kt b/app/src/main/java/org/oxycblt/auxio/music/RevisionedCovers.kt index 0cacd3ce0..b43b386b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/RevisionedCovers.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/RevisionedCovers.kt @@ -19,9 +19,11 @@ package org.oxycblt.auxio.music import android.content.Context +import java.io.File import java.util.UUID import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.oxycblt.musikr.Library import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.CoverFiles import org.oxycblt.musikr.cover.CoverFormat @@ -29,8 +31,11 @@ import org.oxycblt.musikr.cover.CoverParams import org.oxycblt.musikr.cover.MutableStoredCovers import org.oxycblt.musikr.cover.StoredCovers -class RevisionedCovers(private val revision: UUID, private val inner: MutableStoredCovers) : - MutableStoredCovers { +class RevisionedCovers( + private val rootDir: File, + private val revision: UUID, + private val inner: MutableStoredCovers +) : MutableStoredCovers { override suspend fun obtain(id: String): RevisionedCover? { val (coverId, coverRevision) = parse(id) ?: return null if (coverRevision != revision) return null @@ -41,24 +46,27 @@ class RevisionedCovers(private val revision: UUID, private val inner: MutableSto return inner.write(data)?.let { RevisionedCover(revision, it) } } - suspend fun cleanup(context: Context) = + override suspend fun cleanup(assuming: Library) { + inner.cleanup(assuming) + + // Destroy old revisions no longer being used. withContext(Dispatchers.IO) { val exclude = revision.toString() - context.filesDir - .resolve("covers") - .listFiles { file -> file.name != exclude } - ?.forEach { it.deleteRecursively() } + rootDir.listFiles { file -> file.name != exclude }?.forEach { it.deleteRecursively() } } + } companion object { suspend fun at(context: Context, revision: UUID): RevisionedCovers { - val dir = - withContext(Dispatchers.IO) { - context.filesDir.resolve("covers/${revision}").apply { mkdirs() } - } - return RevisionedCovers( - revision, - StoredCovers.from(CoverFiles.at(dir), CoverFormat.jpeg(CoverParams.of(750, 80)))) + val rootDir: File + val revisionDir: File + withContext(Dispatchers.IO) { + rootDir = context.filesDir.resolve("covers").apply { mkdirs() } + revisionDir = rootDir.resolve(revision.toString()).apply { mkdirs() } + } + val files = CoverFiles.at(revisionDir) + val format = CoverFormat.jpeg(CoverParams.of(750, 80)) + return RevisionedCovers(rootDir, revision, StoredCovers.from(files, format)) } private fun parse(id: String): Pair? { diff --git a/musikr/src/main/java/org/oxycblt/musikr/cover/CoverFiles.kt b/musikr/src/main/java/org/oxycblt/musikr/cover/CoverFiles.kt index 91a694759..39a35cf93 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cover/CoverFiles.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cover/CoverFiles.kt @@ -32,6 +32,8 @@ interface CoverFiles { suspend fun write(name: String, block: suspend (OutputStream) -> Unit): CoverFile? + suspend fun deleteWhere(block: (String) -> Boolean) + companion object { suspend fun at(dir: File): CoverFiles { withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) } @@ -83,6 +85,12 @@ private class CoverFilesImpl(private val dir: File) : CoverFiles { } } } + + override suspend fun deleteWhere(block: (String) -> Boolean) { + withContext(Dispatchers.IO) { + dir.listFiles { file -> block(file.name) }?.forEach { it.deleteRecursively() } + } + } } private class CoverFileImpl(private val file: File) : CoverFile { diff --git a/musikr/src/main/java/org/oxycblt/musikr/cover/StoredCovers.kt b/musikr/src/main/java/org/oxycblt/musikr/cover/StoredCovers.kt index 5b7452a20..6f68851a8 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cover/StoredCovers.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cover/StoredCovers.kt @@ -18,6 +18,8 @@ package org.oxycblt.musikr.cover +import org.oxycblt.musikr.Library + interface StoredCovers { suspend fun obtain(id: String): Cover? @@ -32,6 +34,8 @@ interface StoredCovers { interface MutableStoredCovers : StoredCovers { suspend fun write(data: ByteArray): Cover? + + suspend fun cleanup(assuming: Library) } private class FileStoredCovers( @@ -49,6 +53,11 @@ private class FileStoredCovers( ?.let { FileCover(id, it) } } + override suspend fun cleanup(assuming: Library) { + val used = assuming.songs.mapNotNullTo(mutableSetOf()) { it.cover?.id?.let(::getFileName) } + coverFiles.deleteWhere { it !in used } + } + private fun getFileName(id: String) = "$id.${coverFormat.extension}" }