musikr: make subpackages for default impls
This commit is contained in:
parent
dbf2dd510c
commit
0919f29085
16 changed files with 78 additions and 94 deletions
|
@ -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)
|
||||
|
|
|
@ -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}"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
@ -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(
|
|
@ -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
|
||||
)
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue