musikr: cleanup

This commit is contained in:
Alexander Capehart 2025-03-03 20:13:38 -07:00
parent 6feee93438
commit 22249cc95b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
10 changed files with 57 additions and 49 deletions

View file

@ -24,13 +24,13 @@ 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.covers.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.covers.fs.FSCovers
import org.oxycblt.musikr.covers.fs.MutableFSCovers
import org.oxycblt.musikr.covers.internal.CoverIdentifier import org.oxycblt.musikr.covers.internal.CoverIdentifier
import org.oxycblt.musikr.covers.internal.CoverParams import org.oxycblt.musikr.covers.internal.CoverParams
import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.covers.internal.FileCover import org.oxycblt.musikr.covers.internal.FileCover
import org.oxycblt.musikr.covers.fs.FSCovers
import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.covers.fs.MutableFSCovers
interface SettingCovers { interface SettingCovers {
suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover> suspend fun mutate(context: Context, revision: UUID): MutableCovers<out Cover>
@ -57,6 +57,5 @@ constructor(private val imageSettings: ImageSettings, private val identifier: Co
private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams?) = private suspend fun siloedCovers(context: Context, revision: UUID, with: CoverParams?) =
MutableCovers.chain( MutableCovers.chain(
MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier), MutableSiloedCovers.from(context, CoverSilo(revision, with), identifier),
MutableFSCovers(context) MutableFSCovers(context))
)
} }

View file

@ -23,13 +23,13 @@ import java.io.File
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.musikr.covers.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.covers.internal.CoverFormat
import org.oxycblt.musikr.covers.internal.CoverIdentifier
import org.oxycblt.musikr.covers.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.covers.Covers import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.covers.internal.CoverFormat
import org.oxycblt.musikr.covers.internal.CoverIdentifier
import org.oxycblt.musikr.covers.internal.FileCover import org.oxycblt.musikr.covers.internal.FileCover
import org.oxycblt.musikr.covers.internal.InternalCovers import org.oxycblt.musikr.covers.internal.InternalCovers
import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.covers.internal.MutableInternalCovers import org.oxycblt.musikr.covers.internal.MutableInternalCovers
import org.oxycblt.musikr.fs.app.AppFS import org.oxycblt.musikr.fs.app.AppFS
import org.oxycblt.musikr.fs.device.DeviceFile import org.oxycblt.musikr.fs.device.DeviceFile
@ -96,8 +96,7 @@ private constructor(
): MutableSiloedCovers { ): MutableSiloedCovers {
val core = SiloCore.from(context, silo) val core = SiloCore.from(context, silo)
return MutableSiloedCovers( return MutableSiloedCovers(
core.rootDir, silo, MutableInternalCovers(core.files, core.format, coverIdentifier) core.rootDir, silo, MutableInternalCovers(core.files, core.format, coverIdentifier))
)
} }
} }
} }

View file

@ -28,8 +28,8 @@ import org.oxycblt.musikr.tag.interpret.Separators
/** Side-effect laden [Storage] for use during music loading and [MutableLibrary] operation. */ /** Side-effect laden [Storage] for use during music loading and [MutableLibrary] operation. */
data class Storage( data class Storage(
/** /**
* A factory producing a repository of cached metadata to read and write from over the course of * A repository of cached metadata to read and write from over the course of music loading. This
* music loading. This will only be used during music loading. * will only be used during music loading.
*/ */
val cache: MutableCache, val cache: MutableCache,
@ -57,5 +57,5 @@ data class Interpretation(
val separators: Separators, val separators: Separators,
/** Whether to ignore hidden files and directories (those starting with a dot). */ /** Whether to ignore hidden files and directories (those starting with a dot). */
val ignoreHidden: Boolean = true val ignoreHidden: Boolean
) )

View file

@ -18,6 +18,7 @@
package org.oxycblt.musikr.covers package org.oxycblt.musikr.covers
import android.os.ParcelFileDescriptor
import java.io.InputStream import java.io.InputStream
import org.oxycblt.musikr.fs.device.DeviceFile import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.metadata.Metadata
@ -94,6 +95,10 @@ interface Cover {
override fun hashCode(): Int override fun hashCode(): Int
} }
interface FDCover : Cover {
suspend fun fd(): ParcelFileDescriptor?
}
class CoverCollection private constructor(val covers: List<Cover>) { class CoverCollection private constructor(val covers: List<Cover>) {
override fun hashCode() = covers.hashCode() override fun hashCode() = covers.hashCode()

View file

@ -29,20 +29,19 @@ import kotlinx.coroutines.withContext
import org.oxycblt.musikr.covers.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.covers.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.covers.Covers import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.covers.FDCover
import org.oxycblt.musikr.covers.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.covers.internal.FileCover
import org.oxycblt.musikr.fs.device.DeviceDirectory import org.oxycblt.musikr.fs.device.DeviceDirectory
import org.oxycblt.musikr.fs.device.DeviceFile import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.metadata.Metadata
open class FSCovers(private val context: Context) : Covers<FolderCover> { open class FSCovers(private val context: Context) : Covers<FDCover> {
override suspend fun obtain(id: String): CoverResult<FolderCover> { override suspend fun obtain(id: String): CoverResult<FDCover> {
// Parse the ID to get the directory URI // Parse the ID to get the directory URI
if (!id.startsWith("folder:")) { if (!id.startsWith("folder:")) {
return CoverResult.Miss() return CoverResult.Miss()
} }
// TODO: Check if the dir actually exists still to avoid stale uris
val directoryUri = id.substring("folder:".length) val directoryUri = id.substring("folder:".length)
val uri = Uri.parse(directoryUri) val uri = Uri.parse(directoryUri)
@ -65,9 +64,8 @@ open class FSCovers(private val context: Context) : Covers<FolderCover> {
} }
} }
class MutableFSCovers(private val context: Context) : class MutableFSCovers(private val context: Context) : FSCovers(context), MutableCovers<FDCover> {
FSCovers(context), MutableCovers<FolderCover> { override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FDCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FolderCover> {
val parent = file.parent val parent = file.parent
val coverFile = findCoverInDirectory(parent) ?: return CoverResult.Miss() val coverFile = findCoverInDirectory(parent) ?: return CoverResult.Miss()
return CoverResult.Hit(FolderCoverImpl(context, coverFile.uri)) return CoverResult.Hit(FolderCoverImpl(context, coverFile.uri))
@ -88,12 +86,10 @@ class MutableFSCovers(private val context: Context) :
val filename = requireNotNull(file.path.name).lowercase() val filename = requireNotNull(file.path.name).lowercase()
val mimeType = file.mimeType.lowercase() val mimeType = file.mimeType.lowercase()
// Check if the file is an image
if (!mimeType.startsWith("image/")) { if (!mimeType.startsWith("image/")) {
return false return false
} }
// Common cover art filenames
val coverNames = val coverNames =
listOf( listOf(
"cover", "cover",
@ -103,11 +99,8 @@ class MutableFSCovers(private val context: Context) :
"front", "front",
"artwork", "artwork",
"art", "art",
"folder", "folder")
"cover")
// Check if the filename matches any common cover art names
// Also check for case variations (e.g., Cover.jpg, COVER.JPG)
val filenameWithoutExt = filename.substringBeforeLast(".") val filenameWithoutExt = filename.substringBeforeLast(".")
val extension = filename.substringAfterLast(".", "") val extension = filename.substringAfterLast(".", "")
@ -120,12 +113,10 @@ class MutableFSCovers(private val context: Context) :
} }
} }
interface FolderCover : FileCover
private data class FolderCoverImpl( private data class FolderCoverImpl(
private val context: Context, private val context: Context,
private val uri: Uri, private val uri: Uri,
) : FolderCover { ) : FDCover {
override val id = "folder:$uri" override val id = "folder:$uri"
override suspend fun fd(): ParcelFileDescriptor? = override suspend fun fd(): ParcelFileDescriptor? =

View file

@ -1,3 +1,21 @@
/*
* Copyright (c) 2025 Auxio Project
* CoverFormat.kt is part of Auxio.
*
* 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
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.covers.internal package org.oxycblt.musikr.covers.internal
import android.graphics.Bitmap import android.graphics.Bitmap

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 Auxio Project * Copyright (c) 2025 Auxio Project
* FileCovers.kt is part of Auxio. * InternalCovers.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
@ -18,22 +18,22 @@
package org.oxycblt.musikr.covers.internal package org.oxycblt.musikr.covers.internal
import android.os.ParcelFileDescriptor
import org.oxycblt.musikr.covers.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.covers.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.covers.Covers import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.covers.FDCover
import org.oxycblt.musikr.covers.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.fs.app.AppFile
import org.oxycblt.musikr.fs.app.AppFS import org.oxycblt.musikr.fs.app.AppFS
import org.oxycblt.musikr.fs.app.AppFile
import org.oxycblt.musikr.fs.device.DeviceFile import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Metadata import org.oxycblt.musikr.metadata.Metadata
open class InternalCovers(private val appFS: AppFS, private val coverFormat: CoverFormat) : open class InternalCovers(private val appFS: AppFS, private val coverFormat: CoverFormat) :
Covers<FileCover> { Covers<FDCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> { override suspend fun obtain(id: String): CoverResult<FDCover> {
val file = appFS.find(getFileName(id)) val file = appFS.find(getFileName(id))
return if (file != null) { return if (file != null) {
CoverResult.Hit(FileCoverImpl(id, file)) CoverResult.Hit(InternalCoverImpl(id, file))
} else { } else {
CoverResult.Miss() CoverResult.Miss()
} }
@ -46,12 +46,12 @@ class MutableInternalCovers(
private val appFS: AppFS, private val appFS: AppFS,
private val coverFormat: CoverFormat, private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier private val coverIdentifier: CoverIdentifier
) : InternalCovers(appFS, coverFormat), MutableCovers<FileCover> { ) : InternalCovers(appFS, coverFormat), MutableCovers<FDCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> { override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FDCover> {
val data = metadata.cover ?: return CoverResult.Miss() val data = metadata.cover ?: return CoverResult.Miss()
val id = coverIdentifier.identify(data) val id = coverIdentifier.identify(data)
val coverFile = appFS.write(getFileName(id)) { coverFormat.transcodeInto(data, it) } val coverFile = appFS.write(getFileName(id)) { coverFormat.transcodeInto(data, it) }
return CoverResult.Hit(FileCoverImpl(id, coverFile)) return CoverResult.Hit(InternalCoverImpl(id, coverFile))
} }
override suspend fun cleanup(excluding: Collection<Cover>) { override suspend fun cleanup(excluding: Collection<Cover>) {
@ -60,12 +60,8 @@ class MutableInternalCovers(
} }
} }
interface FileCover : Cover { private data class InternalCoverImpl(override val id: String, private val appFile: AppFile) :
suspend fun fd(): ParcelFileDescriptor? FDCover {
}
private data class FileCoverImpl(override val id: String, private val appFile: AppFile) :
FileCover {
override suspend fun fd() = appFile.fd() override suspend fun fd() = appFile.fd()
override suspend fun open() = appFile.open() override suspend fun open() = appFile.open()

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2024 Auxio Project * Copyright (c) 2024 Auxio Project
* AppFiles.kt is part of Auxio. * AppFS.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

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2024 Auxio Project * Copyright (c) 2024 Auxio Project
* DeviceFiles.kt is part of Auxio. * DeviceFS.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
@ -31,7 +31,7 @@ import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
internal interface DeviceFS { internal interface DeviceFS {
fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceNode> fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceNode>
companion object { companion object {
fun from(context: Context): DeviceFS = DeviceFSImpl(context.contentResolverSafe) fun from(context: Context): DeviceFS = DeviceFSImpl(context.contentResolverSafe)

View file

@ -38,8 +38,8 @@ import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.covers.Covers import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.fs.MusicLocation import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.device.DeviceDirectory import org.oxycblt.musikr.fs.device.DeviceDirectory
import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.fs.device.DeviceFS import org.oxycblt.musikr.fs.device.DeviceFS
import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.fs.device.DeviceNode import org.oxycblt.musikr.fs.device.DeviceNode
import org.oxycblt.musikr.playlist.db.StoredPlaylists import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.playlist.m3u.M3U import org.oxycblt.musikr.playlist.m3u.M3U