musikr: simplify cover storage boundaries

This commit is contained in:
Alexander Capehart 2024-12-26 19:22:54 -05:00
parent dc8cbc74e8
commit b8178056f5
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 40 additions and 42 deletions

View file

@ -385,7 +385,7 @@ constructor(
val currentRevision = musicSettings.revision val currentRevision = musicSettings.revision
val newRevision = currentRevision?.takeIf { withCache } ?: UUID.randomUUID() val newRevision = currentRevision?.takeIf { withCache } ?: UUID.randomUUID()
val cache = if (withCache) storedCache.visible() else storedCache.invisible() val cache = if (withCache) storedCache.visible() else storedCache.invisible()
val covers = MutableRevisionedStoredCovers(context, newRevision) val covers = RevisionedCovers.at(context, newRevision)
val storage = Storage(cache, covers, storedPlaylists) val storage = Storage(cache, covers, storedPlaylists)
val interpretation = Interpretation(nameFactory, separators) val interpretation = Interpretation(nameFactory, separators)
@ -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.
RevisionedStoredCovers.cleanup(context, newRevision) RevisionedCovers.cleanup(context, newRevision)
} }
private suspend fun emitIndexingProgress(progress: IndexingProgress) { private suspend fun emitIndexingProgress(progress: IndexingProgress) {

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2024 Auxio Project * Copyright (c) 2024 Auxio Project
* RevisionedLibrary.kt is part of Auxio. * RevisionedCovers.kt is part of Auxio.
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -22,51 +22,50 @@ import android.content.Context
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.auxio.util.unlikelyToBeNull
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverFiles
import org.oxycblt.musikr.cover.CoverFormat import org.oxycblt.musikr.cover.CoverFormat
import org.oxycblt.musikr.cover.MutableStoredCovers import org.oxycblt.musikr.cover.MutableStoredCovers
import org.oxycblt.musikr.cover.StoredCovers import org.oxycblt.musikr.cover.StoredCovers
open class RevisionedStoredCovers(private val context: Context, private val revision: UUID?) : class RevisionedCovers(private val revision: UUID, private val inner: MutableStoredCovers) :
StoredCovers { MutableStoredCovers {
protected val inner =
revision?.let { StoredCovers.from(context, "covers_$it", CoverFormat.jpeg()) }
override suspend fun obtain(id: String): RevisionedCover? { override suspend fun obtain(id: String): RevisionedCover? {
val (coverId, coverRevision) = parse(id) ?: return null
if (coverRevision != revision) return null
return inner.obtain(coverId)?.let { RevisionedCover(revision, it) }
}
override suspend fun write(data: ByteArray): RevisionedCover? {
return inner.write(data)?.let { RevisionedCover(revision, it) }
}
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())))
}
suspend fun cleanup(context: Context, exclude: UUID) =
withContext(Dispatchers.IO) {
val excludeName = exclude.toString()
context.filesDir
.resolve("covers")
.listFiles { file -> file.name != excludeName }
?.forEach { it.deleteRecursively() }
}
private fun parse(id: String): Pair<String, UUID>? {
val split = id.split('@', limit = 2) val split = id.split('@', limit = 2)
if (split.size != 2) return null if (split.size != 2) return null
val (coverId, coverRevisionStr) = split val (coverId, coverRevisionStr) = split
val coverRevision = coverRevisionStr.toUuidOrNull() ?: return null val coverRevision = coverRevisionStr.toUuidOrNull() ?: return null
if (revision != null) { return coverId to coverRevision
if (coverRevision != revision) {
return null
} }
val storedCovers = unlikelyToBeNull(inner)
return storedCovers.obtain(coverId)?.let { RevisionedCover(revision, it) }
} else {
val storedCovers =
StoredCovers.from(context, "covers_$coverRevision", CoverFormat.jpeg())
return storedCovers.obtain(coverId)?.let { RevisionedCover(coverRevision, it) }
}
}
companion object {
suspend fun cleanup(context: Context, exclude: UUID) =
withContext(Dispatchers.IO) {
context.filesDir
.listFiles { file ->
file.name.startsWith("covers_") && file.name != "covers_$exclude"
}
?.forEach { it.deleteRecursively() }
}
}
}
class MutableRevisionedStoredCovers(context: Context, private val revision: UUID) :
RevisionedStoredCovers(context, revision), MutableStoredCovers {
override suspend fun write(data: ByteArray): RevisionedCover? {
return unlikelyToBeNull(inner).write(data)?.let { RevisionedCover(revision, it) }
} }
} }

View file

@ -18,7 +18,6 @@
package org.oxycblt.musikr.cover package org.oxycblt.musikr.cover
import android.content.Context
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
@ -27,18 +26,20 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
internal interface CoverFiles { interface CoverFiles {
suspend fun find(id: String): CoverFile? suspend fun find(id: String): CoverFile?
suspend fun write(id: String, data: ByteArray): CoverFile? suspend fun write(id: String, data: ByteArray): CoverFile?
companion object { companion object {
fun at(context: Context, path: String, format: CoverFormat): CoverFiles = suspend fun at(dir: File, format: CoverFormat): CoverFiles {
CoverFilesImpl(File(context.filesDir, path).also { it.mkdirs() }, format) withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) }
return CoverFilesImpl(dir, format)
}
} }
} }
internal interface CoverFile { interface CoverFile {
suspend fun open(): InputStream? suspend fun open(): InputStream?
} }

View file

@ -18,14 +18,12 @@
package org.oxycblt.musikr.cover package org.oxycblt.musikr.cover
import android.content.Context
interface StoredCovers { interface StoredCovers {
suspend fun obtain(id: String): Cover? suspend fun obtain(id: String): Cover?
companion object { companion object {
fun from(context: Context, path: String, format: CoverFormat): MutableStoredCovers = fun from(files: CoverFiles): MutableStoredCovers =
FileStoredCovers(CoverIdentifier.md5(), CoverFiles.at(context, path, format)) FileStoredCovers(CoverIdentifier.md5(), files)
} }
} }