all: cleanup
This commit is contained in:
parent
61fd11fe04
commit
75612dd1eb
10 changed files with 91 additions and 108 deletions
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.oxycblt.auxio.music.metadata
|
||||
package org.oxycblt.auxio.music.interpret
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
|
@ -96,7 +96,7 @@
|
|||
tools:layout="@layout/dialog_music_locations" />
|
||||
<dialog
|
||||
android:id="@+id/separators_dialog"
|
||||
android:name="org.oxycblt.auxio.music.metadata.SeparatorsDialog"
|
||||
android:name="org.oxycblt.auxio.music.interpret.SeparatorsDialog"
|
||||
android:label="separators_dialog"
|
||||
tools:layout="@layout/dialog_separators" />
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ 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<ExploreNode>): Flow<ExtractedMusic> {
|
||||
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<T>(val file: DeviceFile, val with: T)
|
||||
|
|
|
@ -53,21 +53,22 @@ internal data class PreSong(
|
|||
val preArtists: List<PreArtist>,
|
||||
val preGenres: List<PreGenre>
|
||||
) {
|
||||
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(
|
||||
|
|
Loading…
Reference in a new issue