music: introduce library revisions

Will be used to maintain image loading consistency even during loads.
This commit is contained in:
Alexander Capehart 2024-12-17 15:40:05 -05:00
parent 7df5c5973e
commit a1188b8d4b
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 69 additions and 9 deletions

View file

@ -43,6 +43,7 @@ import org.oxycblt.musikr.playlist.db.PlaylistDatabase
import org.oxycblt.musikr.playlist.db.StoredPlaylists
import org.oxycblt.musikr.tag.interpret.Naming
import org.oxycblt.musikr.tag.interpret.Separators
import java.util.UUID
import timber.log.Timber as L
/**
@ -57,7 +58,8 @@ import timber.log.Timber as L
* configurations
*/
interface MusicRepository {
val library: Library?
/** The current library */
val library: RevisionedLibrary?
/** The current state of music loading. Null if no load has occurred yet. */
val indexingState: IndexingState?
@ -222,7 +224,7 @@ constructor(
private val indexingListeners = mutableListOf<MusicRepository.IndexingListener>()
@Volatile private var indexingWorker: MusicRepository.IndexingWorker? = null
@Volatile override var library: MutableLibrary? = null
@Volatile override var library: MutableRevisionedLibrary? = null
@Volatile private var previousCompletedState: IndexingState.Completed? = null
@Volatile private var currentIndexingState: IndexingState? = null
override val indexingState: IndexingState?
@ -362,17 +364,19 @@ constructor(
}
val locations = musicSettings.musicLocations
val storage =
val revision: UUID
val storage: Storage
if (withCache) {
Storage(
revision = this.library?.revision ?: musicSettings.revision
storage = Storage(
Cache.full(cacheDatabase),
StoredCovers.from(context, "covers"),
StoredCovers.from(context, "covers_$revision"),
StoredPlaylists.from(playlistDatabase))
} else {
// TODO: Revisioned cache (as a stateful extension of musikr)
Storage(
revision = UUID.randomUUID()
storage = Storage(
Cache.writeOnly(cacheDatabase),
StoredCovers.from(context, "covers"),
StoredCovers.from(context, "covers_$revision"),
StoredPlaylists.from(playlistDatabase))
}
@ -381,6 +385,9 @@ constructor(
val newLibrary =
Musikr.new(context, storage, interpretation).run(locations, ::emitIndexingProgress)
val revisionedLibrary =
MutableRevisionedLibrary(revision, newLibrary)
emitIndexingCompletion(null)
// We want to make sure that all reads and writes are synchronized due to the sheer
@ -402,7 +409,7 @@ constructor(
return
}
this.library = newLibrary
this.library = revisionedLibrary
}
// Consumers expect their updates to be on the main thread (notably PlaybackService),
@ -410,6 +417,11 @@ constructor(
withContext(Dispatchers.Main) {
dispatchLibraryChange(deviceLibraryChanged, userLibraryChanged)
}
// Quietly update the revision if needed (this way we don't disrupt any new loads)
if (!withCache) {
musicSettings.revision = revisionedLibrary.revision
}
}
private suspend fun emitIndexingProgress(progress: IndexingProgress) {

View file

@ -25,6 +25,7 @@ import javax.inject.Inject
import org.oxycblt.auxio.R
import org.oxycblt.auxio.settings.Settings
import org.oxycblt.musikr.fs.MusicLocation
import java.util.UUID
import timber.log.Timber as L
/**
@ -33,6 +34,8 @@ import timber.log.Timber as L
* @author Alexander Capehart (OxygenCobalt)
*/
interface MusicSettings : Settings<MusicSettings.Listener> {
/** The current library revision. */
var revision: UUID
/** The locations of music to load. */
var musicLocations: List<MusicLocation>
/** Whether to exclude non-music audio files from the music library. */
@ -55,6 +58,17 @@ interface MusicSettings : Settings<MusicSettings.Listener> {
class MusicSettingsImpl @Inject constructor(@ApplicationContext private val context: Context) :
Settings.Impl<MusicSettings.Listener>(context), MusicSettings {
override var revision: UUID
get() = UUID.fromString(
sharedPreferences.getString(getString(R.string.set_key_library_revision), null)
?: UUID.randomUUID().toString())
set(value) {
sharedPreferences.edit {
putString(getString(R.string.set_key_library_revision), value.toString())
apply()
}
}
override var musicLocations: List<MusicLocation>
get() {
val locations =

View file

@ -0,0 +1,32 @@
package org.oxycblt.auxio.music
import org.oxycblt.musikr.Library
import org.oxycblt.musikr.MutableLibrary
import org.oxycblt.musikr.Playlist
import org.oxycblt.musikr.Song
import java.util.UUID
interface RevisionedLibrary : Library {
val revision: UUID
}
class MutableRevisionedLibrary(
override val revision: UUID,
private val inner: MutableLibrary
) : RevisionedLibrary, Library by inner, MutableLibrary {
override suspend fun createPlaylist(name: String, songs: List<Song>) =
MutableRevisionedLibrary(revision, inner.createPlaylist(name, songs))
override suspend fun renamePlaylist(playlist: Playlist, name: String) =
MutableRevisionedLibrary(revision, inner.renamePlaylist(playlist, name))
override suspend fun addToPlaylist(playlist: Playlist, songs: List<Song>) =
MutableRevisionedLibrary(revision, inner.addToPlaylist(playlist, songs))
override suspend fun rewritePlaylist(playlist: Playlist, songs: List<Song>) =
MutableRevisionedLibrary(revision, inner.rewritePlaylist(playlist, songs))
override suspend fun deletePlaylist(playlist: Playlist) =
MutableRevisionedLibrary(revision, inner.deletePlaylist(playlist))
}

View file

@ -53,6 +53,8 @@
<string name="set_key_artist_songs_sort" translatable="false">auxio_artist_sort</string>
<string name="set_key_genre_songs_sort" translatable="false">auxio_genre_sort</string>
<string name="set_key_library_revision" translatable="false">auxio_library_revision</string>
<string-array name="entries_theme">
<item>@string/set_theme_auto</item>
<item>@string/set_theme_day</item>