musikr: separate immutable/mutable subclasses

This makes it easier for me to centralize certain DI.
This commit is contained in:
Alexander Capehart 2025-01-01 14:37:01 -07:00
parent 194e6b1574
commit a2e6bcbb7f
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 53 additions and 34 deletions

View file

@ -25,14 +25,10 @@ import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.oxycblt.auxio.image.covers.MutableSiloedCovers
import org.oxycblt.auxio.image.covers.SiloedCoverId import org.oxycblt.auxio.image.covers.SiloedCoverId
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.auxio.image.covers.SiloedCovers
import org.oxycblt.musikr.cover.ObtainResult import org.oxycblt.musikr.cover.ObtainResult
// AndroidManifest.xml addition
// ImageProvider.java
class CoverProvider : ContentProvider() { class CoverProvider : ContentProvider() {
override fun onCreate(): Boolean = true override fun onCreate(): Boolean = true
@ -42,8 +38,7 @@ class CoverProvider : ContentProvider() {
val id = requireNotNull(uri.lastPathSegment) { "No ID in URI: $uri" } val id = requireNotNull(uri.lastPathSegment) { "No ID in URI: $uri" }
val coverId = requireNotNull(SiloedCoverId.parse(id)) { "Invalid ID: $id" } val coverId = requireNotNull(SiloedCoverId.parse(id)) { "Invalid ID: $id" }
return runBlocking { return runBlocking {
val siloedCovers = val siloedCovers = SiloedCovers.from(requireContext(), coverId.silo)
MutableSiloedCovers.from(requireContext(), coverId.silo, CoverIdentifier.md5())
when (val res = siloedCovers.obtain(coverId.id)) { when (val res = siloedCovers.obtain(coverId.id)) {
is ObtainResult.Hit -> res.cover.fd() is ObtainResult.Hit -> res.cover.fd()
is ObtainResult.Miss -> null is ObtainResult.Miss -> null

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2024 Auxio Project * Copyright (c) 2024 Auxio Project
* MutableSiloedCovers.kt is part of Auxio. * SiloedCovers.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
@ -25,31 +25,42 @@ import kotlinx.coroutines.withContext
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverFormat import org.oxycblt.musikr.cover.CoverFormat
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.FileCover import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.FileCovers import org.oxycblt.musikr.cover.FileCovers
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.cover.MutableFileCovers
import org.oxycblt.musikr.cover.ObtainResult import org.oxycblt.musikr.cover.ObtainResult
import org.oxycblt.musikr.fs.app.AppFiles import org.oxycblt.musikr.fs.app.AppFiles
class MutableSiloedCovers open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers {
private constructor(
private val rootDir: File,
private val silo: CoverSilo,
private val inner: FileCovers
) : MutableCovers {
override suspend fun obtain(id: String): ObtainResult<SiloedCover> { override suspend fun obtain(id: String): ObtainResult<SiloedCover> {
val coverId = SiloedCoverId.parse(id) ?: return ObtainResult.Miss() val coverId = SiloedCoverId.parse(id) ?: return ObtainResult.Miss()
if (coverId.silo != silo) return ObtainResult.Miss() if (coverId.silo != silo) return ObtainResult.Miss()
return when (val result = inner.obtain(coverId.id)) { return when (val result = fileCovers.obtain(coverId.id)) {
is ObtainResult.Hit -> ObtainResult.Hit(SiloedCover(silo, result.cover)) is ObtainResult.Hit -> ObtainResult.Hit(SiloedCover(silo, result.cover))
is ObtainResult.Miss -> ObtainResult.Miss() is ObtainResult.Miss -> ObtainResult.Miss()
} }
} }
override suspend fun write(data: ByteArray) = SiloedCover(silo, inner.write(data)) companion object {
suspend fun from(context: Context, silo: CoverSilo): SiloedCovers {
val core = SiloCore.from(context, silo)
return SiloedCovers(silo, FileCovers(core.files, core.format))
}
}
}
class MutableSiloedCovers
private constructor(
private val rootDir: File,
private val silo: CoverSilo,
private val fileCovers: MutableFileCovers
) : SiloedCovers(silo, fileCovers), MutableCovers {
override suspend fun write(data: ByteArray) = SiloedCover(silo, fileCovers.write(data))
override suspend fun cleanup(excluding: Collection<Cover>) { override suspend fun cleanup(excluding: Collection<Cover>) {
inner.cleanup(excluding.filterIsInstance<SiloedCover>().map { it.innerCover }) fileCovers.cleanup(excluding.filterIsInstance<SiloedCover>().map { it.innerCover })
// Destroy old revisions no longer being used. // Destroy old revisions no longer being used.
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@ -62,17 +73,11 @@ private constructor(
suspend fun from( suspend fun from(
context: Context, context: Context,
silo: CoverSilo, silo: CoverSilo,
identifier: CoverIdentifier coverIdentifier: CoverIdentifier
): MutableSiloedCovers { ): MutableSiloedCovers {
val rootDir: File val core = SiloCore.from(context, silo)
val revisionDir: File return MutableSiloedCovers(
withContext(Dispatchers.IO) { core.rootDir, silo, MutableFileCovers(core.files, core.format, coverIdentifier))
rootDir = context.coversDir()
revisionDir = rootDir.resolve(silo.toString()).apply { mkdirs() }
}
val files = AppFiles.at(revisionDir)
val format = CoverFormat.jpeg(silo.params)
return MutableSiloedCovers(rootDir, silo, FileCovers(files, format, identifier))
} }
} }
} }
@ -94,3 +99,19 @@ data class SiloedCoverId(val silo: CoverSilo, val id: String) {
} }
} }
} }
private data class SiloCore(val rootDir: File, val files: AppFiles, val format: CoverFormat) {
companion object {
suspend fun from(context: Context, silo: CoverSilo): SiloCore {
val rootDir: File
val revisionDir: File
withContext(Dispatchers.IO) {
rootDir = context.coversDir()
revisionDir = rootDir.resolve(silo.toString()).apply { mkdirs() }
}
val files = AppFiles.at(revisionDir)
val format = CoverFormat.jpeg(silo.params)
return SiloCore(rootDir, files, format)
}
}
}

View file

@ -22,11 +22,8 @@ import android.os.ParcelFileDescriptor
import org.oxycblt.musikr.fs.app.AppFile import org.oxycblt.musikr.fs.app.AppFile
import org.oxycblt.musikr.fs.app.AppFiles import org.oxycblt.musikr.fs.app.AppFiles
class FileCovers( open class FileCovers(private val appFiles: AppFiles, private val coverFormat: CoverFormat) :
private val appFiles: AppFiles, Covers {
private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier,
) : Covers, MutableCovers {
override suspend fun obtain(id: String): ObtainResult<FileCover> { override suspend fun obtain(id: String): ObtainResult<FileCover> {
val file = appFiles.find(getFileName(id)) val file = appFiles.find(getFileName(id))
return if (file != null) { return if (file != null) {
@ -36,6 +33,14 @@ class FileCovers(
} }
} }
protected fun getFileName(id: String) = "$id.${coverFormat.extension}"
}
class MutableFileCovers(
private val appFiles: AppFiles,
private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier
) : FileCovers(appFiles, coverFormat), MutableCovers {
override suspend fun write(data: ByteArray): FileCover { override suspend fun write(data: ByteArray): FileCover {
val id = coverIdentifier.identify(data) val id = coverIdentifier.identify(data)
val file = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) } val file = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) }
@ -46,8 +51,6 @@ class FileCovers(
val used = excluding.mapTo(mutableSetOf()) { getFileName(it.id) } val used = excluding.mapTo(mutableSetOf()) { getFileName(it.id) }
appFiles.deleteWhere { it !in used } appFiles.deleteWhere { it !in used }
} }
private fun getFileName(id: String) = "$id.${coverFormat.extension}"
} }
interface FileCover : Cover { interface FileCover : Cover {