image: implement compat covers backport

For cover.jpg users
This commit is contained in:
Alexander Capehart 2025-02-26 16:04:40 -07:00
parent 25901a0f76
commit 7906867a96
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
7 changed files with 107 additions and 19 deletions

View file

@ -25,13 +25,19 @@ import android.content.UriMatcher
import android.database.Cursor import android.database.Cursor
import android.net.Uri import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.BuildConfig
import org.oxycblt.auxio.image.covers.SettingCovers
import org.oxycblt.auxio.image.covers.SiloedCoverId import org.oxycblt.auxio.image.covers.SiloedCoverId
import org.oxycblt.auxio.image.covers.SiloedCovers import org.oxycblt.auxio.image.covers.SiloedCovers
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.cover.CoverResult
import javax.inject.Inject
class CoverProvider : ContentProvider() { @AndroidEntryPoint
class CoverProvider @Inject constructor(
private val settingCovers: SettingCovers
) : ContentProvider() {
override fun onCreate(): Boolean = true override fun onCreate(): Boolean = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
@ -39,12 +45,10 @@ class CoverProvider : ContentProvider() {
return null return null
} }
val id = uri.lastPathSegment ?: return null val id = uri.lastPathSegment ?: return null
val coverId = SiloedCoverId.parse(id) ?: return null
return runBlocking { return runBlocking {
val siloedCovers = SiloedCovers.from(requireNotNull(context), coverId.silo) when (val result = settingCovers.obtain(requireNotNull(context), id)) {
when (val res = siloedCovers.obtain(id)) { is CoverResult.Hit -> result.cover.fd()
is CoverResult.Hit -> res.cover.fd() else -> null
is CoverResult.Miss -> null
} }
} }
} }

View file

@ -0,0 +1,72 @@
package org.oxycblt.auxio.image.covers
import android.content.Context
import android.net.Uri
import android.os.Build
import android.os.ParcelFileDescriptor
import android.provider.MediaStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata
open class CompatCovers(private val context: Context, private val inner: Covers<FileCover>) : Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> {
when (val innerResult = inner.obtain(id)) {
is CoverResult.Hit -> return CoverResult.Hit(innerResult.cover)
is CoverResult.Miss -> {
if (!id.startsWith("compat:")) return CoverResult.Miss()
val uri = Uri.parse(id.substringAfter("compat:"))
return CoverResult.Hit(CompatCover(context, uri))
}
}
}
}
class MutableCompatCovers(private val context: Context, private val inner: MutableCovers<FileCover>) : CompatCovers(context, inner), MutableCovers<FileCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> {
when (val innerResult = inner.create(file, metadata)) {
is CoverResult.Hit -> return CoverResult.Hit(innerResult.cover)
is CoverResult.Miss -> {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return CoverResult.Miss()
}
val mediaStoreUri = MediaStore.getMediaUri(context, file.uri) ?: return CoverResult.Miss()
val proj = arrayOf(MediaStore.MediaColumns._ID)
val cursor = context.contentResolver.query(mediaStoreUri, proj, null, null, null)
val uri = cursor.use {
if (it == null || !it.moveToFirst()) {
return CoverResult.Miss()
}
val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.MediaColumns._ID))
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI.buildUpon().run {
appendPath(id.toString())
appendPath("albumart")
build()
}
}
return CoverResult.Hit(CompatCover(context, uri))
}
}
}
override suspend fun cleanup(excluding: Collection<Cover>) {}
}
class CompatCover(private val context: Context, private val uri: Uri) : FileCover {
override val id = "compat:$uri"
override suspend fun fd(): ParcelFileDescriptor? {
return context.contentResolver.openFileDescriptor(uri, "r")
}
override suspend fun open() = withContext(Dispatchers.IO) {
context.contentResolver.openInputStream(uri)
}
}

View file

