musikr: separate cover files/format

This commit is contained in:
Alexander Capehart 2024-12-26 19:29:57 -05:00
parent b8178056f5
commit 80c97cbea1
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 32 additions and 27 deletions

View file

@ -47,7 +47,7 @@ class RevisionedCovers(private val revision: UUID, private val inner: MutableSto
context.filesDir.resolve("covers/${revision}").apply { mkdirs() } context.filesDir.resolve("covers/${revision}").apply { mkdirs() }
} }
return RevisionedCovers( return RevisionedCovers(
revision, StoredCovers.from(CoverFiles.at(dir, CoverFormat.jpeg()))) revision, StoredCovers.from(CoverFiles.at(dir), CoverFormat.jpeg()))
} }
suspend fun cleanup(context: Context, exclude: UUID) = suspend fun cleanup(context: Context, exclude: UUID) =

View file

@ -21,20 +21,21 @@ package org.oxycblt.musikr.cover
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
interface CoverFiles { interface CoverFiles {
suspend fun find(id: String): CoverFile? suspend fun find(name: String): CoverFile?
suspend fun write(id: String, data: ByteArray): CoverFile? suspend fun write(name: String, block: suspend (OutputStream) -> Unit): CoverFile?
companion object { companion object {
suspend fun at(dir: File, format: CoverFormat): CoverFiles { suspend fun at(dir: File): CoverFiles {
withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) } withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) }
return CoverFilesImpl(dir, format) return CoverFilesImpl(dir)
} }
} }
} }
@ -43,8 +44,7 @@ interface CoverFile {
suspend fun open(): InputStream? suspend fun open(): InputStream?
} }
private class CoverFilesImpl(private val dir: File, private val coverFormat: CoverFormat) : private class CoverFilesImpl(private val dir: File) : CoverFiles {
CoverFiles {
private val fileMutexes = mutableMapOf<String, Mutex>() private val fileMutexes = mutableMapOf<String, Mutex>()
private val mapMutex = Mutex() private val mapMutex = Mutex()
@ -52,25 +52,25 @@ private class CoverFilesImpl(private val dir: File, private val coverFormat: Cov
return mapMutex.withLock { fileMutexes.getOrPut(file) { Mutex() } } return mapMutex.withLock { fileMutexes.getOrPut(file) { Mutex() } }
} }
override suspend fun find(id: String): CoverFile? = override suspend fun find(name: String): CoverFile? =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
try { try {
File(dir, getTargetFilePath(id)).takeIf { it.exists() }?.let { CoverFileImpl(it) } File(dir, name).takeIf { it.exists() }?.let { CoverFileImpl(it) }
} catch (e: IOException) { } catch (e: IOException) {
null null
} }
} }
override suspend fun write(id: String, data: ByteArray): CoverFile? { override suspend fun write(name: String, block: suspend (OutputStream) -> Unit): CoverFile? {
val fileMutex = getMutexForFile(id) val fileMutex = getMutexForFile(name)
return fileMutex.withLock { return fileMutex.withLock {
val targetFile = File(dir, getTargetFilePath(id)) val targetFile = File(dir, name)
if (!targetFile.exists()) { if (!targetFile.exists()) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val tempFile = File(dir, getTempFilePath(id)) val tempFile = File(dir, "$name.tmp")
try { try {
tempFile.outputStream().use { coverFormat.transcodeInto(data, it) } block(tempFile.outputStream())
tempFile.renameTo(targetFile) tempFile.renameTo(targetFile)
CoverFileImpl(targetFile) CoverFileImpl(targetFile)
} catch (e: IOException) { } catch (e: IOException) {
@ -83,10 +83,6 @@ private class CoverFilesImpl(private val dir: File, private val coverFormat: Cov
} }
} }
} }
private fun getTargetFilePath(name: String) = "cover_${name}.${coverFormat.extension}"
private fun getTempFilePath(name: String) = "${getTargetFilePath(name)}.tmp"
} }
private class CoverFileImpl(private val file: File) : CoverFile { private class CoverFileImpl(private val file: File) : CoverFile {

View file

@ -20,7 +20,7 @@ package org.oxycblt.musikr.cover
import java.security.MessageDigest import java.security.MessageDigest
internal interface CoverIdentifier { interface CoverIdentifier {
suspend fun identify(data: ByteArray): String suspend fun identify(data: ByteArray): String
companion object { companion object {

View file

@ -22,8 +22,11 @@ interface StoredCovers {
suspend fun obtain(id: String): Cover? suspend fun obtain(id: String): Cover?
companion object { companion object {
fun from(files: CoverFiles): MutableStoredCovers = fun from(
FileStoredCovers(CoverIdentifier.md5(), files) coverFiles: CoverFiles,
coverFormat: CoverFormat,
identifier: CoverIdentifier = CoverIdentifier.md5()
): MutableStoredCovers = FileStoredCovers(coverFiles, coverFormat, identifier)
} }
} }
@ -32,15 +35,21 @@ interface MutableStoredCovers : StoredCovers {
} }
private class FileStoredCovers( private class FileStoredCovers(
private val coverFiles: CoverFiles,
private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier, private val coverIdentifier: CoverIdentifier,
private val coverFiles: CoverFiles
) : StoredCovers, MutableStoredCovers { ) : StoredCovers, MutableStoredCovers {
override suspend fun obtain(id: String) = coverFiles.find(id)?.let { FileCover(id, it) } override suspend fun obtain(id: String) =
coverFiles.find(getFileName(id))?.let { FileCover(id, it) }
override suspend fun write(data: ByteArray) = override suspend fun write(data: ByteArray): Cover? {
coverIdentifier.identify(data).let { id -> val id = coverIdentifier.identify(data)
coverFiles.write(id, data)?.let { FileCover(id, it) } return coverFiles
} .write(getFileName(id)) { coverFormat.transcodeInto(data, it) }
?.let { FileCover(id, it) }
}
private fun getFileName(id: String) = "$id.${coverFormat.extension}"
} }
private class FileCover(override val id: String, private val coverFile: CoverFile) : Cover { private class FileCover(override val id: String, private val coverFile: CoverFile) : Cover {