music: consider settings in equality

Make it so that music items are meaningfully different when they were
created under different settings. This resolves an issue where music
information would not correctly update when separators or intelligent
sorting would change.

Resolves #546.
This commit is contained in:
Alexander Capehart 2023-08-18 15:57:53 -06:00
parent 9a67a0d539
commit 881fb58648
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
3 changed files with 61 additions and 23 deletions

View file

@ -1,5 +1,11 @@
# Changelog # Changelog
## dev
#### What's Fixed
- Fixed app restart being required when changing intelligent sorting
or music separator settings
## 3.2.0 ## 3.2.0
#### What's New #### What's New

View file

@ -53,8 +53,8 @@ import org.oxycblt.auxio.util.update
*/ */
class SongImpl( class SongImpl(
private val rawSong: RawSong, private val rawSong: RawSong,
nameFactory: Name.Known.Factory, private val nameFactory: Name.Known.Factory,
separators: Separators private val separators: Separators
) : Song { ) : Song {
override val uid = override val uid =
// Attempt to use a MusicBrainz ID first before falling back to a hashed UID. // Attempt to use a MusicBrainz ID first before falling back to a hashed UID.
@ -110,17 +110,6 @@ class SongImpl(
override val genres: List<Genre> override val genres: List<Genre>
get() = _genres get() = _genres
private val hashCode = 31 * uid.hashCode() + rawSong.hashCode()
override fun hashCode() = hashCode
// TODO: I cant compare by raw information actually, as it also means that any settings
// configuration will be lost as well.
override fun equals(other: Any?) =
other is SongImpl && uid == other.uid && rawSong == other.rawSong
override fun toString() = "Song(uid=$uid, name=$name)"
/** /**
* The [RawAlbum] instances collated by the [Song]. This can be used to group [Song]s into an * The [RawAlbum] instances collated by the [Song]. This can be used to group [Song]s into an
* [Album]. * [Album].
@ -140,6 +129,8 @@ class SongImpl(
*/ */
val rawGenres: List<RawGenre> val rawGenres: List<RawGenre>
private var hashCode: Int = uid.hashCode()
init { init {
val artistMusicBrainzIds = separators.split(rawSong.artistMusicBrainzIds) val artistMusicBrainzIds = separators.split(rawSong.artistMusicBrainzIds)
val artistNames = separators.split(rawSong.artistNames) val artistNames = separators.split(rawSong.artistNames)
@ -190,8 +181,24 @@ class SongImpl(
.mapTo(mutableSetOf()) { RawGenre(it) } .mapTo(mutableSetOf()) { RawGenre(it) }
.toList() .toList()
.ifEmpty { listOf(RawGenre()) } .ifEmpty { listOf(RawGenre()) }
hashCode = 31 * rawSong.hashCode()
hashCode = 31 * nameFactory.hashCode()
} }
override fun hashCode() = hashCode
// Since equality on public-facing music models is not identical to the tag equality,
// we just compare raw instances and how they are interpreted.
override fun equals(other: Any?) =
other is SongImpl &&
uid == other.uid &&
nameFactory == other.nameFactory &&
separators == other.separators &&
rawSong == other.rawSong
override fun toString() = "Song(uid=$uid, name=$name)"
/** /**
* Links this [Song] with a parent [Album]. * Links this [Song] with a parent [Album].
* *
@ -259,7 +266,10 @@ class SongImpl(
* @param nameFactory The [Name.Known.Factory] to interpret name information with. * @param nameFactory The [Name.Known.Factory] to interpret name information with.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class AlbumImpl(grouping: Grouping<RawAlbum, SongImpl>, nameFactory: Name.Known.Factory) : Album { class AlbumImpl(
grouping: Grouping<RawAlbum, SongImpl>,
private val nameFactory: Name.Known.Factory
) : Album {
private val rawAlbum = grouping.raw.inner private val rawAlbum = grouping.raw.inner
override val uid = override val uid =
@ -322,13 +332,20 @@ class AlbumImpl(grouping: Grouping<RawAlbum, SongImpl>, nameFactory: Name.Known.
dateAdded = earliestDateAdded dateAdded = earliestDateAdded
hashCode = 31 * hashCode + rawAlbum.hashCode() hashCode = 31 * hashCode + rawAlbum.hashCode()
hashCode = 31 * nameFactory.hashCode()
hashCode = 31 * hashCode + songs.hashCode() hashCode = 31 * hashCode + songs.hashCode()
} }
override fun hashCode() = hashCode override fun hashCode() = hashCode
// Since equality on public-facing music models is not identical to the tag equality,
// we just compare raw instances and how they are interpreted.
override fun equals(other: Any?) = override fun equals(other: Any?) =
other is AlbumImpl && uid == other.uid && rawAlbum == other.rawAlbum && songs == other.songs other is AlbumImpl &&
uid == other.uid &&
rawAlbum == other.rawAlbum &&
nameFactory == other.nameFactory &&
songs == other.songs
override fun toString() = "Album(uid=$uid, name=$name)" override fun toString() = "Album(uid=$uid, name=$name)"
@ -376,7 +393,10 @@ class AlbumImpl(grouping: Grouping<RawAlbum, SongImpl>, nameFactory: Name.Known.
* @param nameFactory The [Name.Known.Factory] to interpret name information with. * @param nameFactory The [Name.Known.Factory] to interpret name information with.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class ArtistImpl(grouping: Grouping<RawArtist, Music>, nameFactory: Name.Known.Factory) : Artist { class ArtistImpl(
grouping: Grouping<RawArtist, Music>,
private val nameFactory: Name.Known.Factory
) : Artist {
private val rawArtist = grouping.raw.inner private val rawArtist = grouping.raw.inner
override val uid = override val uid =
@ -425,6 +445,7 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, nameFactory: Name.Known.F
durationMs = songs.sumOf { it.durationMs }.positiveOrNull() durationMs = songs.sumOf { it.durationMs }.positiveOrNull()
hashCode = 31 * hashCode + rawArtist.hashCode() hashCode = 31 * hashCode + rawArtist.hashCode()
hashCode = 31 * hashCode + nameFactory.hashCode()
hashCode = 31 * hashCode + songs.hashCode() hashCode = 31 * hashCode + songs.hashCode()
} }
@ -432,10 +453,13 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, nameFactory: Name.Known.F
// the same UID but different songs are not equal. // the same UID but different songs are not equal.
override fun hashCode() = hashCode override fun hashCode() = hashCode
// Since equality on public-facing music models is not identical to the tag equality,
// we just compare raw instances and how they are interpreted.
override fun equals(other: Any?) = override fun equals(other: Any?) =
other is ArtistImpl && other is ArtistImpl &&
uid == other.uid && uid == other.uid &&
rawArtist == other.rawArtist && rawArtist == other.rawArtist &&
nameFactory == other.nameFactory &&
songs == other.songs songs == other.songs
override fun toString() = "Artist(uid=$uid, name=$name)" override fun toString() = "Artist(uid=$uid, name=$name)"
@ -473,7 +497,10 @@ class ArtistImpl(grouping: Grouping<RawArtist, Music>, nameFactory: Name.Known.F
* @param nameFactory The [Name.Known.Factory] to interpret name information with. * @param nameFactory The [Name.Known.Factory] to interpret name information with.
* @author Alexander Capehart (OxygenCobalt) * @author Alexander Capehart (OxygenCobalt)
*/ */
class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, nameFactory: Name.Known.Factory) : Genre { class GenreImpl(
grouping: Grouping<RawGenre, SongImpl>,
private val nameFactory: Name.Known.Factory
) : Genre {
private val rawGenre = grouping.raw.inner private val rawGenre = grouping.raw.inner
override val uid = Music.UID.auxio(MusicType.GENRES) { update(rawGenre.name) } override val uid = Music.UID.auxio(MusicType.GENRES) { update(rawGenre.name) }
@ -502,13 +529,18 @@ class GenreImpl(grouping: Grouping<RawGenre, SongImpl>, nameFactory: Name.Known.
durationMs = totalDuration durationMs = totalDuration
hashCode = 31 * hashCode + rawGenre.hashCode() hashCode = 31 * hashCode + rawGenre.hashCode()
hashCode = 31 * nameFactory.hashCode()
hashCode = 31 * hashCode + songs.hashCode() hashCode = 31 * hashCode + songs.hashCode()
} }
override fun hashCode() = hashCode override fun hashCode() = hashCode
override fun equals(other: Any?) = override fun equals(other: Any?) =
other is GenreImpl && uid == other.uid && rawGenre == other.rawGenre && songs == other.songs other is GenreImpl &&
uid == other.uid &&
rawGenre == other.rawGenre &&
nameFactory == other.nameFactory &&
songs == other.songs
override fun toString() = "Genre(uid=$uid, name=$name)" override fun toString() = "Genre(uid=$uid, name=$name)"

View file

@ -95,13 +95,13 @@ sealed interface Name : Comparable<Name> {
* user-defined name configuration. * user-defined name configuration.
* *
* @param settings The [MusicSettings] to use. * @param settings The [MusicSettings] to use.
* @return A new [Factory] instance reflecting the configuration state. * @return A [Factory] instance reflecting the configuration state.
*/ */
fun from(settings: MusicSettings) = fun from(settings: MusicSettings) =
if (settings.intelligentSorting) { if (settings.intelligentSorting) {
IntelligentKnownName.Factory() IntelligentKnownName.Factory
} else { } else {
SimpleKnownName.Factory() SimpleKnownName.Factory
} }
} }
} }
@ -149,7 +149,7 @@ data class SimpleKnownName(override val raw: String, override val sort: String?)
return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC) return SortToken(collationKey, SortToken.Type.LEXICOGRAPHIC)
} }
class Factory : Name.Known.Factory { data object Factory : Name.Known.Factory {
override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort) override fun parse(raw: String, sort: String?) = SimpleKnownName(raw, sort)
} }
} }
@ -208,7 +208,7 @@ data class IntelligentKnownName(override val raw: String, override val sort: Str
} }
} }
class Factory : Name.Known.Factory { data object Factory : Name.Known.Factory {
override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort) override fun parse(raw: String, sort: String?) = IntelligentKnownName(raw, sort)
} }