musikr: introduce cover cleanup

Helps reduce overall memory use.
This commit is contained in:
Alexander Capehart 2024-12-27 10:06:04 -05:00
parent 7b35ba840b
commit 8b58f357cb
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 40 additions and 15 deletions

View file

@ -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) {

View file

@ -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<String, UUID>? {

View file

@ -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 {

View file

@ -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}"
}