@ -19,13 +19,14 @@
package org.oxycblt.auxio.image.covers package org.oxycblt.auxio.image.covers
import android.content.Context import android.content.Context
import org.apache.commons.lang3.ObjectUtils.Null
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.metadata.Metadata
class NullCovers(private val context: Context) : MutableCovers { class NullCovers(private val context: Context) : MutableCovers<NullCover> {
override suspend fun obtain(id: String) = CoverResult.Hit(NullCover) override suspend fun obtain(id: String) = CoverResult.Hit(NullCover)
override suspend fun create(file: DeviceFile, metadata: Metadata) = CoverResult.Hit(NullCover) override suspend fun create(file: DeviceFile, metadata: Metadata) = CoverResult.Hit(NullCover)

View file

@ -23,19 +23,23 @@ import java.util.UUID
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.auxio.image.CoverMode import org.oxycblt.auxio.image.CoverMode
import org.oxycblt.auxio.image.ImageSettings import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.CoverParams import org.oxycblt.musikr.cover.CoverParams
import org.oxycblt.musikr.cover.CoverResult
import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.cover.MutableCovers
interface SettingCovers { interface SettingCovers {
suspend fun create(context: Context, revision: UUID): MutableCovers suspend fun obtain(context: Context, id: String): CoverResult<FileCover>
suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover>
} }
class SettingCoversImpl class SettingCoversImpl
@Inject @Inject
constructor(private val imageSettings: ImageSettings, private val identifier: CoverIdentifier) : constructor(private val imageSettings: ImageSettings, private val identifier: CoverIdentifier) :
SettingCovers { SettingCovers {
override suspend fun create(context: Context, revision: UUID): MutableCovers = override suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover> =
when (imageSettings.coverMode) { when (imageSettings.coverMode) {
CoverMode.OFF -> NullCovers(context) CoverMode.OFF -> NullCovers(context)
CoverMode.SAVE_SPACE -> siloedCovers(context, revision, CoverParams.of(500, 70)) CoverMode.SAVE_SPACE -> siloedCovers(context, revision, CoverParams.of(500, 70))
@ -43,6 +47,13 @@ constructor(private val imageSettings: ImageSettings, private val identifier: Co
CoverMode.HIGH_QUALITY -> siloedCovers(context, revision, CoverParams.of(1000, 100)) CoverMode.HIGH_QUALITY -> siloedCovers(context, revision, CoverParams.of(1000, 100))
} }
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) = override suspend fun obtain(context: Context, id: String): CoverResult<FileCover> {
MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier) val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss()
val siloedCovers = SiloedCovers.from(context, coverId.silo)
val covers = CompatCovers(context, siloedCovers)
return covers.obtain(coverId.id)
}
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) =
MutableCompatCovers(context, MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier))
} }

View file

@ -35,8 +35,8 @@ import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.fs.app.AppFiles import org.oxycblt.musikr.fs.app.AppFiles
import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.metadata.Metadata
open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers { open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<SiloedCover> { override suspend fun obtain(id: String): CoverResult<FileCover> {
val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss() val coverId = SiloedCoverId.parse(id) ?: return CoverResult.Miss()
if (coverId.silo != silo) return CoverResult.Miss() if (coverId.silo != silo) return CoverResult.Miss()
return when (val result = fileCovers.obtain(coverId.id)) { return when (val result = fileCovers.obtain(coverId.id)) {
@ -58,8 +58,8 @@ private constructor(
private val rootDir: File, private val rootDir: File,
private val silo: CoverSilo, private val silo: CoverSilo,
private val fileCovers: MutableFileCovers private val fileCovers: MutableFileCovers
) : SiloedCovers(silo, fileCovers), MutableCovers { ) : SiloedCovers(silo, fileCovers), MutableCovers<FileCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<out Cover> = override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> =
when (val result = fileCovers.create(file, metadata)) { when (val result = fileCovers.create(file, metadata)) {
is CoverResult.Hit -> CoverResult.Hit(SiloedCover(silo, result.cover)) is CoverResult.Hit -> CoverResult.Hit(SiloedCover(silo, result.cover))
is CoverResult.Miss -> CoverResult.Miss() is CoverResult.Miss -> CoverResult.Miss()

View file

@ -389,7 +389,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 = settingCovers.create(context, newRevision) val covers = settingCovers.mutate(context, newRevision)
val storage = Storage(cache, covers, storedPlaylists) val storage = Storage(cache, covers, storedPlaylists)
val interpretation = Interpretation(nameFactory, separators, ignoreHidden) val interpretation = Interpretation(nameFactory, separators, ignoreHidden)

View file

@ -22,12 +22,12 @@ import java.io.InputStream
import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.fs.DeviceFile
import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.metadata.Metadata
interface Covers { interface Covers<T : Cover> {
suspend fun obtain(id: String): CoverResult<out Cover> suspend fun obtain(id: String): CoverResult<T>
} }
interface MutableCovers : Covers { interface MutableCovers<T : Cover> : Covers<T> {
suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<out Cover> suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<T>
suspend fun cleanup(excluding: Collection<Cover>) suspend fun cleanup(excluding: Collection<Cover>)
} }