musikr: streamline package structure
This commit is contained in:
parent
0d0a20d760
commit
6feee93438
29 changed files with 118 additions and 148 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() =
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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()
|
|
@ -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
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
@ -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 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue