musikr: streamline package structure

This commit is contained in:
Alexander Capehart 2025-03-03 19:59:11 -07:00
parent 0d0a20d760
commit 6feee93438
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
29 changed files with 118 additions and 148 deletions

View file

@ -28,7 +28,7 @@ import android.os.ParcelFileDescriptor
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.SettingCovers
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.covers.CoverResult
class CoverProvider() : ContentProvider() { class CoverProvider() : ContentProvider() {
override fun onCreate(): Boolean = true override fun onCreate(): Boolean = true

View file

@ -64,7 +64,7 @@ import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
/** /**
* Auxio's extension of [ImageView] that enables cover art loading and playing indicator and * Auxio's extension of [ImageView] that enables cover art loading and playing indicator and

View file

@ -46,7 +46,7 @@ import kotlinx.coroutines.withContext
import okio.FileSystem import okio.FileSystem
import okio.buffer import okio.buffer
import okio.source import okio.source
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
class CoverCollectionFetcher class CoverCollectionFetcher
private constructor( private constructor(

View file

@ -40,7 +40,7 @@ import javax.inject.Inject
import okio.FileSystem import okio.FileSystem
import okio.buffer import okio.buffer
import okio.source import okio.source
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
class CoverFetcher private constructor(private val context: Context, private val cover: Cover) : class CoverFetcher private constructor(private val context: Context, private val cover: Cover) :
Fetcher { Fetcher {

View file

@ -21,8 +21,8 @@ package org.oxycblt.auxio.image.coil
import coil3.key.Keyer import coil3.key.Keyer
import coil3.request.Options import coil3.request.Options
import javax.inject.Inject import javax.inject.Inject
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
class CoverKeyer @Inject constructor() : Keyer<Cover> { class CoverKeyer @Inject constructor() : Keyer<Cover> {
override fun key(data: Cover, options: Options) = "${data.id}&${options.size}" override fun key(data: Cover, options: Options) = "${data.id}&${options.size}"

View file

@ -23,7 +23,7 @@ import dagger.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.musikr.covers.internal.CoverIdentifier
@Module @Module
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)

View file

@ -19,7 +19,7 @@
package org.oxycblt.auxio.image.covers package org.oxycblt.auxio.image.covers
import java.util.UUID import java.util.UUID
import org.oxycblt.musikr.cover.CoverParams import org.oxycblt.musikr.covers.internal.CoverParams
data class CoverSilo(val revision: UUID, val params: CoverParams?) { data class CoverSilo(val revision: UUID, val params: CoverParams?) {
override fun toString() = override fun toString() =

View file

@ -19,9 +19,9 @@
package org.oxycblt.auxio.image.covers package org.oxycblt.auxio.image.covers
import android.content.Context import android.content.Context
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
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

View file

@ -23,21 +23,21 @@ 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.covers.Cover
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.musikr.covers.internal.CoverIdentifier
import org.oxycblt.musikr.cover.CoverParams import org.oxycblt.musikr.covers.internal.CoverParams
import org.oxycblt.musikr.cover.Covers import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.cover.FileCover import org.oxycblt.musikr.covers.internal.FileCover
import org.oxycblt.musikr.cover.FolderCovers import org.oxycblt.musikr.covers.fs.FSCovers
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.cover.MutableFolderCovers 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>
companion object { companion object {
fun immutable(context: Context): Covers<FileCover> = fun immutable(context: Context): Covers<FileCover> =
Covers.chain(BaseSiloedCovers(context), FolderCovers(context)) Covers.chain(BaseSiloedCovers(context), FSCovers(context))
} }
} }
@ -57,5 +57,6 @@ 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),
MutableFolderCovers(context)) MutableFSCovers(context)
)
} }

View file

@ -22,16 +22,16 @@ import android.content.Context
import java.io.File import java.io.File
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.CoverFormat import org.oxycblt.musikr.covers.internal.CoverFormat
import org.oxycblt.musikr.cover.CoverIdentifier import org.oxycblt.musikr.covers.internal.CoverIdentifier
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.cover.Covers import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.cover.FileCover import org.oxycblt.musikr.covers.internal.FileCover
import org.oxycblt.musikr.cover.FileCovers import org.oxycblt.musikr.covers.internal.InternalCovers
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.cover.MutableFileCovers import org.oxycblt.musikr.covers.internal.MutableInternalCovers
import org.oxycblt.musikr.fs.app.AppFiles import org.oxycblt.musikr.fs.app.AppFS
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
@ -39,20 +39,20 @@ class BaseSiloedCovers(private val context: Context) : Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> { override suspend fun obtain(id: String): CoverResult<FileCover> {
val siloedId = SiloedCoverId.parse(id) ?: return CoverResult.Miss() val siloedId = SiloedCoverId.parse(id) ?: return CoverResult.Miss()
val core = SiloCore.from(context, siloedId.silo) val core = SiloCore.from(context, siloedId.silo)
val fileCovers = FileCovers(core.files, core.format) val internalCovers = InternalCovers(core.files, core.format)
return when (val result = fileCovers.obtain(siloedId.id)) { return when (val result = internalCovers.obtain(siloedId.id)) {
is CoverResult.Hit -> CoverResult.Hit(SiloedCover(siloedId.silo, result.cover)) is CoverResult.Hit -> CoverResult.Hit(SiloedCover(siloedId.silo, result.cover))
is CoverResult.Miss -> CoverResult.Miss() is CoverResult.Miss -> CoverResult.Miss()
} }
} }
} }
open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : open class SiloedCovers(private val silo: CoverSilo, private val internalCovers: InternalCovers) :
Covers<FileCover> { Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> { 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 (silo != coverId.silo) return CoverResult.Miss() if (silo != coverId.silo) return CoverResult.Miss()
return when (val result = fileCovers.obtain(coverId.id)) { return when (val result = internalCovers.obtain(coverId.id)) {
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()
} }
@ -61,7 +61,7 @@ open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: Fil
companion object { companion object {
suspend fun from(context: Context, silo: CoverSilo): SiloedCovers { suspend fun from(context: Context, silo: CoverSilo): SiloedCovers {
val core = SiloCore.from(context, silo) val core = SiloCore.from(context, silo)
return SiloedCovers(silo, FileCovers(core.files, core.format)) return SiloedCovers(silo, InternalCovers(core.files, core.format))
} }
} }
} }
@ -70,7 +70,7 @@ class MutableSiloedCovers
private constructor( 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: MutableInternalCovers
) : SiloedCovers(silo, fileCovers), MutableCovers<FileCover> { ) : SiloedCovers(silo, fileCovers), MutableCovers<FileCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> = override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> =
when (val result = fileCovers.create(file, metadata)) { when (val result = fileCovers.create(file, metadata)) {
@ -96,7 +96,8 @@ private constructor(
): MutableSiloedCovers { ): MutableSiloedCovers {
val core = SiloCore.from(context, silo) val core = SiloCore.from(context, silo)
return MutableSiloedCovers( return MutableSiloedCovers(
core.rootDir, silo, MutableFileCovers(core.files, core.format, coverIdentifier)) core.rootDir, silo, MutableInternalCovers(core.files, core.format, coverIdentifier)
)
} }
} }
} }
@ -120,7 +121,7 @@ data class SiloedCoverId(val silo: CoverSilo, val id: String) {
} }
} }
private data class SiloCore(val rootDir: File, val files: AppFiles, val format: CoverFormat) { private data class SiloCore(val rootDir: File, val files: AppFS, val format: CoverFormat) {
companion object { companion object {
suspend fun from(context: Context, silo: CoverSilo): SiloCore { suspend fun from(context: Context, silo: CoverSilo): SiloCore {
val rootDir: File val rootDir: File
@ -129,7 +130,7 @@ private data class SiloCore(val rootDir: File, val files: AppFiles, val format:
rootDir = context.coversDir() rootDir = context.coversDir()
revisionDir = rootDir.resolve(silo.toString()).apply { mkdirs() } revisionDir = rootDir.resolve(silo.toString()).apply { mkdirs() }
} }
val files = AppFiles.at(revisionDir) val files = AppFS.at(revisionDir)
val format = silo.params?.let(CoverFormat::jpeg) ?: CoverFormat.asIs() val format = silo.params?.let(CoverFormat::jpeg) ?: CoverFormat.asIs()
return SiloCore(rootDir, files, format) return SiloCore(rootDir, files, format)
} }

View file

@ -19,8 +19,8 @@
package org.oxycblt.musikr package org.oxycblt.musikr
import org.oxycblt.musikr.cache.MutableCache import org.oxycblt.musikr.cache.MutableCache
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
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

View file

@ -25,8 +25,8 @@ import java.security.MessageDigest
import java.util.UUID import java.util.UUID
import kotlinx.parcelize.IgnoredOnParcel import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Date

View file

@ -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.musikr.cover package org.oxycblt.musikr.covers
import java.io.InputStream import java.io.InputStream
import org.oxycblt.musikr.fs.device.DeviceFile import org.oxycblt.musikr.fs.device.DeviceFile

View file

@ -1,6 +1,6 @@
/* /*
* Copyright (c) 2025 Auxio Project * Copyright (c) 2025 Auxio Project
* FolderCovers.kt is part of Auxio. * FSCovers.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.musikr.cover package org.oxycblt.musikr.covers.fs
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
@ -26,11 +26,16 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.covers.Covers
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 FolderCovers(private val context: Context) : Covers<FolderCover> { open class FSCovers(private val context: Context) : Covers<FolderCover> {
override suspend fun obtain(id: String): CoverResult<FolderCover> { override suspend fun obtain(id: String): CoverResult<FolderCover> {
// Parse the ID to get the directory URI // Parse the ID to get the directory URI
if (!id.startsWith("folder:")) { if (!id.startsWith("folder:")) {
@ -60,8 +65,8 @@ open class FolderCovers(private val context: Context) : Covers<FolderCover> {
} }
} }
class MutableFolderCovers(private val context: Context) : class MutableFSCovers(private val context: Context) :
FolderCovers(context), MutableCovers<FolderCover> { FSCovers(context), MutableCovers<FolderCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FolderCover> { 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()

View file

@ -1,22 +1,4 @@
/* package org.oxycblt.musikr.covers.internal
* Copyright (c) 2024 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.cover
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory

View file

@ -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.musikr.cover package org.oxycblt.musikr.covers.internal
import java.security.MessageDigest import java.security.MessageDigest

View file

@ -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.musikr.cover package org.oxycblt.musikr.covers.internal
class CoverParams private constructor(val resolution: Int, val quality: Int) { class CoverParams private constructor(val resolution: Int, val quality: Int) {
override fun hashCode() = 31 * resolution + quality override fun hashCode() = 31 * resolution + quality

View file

@ -16,18 +16,22 @@
* 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.musikr.cover package org.oxycblt.musikr.covers.internal
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.covers.Covers
import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.fs.app.AppFile import org.oxycblt.musikr.fs.app.AppFile
import org.oxycblt.musikr.fs.app.AppFiles import org.oxycblt.musikr.fs.app.AppFS
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 FileCovers(private val appFiles: AppFiles, private val coverFormat: CoverFormat) : open class InternalCovers(private val appFS: AppFS, private val coverFormat: CoverFormat) :
Covers<FileCover> { Covers<FileCover> {
override suspend fun obtain(id: String): CoverResult<FileCover> { override suspend fun obtain(id: String): CoverResult<FileCover> {
val file = appFiles.find(getFileName(id)) val file = appFS.find(getFileName(id))
return if (file != null) { return if (file != null) {
CoverResult.Hit(FileCoverImpl(id, file)) CoverResult.Hit(FileCoverImpl(id, file))
} else { } else {
@ -38,21 +42,21 @@ open class FileCovers(private val appFiles: AppFiles, private val coverFormat: C
protected fun getFileName(id: String) = "$id.${coverFormat.extension}" protected fun getFileName(id: String) = "$id.${coverFormat.extension}"
} }
class MutableFileCovers( class MutableInternalCovers(
private val appFiles: AppFiles, private val appFS: AppFS,
private val coverFormat: CoverFormat, private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier private val coverIdentifier: CoverIdentifier
) : FileCovers(appFiles, coverFormat), MutableCovers<FileCover> { ) : InternalCovers(appFS, coverFormat), MutableCovers<FileCover> {
override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> { override suspend fun create(file: DeviceFile, metadata: Metadata): CoverResult<FileCover> {
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 = appFiles.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(FileCoverImpl(id, coverFile))
} }
override suspend fun cleanup(excluding: Collection<Cover>) { override suspend fun cleanup(excluding: Collection<Cover>) {
val used = excluding.mapTo(mutableSetOf()) { getFileName(it.id) } val used = excluding.mapTo(mutableSetOf()) { getFileName(it.id) }
appFiles.deleteWhere { it !in used } appFS.deleteWhere { it !in used }
} }
} }

View file

@ -28,7 +28,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
interface AppFiles { interface AppFS {
suspend fun find(name: String): AppFile? suspend fun find(name: String): AppFile?
suspend fun write(name: String, block: suspend (OutputStream) -> Unit): AppFile suspend fun write(name: String, block: suspend (OutputStream) -> Unit): AppFile
@ -36,9 +36,9 @@ interface AppFiles {
suspend fun deleteWhere(block: (String) -> Boolean) suspend fun deleteWhere(block: (String) -> Boolean)
companion object { companion object {
suspend fun at(dir: File): AppFiles { suspend fun at(dir: File): AppFS {
withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) } withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) }
return AppFilesImpl(dir) return AppFSImpl(dir)
} }
} }
} }
@ -49,7 +49,7 @@ interface AppFile {
suspend fun open(): InputStream? suspend fun open(): InputStream?
} }
private class AppFilesImpl(private val dir: File) : AppFiles { private class AppFSImpl(private val dir: File) : AppFS {
private val fileMutexes = mutableMapOf<String, Mutex>() private val fileMutexes = mutableMapOf<String, Mutex>()
private val mapMutex = Mutex() private val mapMutex = Mutex()

View file

@ -30,16 +30,37 @@ import kotlinx.coroutines.flow.flow
import org.oxycblt.musikr.fs.MusicLocation import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
internal interface DeviceFiles { internal interface DeviceFS {
fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceNode> fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean = true): Flow<DeviceNode>
companion object { companion object {
fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe) fun from(context: Context): DeviceFS = DeviceFSImpl(context.contentResolverSafe)
} }
} }
sealed interface DeviceNode {
val uri: Uri
val path: Path
}
data class DeviceDirectory(
override val uri: Uri,
override val path: Path,
val parent: DeviceDirectory?,
var children: Flow<DeviceNode>
) : DeviceNode
data class DeviceFile(
override val uri: Uri,
override val path: Path,
val modifiedMs: Long,
val mimeType: String,
val size: Long,
val parent: DeviceDirectory
) : DeviceNode
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private class DeviceFilesImpl(private val contentResolver: ContentResolver) : DeviceFiles { private class DeviceFSImpl(private val contentResolver: ContentResolver) : DeviceFS {
override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceNode> = override fun explore(locations: Flow<MusicLocation>, ignoreHidden: Boolean): Flow<DeviceNode> =
locations.flatMapMerge { location -> locations.flatMapMerge { location ->
// Create a root directory for each location // Create a root directory for each location

View file

@ -1,44 +0,0 @@
/*
* Copyright (c) 2024 Auxio Project
* DeviceFile.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.fs.device
import android.net.Uri
import kotlinx.coroutines.flow.Flow
import org.oxycblt.musikr.fs.Path
sealed interface DeviceNode {
val uri: Uri
val path: Path
}
data class DeviceDirectory(
override val uri: Uri,
override val path: Path,
val parent: DeviceDirectory?,
var children: Flow<DeviceNode>
) : DeviceNode
data class DeviceFile(
override val uri: Uri,
override val path: Path,
val modifiedMs: Long,
val mimeType: String,
val size: Long,
val parent: DeviceDirectory
) : DeviceNode

View file

@ -22,7 +22,7 @@ import org.oxycblt.musikr.Album
import org.oxycblt.musikr.Artist import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Date
import org.oxycblt.musikr.tag.interpret.PreAlbum import org.oxycblt.musikr.tag.interpret.PreAlbum
import org.oxycblt.musikr.util.update import org.oxycblt.musikr.util.update

View file

@ -23,7 +23,7 @@ import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
import org.oxycblt.musikr.tag.interpret.PreArtist import org.oxycblt.musikr.tag.interpret.PreArtist
import org.oxycblt.musikr.util.update import org.oxycblt.musikr.util.update

View file

@ -22,7 +22,7 @@ import org.oxycblt.musikr.Artist
import org.oxycblt.musikr.Genre import org.oxycblt.musikr.Genre
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
import org.oxycblt.musikr.tag.interpret.PreGenre import org.oxycblt.musikr.tag.interpret.PreGenre
import org.oxycblt.musikr.util.update import org.oxycblt.musikr.util.update

View file

@ -20,7 +20,7 @@ package org.oxycblt.musikr.model
import org.oxycblt.musikr.Playlist import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cover.CoverCollection import org.oxycblt.musikr.covers.CoverCollection
import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo import org.oxycblt.musikr.playlist.interpret.PrePlaylistInfo
import org.oxycblt.musikr.tag.Name import org.oxycblt.musikr.tag.Name

View file

@ -33,13 +33,13 @@ import kotlinx.coroutines.flow.merge
import org.oxycblt.musikr.Storage import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.Cache import org.oxycblt.musikr.cache.Cache
import org.oxycblt.musikr.cache.CacheResult import org.oxycblt.musikr.cache.CacheResult
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.cover.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.DeviceFile
import org.oxycblt.musikr.fs.device.DeviceFiles import org.oxycblt.musikr.fs.device.DeviceFS
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
@ -50,12 +50,12 @@ internal interface ExploreStep {
companion object { companion object {
fun from(context: Context, storage: Storage): ExploreStep = fun from(context: Context, storage: Storage): ExploreStep =
ExploreStepImpl( ExploreStepImpl(
DeviceFiles.from(context), storage.cache, storage.covers, storage.storedPlaylists) DeviceFS.from(context), storage.cache, storage.covers, storage.storedPlaylists)
} }
} }
private class ExploreStepImpl( private class ExploreStepImpl(
private val deviceFiles: DeviceFiles, private val deviceFS: DeviceFS,
private val cache: Cache, private val cache: Cache,
private val covers: Covers<out Cover>, private val covers: Covers<out Cover>,
private val storedPlaylists: StoredPlaylists private val storedPlaylists: StoredPlaylists
@ -64,7 +64,7 @@ private class ExploreStepImpl(
override fun explore(locations: List<MusicLocation>): Flow<Explored> { override fun explore(locations: List<MusicLocation>): Flow<Explored> {
val addingMs = System.currentTimeMillis() val addingMs = System.currentTimeMillis()
return merge( return merge(
deviceFiles deviceFS
.explore(locations.asFlow()) .explore(locations.asFlow())
.flattenFilter { it.mimeType.startsWith("audio/") || it.mimeType == M3U.MIME_TYPE } .flattenFilter { it.mimeType.startsWith("audio/") || it.mimeType == M3U.MIME_TYPE }
.distribute(8) .distribute(8)

View file

@ -26,9 +26,9 @@ import kotlinx.coroutines.flow.onCompletion
import org.oxycblt.musikr.Storage import org.oxycblt.musikr.Storage
import org.oxycblt.musikr.cache.CachedSong import org.oxycblt.musikr.cache.CachedSong
import org.oxycblt.musikr.cache.MutableCache import org.oxycblt.musikr.cache.MutableCache
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.cover.CoverResult import org.oxycblt.musikr.covers.CoverResult
import org.oxycblt.musikr.cover.MutableCovers import org.oxycblt.musikr.covers.MutableCovers
import org.oxycblt.musikr.metadata.MetadataExtractor import org.oxycblt.musikr.metadata.MetadataExtractor
import org.oxycblt.musikr.tag.parse.TagParser import org.oxycblt.musikr.tag.parse.TagParser

View file

@ -18,7 +18,7 @@
package org.oxycblt.musikr.pipeline package org.oxycblt.musikr.pipeline
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.fs.device.DeviceFile import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Properties import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.playlist.PlaylistFile import org.oxycblt.musikr.playlist.PlaylistFile
@ -44,7 +44,7 @@ internal sealed interface Extracted : PipelineItem {
sealed interface Invalid : Extracted sealed interface Invalid : Extracted
} }
data object InvalidSong : Extracted.Invalid internal data object InvalidSong : Extracted.Invalid
internal data class RawPlaylist(val file: PlaylistFile) : Explored.Known, Extracted.Valid internal data class RawPlaylist(val file: PlaylistFile) : Explored.Known, Extracted.Valid

View file

@ -21,7 +21,7 @@ package org.oxycblt.musikr.tag.interpret
import android.net.Uri import android.net.Uri
import java.util.UUID import java.util.UUID
import org.oxycblt.musikr.Music import org.oxycblt.musikr.Music
import org.oxycblt.musikr.cover.Cover import org.oxycblt.musikr.covers.Cover
import org.oxycblt.musikr.fs.Format import org.oxycblt.musikr.fs.Format
import org.oxycblt.musikr.fs.Path import org.oxycblt.musikr.fs.Path
import org.oxycblt.musikr.tag.Date import org.oxycblt.musikr.tag.Date