diff --git a/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt deleted file mode 100644 index 193b4a75a..000000000 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexing.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) 2023 Auxio Project - * Indexing.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 . - */ - -package org.oxycblt.auxio.music - -import android.os.Build -import org.oxycblt.musikr.IndexingProgress - -/** Version-aware permission identifier for reading audio files. */ -val PERMISSION_READ_AUDIO = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - android.Manifest.permission.READ_MEDIA_AUDIO - } else { - android.Manifest.permission.READ_EXTERNAL_STORAGE - } - -/** - * Represents the current state of the music loader. - * - * @author Alexander Capehart (OxygenCobalt) - */ -sealed interface IndexingState { - /** - * Music loading is on-going. - * - * @param progress The current progress of the music loading. - */ - data class Indexing(val progress: IndexingProgress) : IndexingState - - /** - * Music loading has completed. - * - * @param error If music loading has failed, the error that occurred will be here. Otherwise, it - * will be null. - */ - data class Completed(val error: Exception?) : IndexingState -} diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt index 5d524b1fd..621bec0c4 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicModule.kt @@ -26,7 +26,6 @@ import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton -import org.oxycblt.musikr.cache.Cache import org.oxycblt.musikr.cache.StoredCache import org.oxycblt.musikr.playlist.db.StoredPlaylists @@ -41,7 +40,9 @@ interface MusicModule { @Module @InstallIn(SingletonComponent::class) class MusikrShimModule { - @Singleton @Provides fun storedCache(@ApplicationContext context: Context) = StoredCache.from(context) + @Singleton + @Provides + fun storedCache(@ApplicationContext context: Context) = StoredCache.from(context) @Singleton @Provides diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt index b07fccca6..0af145490 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicRepository.kt @@ -209,6 +209,28 @@ interface MusicRepository { } } +/** + * Represents the current state of the music loader. + * + * @author Alexander Capehart (OxygenCobalt) + */ +sealed interface IndexingState { + /** + * Music loading is on-going. + * + * @param progress The current progress of the music loading. + */ + data class Indexing(val progress: IndexingProgress) : IndexingState + + /** + * Music loading has completed. + * + * @param error If music loading has failed, the error that occurred will be here. Otherwise, it + * will be null. + */ + data class Completed(val error: Exception?) : IndexingState +} + class MusicRepositoryImpl @Inject constructor( diff --git a/app/src/main/java/org/oxycblt/auxio/music/RevisionedLibrary.kt b/app/src/main/java/org/oxycblt/auxio/music/RevisionedLibrary.kt index d35e5f3e5..ad176e28a 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/RevisionedLibrary.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/RevisionedLibrary.kt @@ -75,7 +75,7 @@ class RevisionedCover(private val revision: UUID, val inner: Cover) : Cover by i get() = "${inner.id}@${revision}" } -internal fun String.toUuidOrNull(): UUID? = +private fun String.toUuidOrNull(): UUID? = try { UUID.fromString(this) } catch (e: IllegalArgumentException) { diff --git a/app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/interpret/SeparatorsDialog.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt rename to app/src/main/java/org/oxycblt/auxio/music/interpret/SeparatorsDialog.kt index f2c5d0cec..4a37a7461 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/metadata/SeparatorsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/interpret/SeparatorsDialog.kt @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.metadata +package org.oxycblt.auxio.music.interpret import android.os.Bundle import android.view.LayoutInflater diff --git a/app/src/main/res/navigation/outer.xml b/app/src/main/res/navigation/outer.xml index 503172f3b..0d15cbc8a 100644 --- a/app/src/main/res/navigation/outer.xml +++ b/app/src/main/res/navigation/outer.xml @@ -96,7 +96,7 @@ tools:layout="@layout/dialog_music_locations" /> diff --git a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt index a9f12af2a..dd4d8df63 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/CacheDatabase.kt @@ -21,7 +21,6 @@ package org.oxycblt.musikr.cache import android.content.Context import androidx.room.Dao import androidx.room.Database -import androidx.room.Delete import androidx.room.Entity import androidx.room.Insert import androidx.room.OnConflictStrategy @@ -32,7 +31,6 @@ import androidx.room.RoomDatabase import androidx.room.Transaction import androidx.room.TypeConverter import androidx.room.TypeConverters -import androidx.room.Update import org.oxycblt.musikr.cover.StoredCovers import org.oxycblt.musikr.fs.DeviceFile import org.oxycblt.musikr.metadata.Properties @@ -67,8 +65,7 @@ internal interface VisibleCacheDao { @Query("SELECT addedMs FROM CachedSong WHERE uri = :uri") suspend fun selectAddedMs(uri: String): Long? - @Transaction - suspend fun touch(uri: String) = updateTouchedNs(uri, System.nanoTime()) + @Transaction suspend fun touch(uri: String) = updateTouchedNs(uri, System.nanoTime()) @Query("UPDATE cachedsong SET touchedNs = :nowNs WHERE uri = :uri") suspend fun updateTouchedNs(uri: String, nowNs: Long) @@ -84,8 +81,7 @@ internal interface InvisibleCacheDao { internal interface CacheWriteDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun updateSong(cachedSong: CachedSong) - @Query("DELETE FROM CachedSong WHERE touchedNs < :now") - suspend fun pruneOlderThan(now: Long) + @Query("DELETE FROM CachedSong WHERE touchedNs < :now") suspend fun pruneOlderThan(now: Long) } @Entity diff --git a/musikr/src/main/java/org/oxycblt/musikr/cache/StoredCache.kt b/musikr/src/main/java/org/oxycblt/musikr/cache/StoredCache.kt index fca633e5d..5f4c95d70 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/cache/StoredCache.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/cache/StoredCache.kt @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2024 Auxio Project + * StoredCache.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 . + */ + package org.oxycblt.musikr.cache import android.content.Context @@ -24,8 +42,7 @@ private class StoredCacheImpl(private val cacheDatabase: CacheDatabase) : Stored private abstract class BaseStoredCache(protected val writeDao: CacheWriteDao) : Cache() { private val created = System.nanoTime() - override suspend fun write(song: RawSong) = - writeDao.updateSong(CachedSong.fromRawSong(song)) + override suspend fun write(song: RawSong) = writeDao.updateSong(CachedSong.fromRawSong(song)) override suspend fun finalize() { // Anything not create during this cache's use implies that it has not been @@ -34,13 +51,10 @@ private abstract class BaseStoredCache(protected val writeDao: CacheWriteDao) : } } -private class VisibleStoredCache( - private val visibleDao: VisibleCacheDao, - writeDao: CacheWriteDao -) : BaseStoredCache(writeDao) { +private class VisibleStoredCache(private val visibleDao: VisibleCacheDao, writeDao: CacheWriteDao) : + BaseStoredCache(writeDao) { override suspend fun read(file: DeviceFile, storedCovers: StoredCovers): CacheResult { - val song = - visibleDao.selectSong(file.uri.toString()) ?: return CacheResult.Miss(file, null) + val song = visibleDao.selectSong(file.uri.toString()) ?: return CacheResult.Miss(file, null) if (song.modifiedMs != file.lastModified) { // We *found* this file earlier, but it's out of date. // Send back it with the timestamp so it will be re-used. @@ -53,7 +67,8 @@ private class VisibleStoredCache( } class Factory(private val cacheDatabase: CacheDatabase) : Cache.Factory() { - override fun open() = VisibleStoredCache(cacheDatabase.visibleDao(), cacheDatabase.writeDao()) + override fun open() = + VisibleStoredCache(cacheDatabase.visibleDao(), cacheDatabase.writeDao()) } } @@ -65,6 +80,7 @@ private class InvisibleStoredCache( CacheResult.Miss(file, invisibleCacheDao.selectAddedMs(file.uri.toString())) class Factory(private val cacheDatabase: CacheDatabase) : Cache.Factory() { - override fun open() = InvisibleStoredCache(cacheDatabase.invisibleDao(), cacheDatabase.writeDao()) + override fun open() = + InvisibleStoredCache(cacheDatabase.invisibleDao(), cacheDatabase.writeDao()) } } diff --git a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt index 7ef83627c..d42a17389 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/pipeline/ExtractStep.kt @@ -15,11 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package org.oxycblt.musikr.pipeline import android.content.Context import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.buffer @@ -52,8 +53,7 @@ internal interface ExtractStep { MetadataExtractor.new(), TagParser.new(), storage.cache, - storage.storedCovers - ) + storage.storedCovers) } } @@ -64,6 +64,7 @@ private class ExtractStepImpl( private val cacheFactory: Cache.Factory, private val storedCovers: MutableStoredCovers ) : ExtractStep { + @OptIn(ExperimentalCoroutinesApi::class) override fun extract(nodes: Flow): Flow { val cache = cacheFactory.open() val addingMs = System.currentTimeMillis() @@ -114,13 +115,13 @@ private class ExtractStepImpl( val metadata = fds.mapNotNull { fileWith -> - wrap(fileWith.file) { _ -> - metadataExtractor - .extract(fileWith.with) - ?.let { FileWith(fileWith.file, it) } - .also { withContext(Dispatchers.IO) { fileWith.with.close() } } + wrap(fileWith.file) { _ -> + metadataExtractor + .extract(fileWith.with) + ?.let { FileWith(fileWith.file, it) } + .also { withContext(Dispatchers.IO) { fileWith.with.close() } } + } } - } .flowOn(Dispatchers.IO) // Covers are pretty big, so cap the amount of parsed metadata in-memory to at most // 8 to minimize GCs. @@ -150,19 +151,17 @@ private class ExtractStepImpl( } .flattenMerge() - val merged = merge( - filterFlow.manager, - readDistributedFlow.manager, - cacheFlow.manager, - cachedSongs, - writeDistributedFlow.manager, - writtenSongs, - playlistNodes - ) + val merged = + merge( + filterFlow.manager, + readDistributedFlow.manager, + cacheFlow.manager, + cachedSongs, + writeDistributedFlow.manager, + writtenSongs, + playlistNodes) - return merged.onCompletion { - cache.finalize() - } + return merged.onCompletion { cache.finalize() } } private data class FileWith(val file: DeviceFile, val with: T) diff --git a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt index ba6c28da4..413f351a9 100644 --- a/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt +++ b/musikr/src/main/java/org/oxycblt/musikr/tag/interpret/PreMusic.kt @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ - + package org.oxycblt.musikr.tag.interpret import android.net.Uri @@ -53,21 +53,22 @@ internal data class PreSong( val preArtists: List, val preGenres: List ) { - val uid = musicBrainzId?.let { Music.UID.musicBrainz(Music.UID.Item.SONG, it) } - ?: Music.UID.auxio(Music.UID.Item.SONG) { - // Song UIDs are based on the raw data without parsing so that they remain - // consistent across music setting changes. Parents are not held up to the - // same standard since grouping is already inherently linked to settings. - update(rawName) - update(preAlbum.rawName) - update(date) + val uid = + musicBrainzId?.let { Music.UID.musicBrainz(Music.UID.Item.SONG, it) } + ?: Music.UID.auxio(Music.UID.Item.SONG) { + // Song UIDs are based on the raw data without parsing so that they remain + // consistent across music setting changes. Parents are not held up to the + // same standard since grouping is already inherently linked to settings. + update(rawName) + update(preAlbum.rawName) + update(date) - update(track) - update(disc?.number) + update(track) + update(disc?.number) - update(preArtists.map { artist -> artist.rawName }) - update(preAlbum.preArtists.map { artist -> artist.rawName }) - } + update(preArtists.map { artist -> artist.rawName }) + update(preAlbum.preArtists.map { artist -> artist.rawName }) + } } internal data class PreAlbum(