musikr: separate cover files/format
This commit is contained in:
parent
b8178056f5
commit
80c97cbea1
4 changed files with 32 additions and 27 deletions
|
@ -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) =
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue