music: introduce siloed covers
Will allow me to dynamically configure cover quality by user settings.
This commit is contained in:
parent
8b58f357cb
commit
32156f23b2
3 changed files with 51 additions and 28 deletions
|
@ -28,6 +28,8 @@ import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.oxycblt.auxio.music.MusicRepository.IndexingWorker
|
import org.oxycblt.auxio.music.MusicRepository.IndexingWorker
|
||||||
|
import org.oxycblt.auxio.music.covers.CoverSilo
|
||||||
|
import org.oxycblt.auxio.music.covers.SiloedCovers
|
||||||
import org.oxycblt.musikr.IndexingProgress
|
import org.oxycblt.musikr.IndexingProgress
|
||||||
import org.oxycblt.musikr.Interpretation
|
import org.oxycblt.musikr.Interpretation
|
||||||
import org.oxycblt.musikr.Library
|
import org.oxycblt.musikr.Library
|
||||||
|
@ -38,6 +40,7 @@ import org.oxycblt.musikr.Playlist
|
||||||
import org.oxycblt.musikr.Song
|
import org.oxycblt.musikr.Song
|
||||||
import org.oxycblt.musikr.Storage
|
import org.oxycblt.musikr.Storage
|
||||||
import org.oxycblt.musikr.cache.StoredCache
|
import org.oxycblt.musikr.cache.StoredCache
|
||||||
|
import org.oxycblt.musikr.cover.CoverParams
|
||||||
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
import org.oxycblt.musikr.playlist.db.StoredPlaylists
|
||||||
import org.oxycblt.musikr.tag.interpret.Naming
|
import org.oxycblt.musikr.tag.interpret.Naming
|
||||||
import org.oxycblt.musikr.tag.interpret.Separators
|
import org.oxycblt.musikr.tag.interpret.Separators
|
||||||
|
@ -385,7 +388,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 = RevisionedCovers.at(context, newRevision)
|
val covers = SiloedCovers.at(context, CoverSilo(newRevision, CoverParams.of(750, 80)))
|
||||||
val storage = Storage(cache, covers, storedPlaylists)
|
val storage = Storage(cache, covers, storedPlaylists)
|
||||||
val interpretation = Interpretation(nameFactory, separators)
|
val interpretation = Interpretation(nameFactory, separators)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2024 Auxio Project
|
* Copyright (c) 2024 Auxio Project
|
||||||
* RevisionedCovers.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
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.oxycblt.auxio.music
|
package org.oxycblt.auxio.music.covers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -31,19 +31,19 @@ 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(
|
class SiloedCovers(
|
||||||
private val rootDir: File,
|
private val rootDir: File,
|
||||||
private val revision: UUID,
|
private val silo: CoverSilo,
|
||||||
private val inner: MutableStoredCovers
|
private val inner: MutableStoredCovers
|
||||||
) : MutableStoredCovers {
|
) : MutableStoredCovers {
|
||||||
override suspend fun obtain(id: String): RevisionedCover? {
|
override suspend fun obtain(id: String): SiloedCover? {
|
||||||
val (coverId, coverRevision) = parse(id) ?: return null
|
val coverId = SiloedCoverId.parse(id) ?: return null
|
||||||
if (coverRevision != revision) return null
|
if (coverId.silo != silo) return null
|
||||||
return inner.obtain(coverId)?.let { RevisionedCover(revision, it) }
|
return inner.obtain(coverId.id)?.let { SiloedCover(coverId.silo, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun write(data: ByteArray): RevisionedCover? {
|
override suspend fun write(data: ByteArray): SiloedCover? {
|
||||||
return inner.write(data)?.let { RevisionedCover(revision, it) }
|
return inner.write(data)?.let { SiloedCover(silo, it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun cleanup(assuming: Library) {
|
override suspend fun cleanup(assuming: Library) {
|
||||||
|
@ -51,37 +51,57 @@ class RevisionedCovers(
|
||||||
|
|
||||||
// Destroy old revisions no longer being used.
|
// Destroy old revisions no longer being used.
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val exclude = revision.toString()
|
val exclude = silo.toString()
|
||||||
rootDir.listFiles { file -> file.name != exclude }?.forEach { it.deleteRecursively() }
|
rootDir.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, silo: CoverSilo): SiloedCovers {
|
||||||
val rootDir: File
|
val rootDir: File
|
||||||
val revisionDir: File
|
val revisionDir: File
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
rootDir = context.filesDir.resolve("covers").apply { mkdirs() }
|
rootDir = context.filesDir.resolve("covers").apply { mkdirs() }
|
||||||
revisionDir = rootDir.resolve(revision.toString()).apply { mkdirs() }
|
revisionDir = rootDir.resolve(silo.toString()).apply { mkdirs() }
|
||||||
}
|
}
|
||||||
val files = CoverFiles.at(revisionDir)
|
val files = CoverFiles.at(revisionDir)
|
||||||
val format = CoverFormat.jpeg(CoverParams.of(750, 80))
|
val format = CoverFormat.jpeg(silo.params)
|
||||||
return RevisionedCovers(rootDir, revision, StoredCovers.from(files, format))
|
return SiloedCovers(rootDir, silo, StoredCovers.from(files, format))
|
||||||
}
|
|
||||||
|
|
||||||
private fun parse(id: String): Pair<String, UUID>? {
|
|
||||||
val split = id.split('@', limit = 2)
|
|
||||||
if (split.size != 2) return null
|
|
||||||
val (coverId, coverRevisionStr) = split
|
|
||||||
val coverRevision = coverRevisionStr.toUuidOrNull() ?: return null
|
|
||||||
return coverId to coverRevision
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RevisionedCover(private val revision: UUID, val inner: Cover) : Cover by inner {
|
data class CoverSilo(val revision: UUID, val params: CoverParams) {
|
||||||
override val id: String
|
override fun toString() = "${revision}.${params.resolution}.${params.quality}"
|
||||||
get() = "${inner.id}@${revision}"
|
|
||||||
|
companion object {
|
||||||
|
fun parse(silo: String): CoverSilo? {
|
||||||
|
val parts = silo.split('.')
|
||||||
|
if (parts.size != 3) return null
|
||||||
|
val revision = parts[0].toUuidOrNull() ?: return null
|
||||||
|
val resolution = parts[1].toIntOrNull() ?: return null
|
||||||
|
val quality = parts[2].toIntOrNull() ?: return null
|
||||||
|
return CoverSilo(revision, CoverParams.of(resolution, quality))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SiloedCover(silo: CoverSilo, private val innerCover: Cover) : Cover by innerCover {
|
||||||
|
private val innerId = SiloedCoverId(silo, innerCover.id)
|
||||||
|
override val id = innerId.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SiloedCoverId(val silo: CoverSilo, val id: String) {
|
||||||
|
override fun toString() = "$id@$silo"
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun parse(id: String): SiloedCoverId? {
|
||||||
|
val parts = id.split('@')
|
||||||
|
if (parts.size != 2) return null
|
||||||
|
val silo = CoverSilo.parse(parts[1]) ?: return null
|
||||||
|
return SiloedCoverId(silo, parts[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.toUuidOrNull(): UUID? =
|
private fun String.toUuidOrNull(): UUID? =
|
|
@ -18,7 +18,7 @@
|
||||||
|
|
||||||
package org.oxycblt.musikr.cover
|
package org.oxycblt.musikr.cover
|
||||||
|
|
||||||
class CoverParams private constructor(internal val resolution: Int, internal val quality: Int) {
|
class CoverParams private constructor(val resolution: Int, val quality: Int) {
|
||||||
companion object {
|
companion object {
|
||||||
fun of(resolution: Int, quality: Int): CoverParams {
|
fun of(resolution: Int, quality: Int): CoverParams {
|
||||||
check(resolution > 0) { "Resolution must be positive" }
|
check(resolution > 0) { "Resolution must be positive" }
|
||||||
|
|
Loading…
Reference in a new issue