image: implement compat covers backport
For cover.jpg users
This commit is contained in:
parent
25901a0f76
commit
7906867a96
7 changed files with 107 additions and 19 deletions
|
@ -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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun obtain(context: Context, id: String): CoverResult<FileCover> {
|
||||||
|
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) =
|
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams) =
|
||||||
MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier)
|
MutableCompatCovers(context, MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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>)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue