musikr: make subpackages for default impls

This commit is contained in:
Alexander Capehart 2025-01-21 15:58:44 -07:00
parent dbf2dd510c
commit 0919f29085
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
16 changed files with 78 additions and 94 deletions

View file

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

View file

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

View file

@ -23,9 +23,9 @@ import java.util.UUID
import javax.inject.Inject
import org.oxycblt.auxio.image.CoverMode
import org.oxycblt.auxio.image.ImageSettings
import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.CoverParams
import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.cover.fs.CoverIdentifier
import org.oxycblt.musikr.cover.fs.CoverParams
interface SettingCovers {
suspend fun create(context: Context, revision: UUID): MutableCovers

View file

@ -23,21 +23,21 @@ import java.io.File
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.CoverFormat
import org.oxycblt.musikr.cover.CoverIdentifier
import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.FileCover
import org.oxycblt.musikr.cover.FileCovers
import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.cover.MutableFileCovers
import org.oxycblt.musikr.cover.ObtainResult
import org.oxycblt.musikr.fs.app.AppFiles
import org.oxycblt.musikr.cover.fs.CoverFormat
import org.oxycblt.musikr.cover.fs.CoverIdentifier
import org.oxycblt.musikr.cover.fs.FSCovers
import org.oxycblt.musikr.cover.fs.FileCover
import org.oxycblt.musikr.cover.fs.MutableFSCovers
import org.oxycblt.musikr.fs.app.AppFS
open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: FileCovers) : Covers {
open class SiloedCovers(private val silo: CoverSilo, private val FSCovers: FSCovers) : Covers {
override suspend fun obtain(id: String): ObtainResult<SiloedCover> {
val coverId = SiloedCoverId.parse(id) ?: return ObtainResult.Miss()
if (coverId.silo != silo) return ObtainResult.Miss()
return when (val result = fileCovers.obtain(coverId.id)) {
return when (val result = FSCovers.obtain(coverId.id)) {
is ObtainResult.Hit -> ObtainResult.Hit(SiloedCover(silo, result.cover))
is ObtainResult.Miss -> ObtainResult.Miss()
}
@ -46,7 +46,7 @@ open class SiloedCovers(private val silo: CoverSilo, private val fileCovers: Fil
companion object {
suspend fun from(context: Context, silo: CoverSilo): SiloedCovers {
val core = SiloCore.from(context, silo)
return SiloedCovers(silo, FileCovers(core.files, core.format))
return SiloedCovers(silo, FSCovers(core.files, core.format))
}
}
}
@ -55,7 +55,7 @@ class MutableSiloedCovers
private constructor(
private val rootDir: File,
private val silo: CoverSilo,
private val fileCovers: MutableFileCovers
private val fileCovers: MutableFSCovers
) : SiloedCovers(silo, fileCovers), MutableCovers {
override suspend fun write(data: ByteArray) = SiloedCover(silo, fileCovers.write(data))
@ -77,7 +77,7 @@ private constructor(
): MutableSiloedCovers {
val core = SiloCore.from(context, silo)
return MutableSiloedCovers(
core.rootDir, silo, MutableFileCovers(core.files, core.format, coverIdentifier))
core.rootDir, silo, MutableFSCovers(core.files, core.format, coverIdentifier))
}
}
}
@ -101,7 +101,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 {
suspend fun from(context: Context, silo: CoverSilo): SiloCore {
val rootDir: File
@ -110,7 +110,7 @@ private data class SiloCore(val rootDir: File, val files: AppFiles, val format:
rootDir = context.coversDir()
revisionDir = rootDir.resolve(silo.toString()).apply { mkdirs() }
}
val files = AppFiles.at(revisionDir)
val files = AppFS.at(revisionDir)
val format = CoverFormat.jpeg(silo.params)
return SiloCore(rootDir, files, format)
}

View file

@ -25,7 +25,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
import org.oxycblt.musikr.cache.DBSongCache
import org.oxycblt.musikr.cache.DatabaseSongCache
import org.oxycblt.musikr.cache.SongCache
import org.oxycblt.musikr.playlist.db.StoredPlaylists
@ -34,7 +34,7 @@ import org.oxycblt.musikr.playlist.db.StoredPlaylists
class MusikrShimModule {
@Singleton
@Provides
fun songCache(@ApplicationContext context: Context): SongCache = DBSongCache.from(context)
fun songCache(@ApplicationContext context: Context): SongCache = DatabaseSongCache.from(context)
@Singleton
@Provides

View file

@ -16,10 +16,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.cache
package org.oxycblt.musikr.cache.db
import android.content.Context
import org.oxycblt.musikr.Song
import org.oxycblt.musikr.cache.CacheResult
import org.oxycblt.musikr.cache.CachedSong
import org.oxycblt.musikr.cache.MutableSongCache
import org.oxycblt.musikr.fs.device.DeviceFile
import org.oxycblt.musikr.metadata.Properties
import org.oxycblt.musikr.tag.parse.ParsedTags

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.cache
package org.oxycblt.musikr.cache.db
import android.content.Context
import androidx.room.Dao

View file

@ -20,22 +20,6 @@ package org.oxycblt.musikr.cover
import java.io.InputStream
interface Covers {
suspend fun obtain(id: String): ObtainResult<out Cover>
}
interface MutableCovers : Covers {
suspend fun write(data: ByteArray): Cover
suspend fun cleanup(excluding: Collection<Cover>)
}
sealed interface ObtainResult<T : Cover> {
data class Hit<T : Cover>(val cover: T) : ObtainResult<T>
class Miss<T : Cover> : ObtainResult<T>
}
interface Cover {
val id: String
@ -58,3 +42,19 @@ class CoverCollection private constructor(val covers: List<Cover>) {
.map { it.value.first() })
}
}
interface Covers {
suspend fun obtain(id: String): ObtainResult<out Cover>
}
interface MutableCovers : Covers {
suspend fun write(data: ByteArray): Cover
suspend fun cleanup(excluding: Collection<Cover>)
}
sealed interface ObtainResult<T : Cover> {
data class Hit<T : Cover>(val cover: T) : ObtainResult<T>
class Miss<T : Cover> : ObtainResult<T>
}

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.cover
package org.oxycblt.musikr.cover.fs
import android.graphics.Bitmap
import android.graphics.BitmapFactory

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.cover
package org.oxycblt.musikr.cover.fs
import java.security.MessageDigest

View file

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.cover
package org.oxycblt.musikr.cover.fs
class CoverParams private constructor(val resolution: Int, val quality: Int) {
override fun hashCode() = 31 * resolution + quality

View file

@ -1,6 +1,6 @@
/*
* Copyright (c) 2025 Auxio Project
* FileCovers.kt is part of Auxio.
* FSCovers.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
@ -16,16 +16,19 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package org.oxycblt.musikr.cover
package org.oxycblt.musikr.cover.fs
import android.os.ParcelFileDescriptor
import org.oxycblt.musikr.cover.Cover
import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.MutableCovers
import org.oxycblt.musikr.cover.ObtainResult
import org.oxycblt.musikr.fs.app.AppFS
import org.oxycblt.musikr.fs.app.AppFile
import org.oxycblt.musikr.fs.app.AppFiles
open class FileCovers(private val appFiles: AppFiles, private val coverFormat: CoverFormat) :
Covers {
open class FSCovers(private val appFS: AppFS, private val coverFormat: CoverFormat) : Covers {
override suspend fun obtain(id: String): ObtainResult<FileCover> {
val file = appFiles.find(getFileName(id))
val file = appFS.find(getFileName(id))
return if (file != null) {
ObtainResult.Hit(FileCoverImpl(id, file))
} else {
@ -36,20 +39,20 @@ open class FileCovers(private val appFiles: AppFiles, private val coverFormat: C
protected fun getFileName(id: String) = "$id.${coverFormat.extension}"
}
class MutableFileCovers(
private val appFiles: AppFiles,
class MutableFSCovers(
private val appFS: AppFS,
private val coverFormat: CoverFormat,
private val coverIdentifier: CoverIdentifier
) : FileCovers(appFiles, coverFormat), MutableCovers {
) : FSCovers(appFS, coverFormat), MutableCovers {
override suspend fun write(data: ByteArray): FileCover {
val id = coverIdentifier.identify(data)
val file = appFiles.write(getFileName(id)) { coverFormat.transcodeInto(data, it) }
val file = appFS.write(getFileName(id)) { coverFormat.transcodeInto(data, it) }
return FileCoverImpl(id, file)
}
override suspend fun cleanup(excluding: Collection<Cover>) {
val used = excluding.mapTo(mutableSetOf()) { getFileName(it.id) }
appFiles.deleteWhere { it !in used }
appFS.deleteWhere { it !in used }
}
}

View file

@ -1,6 +1,6 @@
/*
* 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
* it under the terms of the GNU General Public License as published by
@ -28,7 +28,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
interface AppFiles {
interface AppFS {
suspend fun find(name: String): AppFile?
suspend fun write(name: String, block: suspend (OutputStream) -> Unit): AppFile
@ -36,9 +36,9 @@ interface AppFiles {
suspend fun deleteWhere(block: (String) -> Boolean)
companion object {
suspend fun at(dir: File): AppFiles {
suspend fun at(dir: File): AppFS {
withContext(Dispatchers.IO) { check(dir.exists() && dir.isDirectory) }
return AppFilesImpl(dir)
return AppFSImpl(dir)
}
}
}
@ -49,7 +49,7 @@ interface AppFile {
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 mapMutex = Mutex()

View file

@ -1,6 +1,6 @@
/*
* 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
* it under the terms of the GNU General Public License as published by
@ -32,16 +32,24 @@ import kotlinx.coroutines.flow.flow
import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.Path
internal interface DeviceFiles {
internal interface DeviceFS {
fun explore(locations: Flow<MusicLocation>): Flow<DeviceFile>
companion object {
fun from(context: Context): DeviceFiles = DeviceFilesImpl(context.contentResolverSafe)
fun from(context: Context): DeviceFS = DeviceFSImpl(context.contentResolverSafe)
}
}
data class DeviceFile(
val uri: Uri,
val mimeType: String,
val path: Path,
val size: Long,
val modifiedMs: Long
)
@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>): Flow<DeviceFile> =
locations.flatMapMerge { location ->
exploreImpl(

View file

@ -1,30 +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 org.oxycblt.musikr.fs.Path
data class DeviceFile(
val uri: Uri,
val mimeType: String,
val path: Path,
val size: Long,
val modifiedMs: Long
)

View file

@ -35,7 +35,7 @@ import org.oxycblt.musikr.cache.SongCache
import org.oxycblt.musikr.cover.Covers
import org.oxycblt.musikr.cover.ObtainResult
import org.oxycblt.musikr.fs.MusicLocation
import org.oxycblt.musikr.fs.device.DeviceFiles
import org.oxycblt.musikr.fs.device.DeviceFS
import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.playlist.m3u.M3U
@ -45,19 +45,19 @@ internal interface ExploreStep {
companion object {
fun from(context: Context, storage: Storage): ExploreStep =
ExploreStepImpl(
DeviceFiles.from(context), storage.storedPlaylists, storage.cache, storage.covers)
DeviceFS.from(context), storage.storedPlaylists, storage.cache, storage.covers)
}
}
private class ExploreStepImpl(
private val deviceFiles: DeviceFiles,
private val deviceFS: DeviceFS,
private val storedPlaylists: StoredPlaylists,
private val songCache: SongCache,
private val covers: Covers
) : ExploreStep {
override fun explore(locations: List<MusicLocation>): Flow<Explored> {
val audioFiles =
deviceFiles
deviceFS
.explore(locations.asFlow())
.filter { it.mimeType.startsWith("audio/") || it.mimeType == M3U.MIME_TYPE }
.flowOn(Dispatchers.IO)