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 // 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 // to really lucky cancellations. Clean those up now that it's impossible for
// the rest of the app to be using them. // the rest of the app to be using them.
covers.cleanup(context) covers.cleanup(newLibrary)
} }
private suspend fun emitIndexingProgress(progress: IndexingProgress) { private suspend fun emitIndexingProgress(progress: IndexingProgress) {

View file

@ -19,9 +19,11 @@
package org.oxycblt.auxio.music package org.oxycblt.auxio.music
import android.content.Context import android.content.Context
import java.io.File
import java.util.UUID import java.util.UUID
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.musikr.Library
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverFiles import org.oxycblt.musikr.cover.CoverFiles
import org.oxycblt.musikr.cover.CoverFormat 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.MutableStoredCovers
import org.oxycblt.musikr.cover.StoredCovers import org.oxycblt.musikr.cover.StoredCovers
class RevisionedCovers(private val revision: UUID, private val inner: MutableStoredCovers) : class RevisionedCovers(
MutableStoredCovers { private val rootDir: File,
private val revision: UUID,
private val inner: MutableStoredCovers
) : MutableStoredCovers {
override suspend fun obtain(id: String): RevisionedCover? { override suspend fun obtain(id: String): RevisionedCover? {
val (coverId, coverRevision) = parse(id) ?: return null val (coverId, coverRevision) = parse(id) ?: return null
if (coverRevision != revision) 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) } 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) { withContext(Dispatchers.IO) {
val exclude = revision.toString() val exclude = revision.toString()
context.filesDir rootDir.listFiles { file -> file.name != exclude }?.forEach { it.deleteRecursively() }
.resolve("covers")
.listFiles { file -> file.name != exclude }
?.forEach { it.deleteRecursively() }
} }
}
companion object { companion object {
suspend fun at(context: Context, revision: UUID): RevisionedCovers { suspend fun at(context: Context, revision: UUID): RevisionedCovers {
val dir = val rootDir: File
withContext(Dispatchers.IO) { val revisionDir: File
context.filesDir.resolve("covers/${revision}").apply { mkdirs() } withContext(Dispatchers.IO) {
} rootDir = context.filesDir.resolve("covers").apply { mkdirs() }
return RevisionedCovers( revisionDir = rootDir.resolve(revision.toString()).apply { mkdirs() }
revision, }
StoredCovers.from(CoverFiles.at(dir), CoverFormat.jpeg(CoverParams.of(750, 80)))) 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>? { 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 write(name: String, block: suspend (OutputStream) -> Unit): CoverFile?
suspend fun deleteWhere(block: (String) -> Boolean)
companion object { companion object {
suspend fun at(dir: File): CoverFiles { suspend fun at(dir: File): CoverFiles {
withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) } 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 { private class CoverFileImpl(private val file: File) : CoverFile {

View file

@ -18,6 +18,8 @@
package org.oxycblt.musikr.cover package org.oxycblt.musikr.cover
import org.oxycblt.musikr.Library
interface StoredCovers { interface StoredCovers {
suspend fun obtain(id: String): Cover? suspend fun obtain(id: String): Cover?
@ -32,6 +34,8 @@ interface StoredCovers {
interface MutableStoredCovers : StoredCovers { interface MutableStoredCovers : StoredCovers {
suspend fun write(data: ByteArray): Cover? suspend fun write(data: ByteArray): Cover?
suspend fun cleanup(assuming: Library)
} }
private class FileStoredCovers( private class FileStoredCovers(
@ -49,6 +53,11 @@ private class FileStoredCovers(
?.let { FileCover(id, it) } ?.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}" private fun getFileName(id: String) = "$id.${coverFormat.extension}"
} }