music: cache hashcode in data

Cache the hashcode of song/album/artist/genre information so that it
can be calculated easily later.
This commit is contained in:
Alexander Capehart 2023-06-01 20:34:21 -06:00
parent 0d28bdf99e
commit df174e22f6
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 63 additions and 63 deletions

View file

@ -170,19 +170,21 @@ private class DeviceLibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings
}
private fun buildSongs(rawSongs: List<RawSong>, settings: MusicSettings): List<SongImpl> {
val start = System.currentTimeMillis()
val songs =
Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING)
.songs(rawSongs.map { SongImpl(it, settings) }.distinctBy { it.uid })
logD("Successfully built ${songs.size} songs")
logD("Successfully built ${songs.size} songs in ${System.currentTimeMillis() - start}ms")
return songs
}
private fun buildAlbums(songs: List<SongImpl>, settings: MusicSettings): List<AlbumImpl> {
val start = System.currentTimeMillis()
// Group songs by their singular raw album, then map the raw instances and their
// grouped songs to Album values. Album.Raw will handle the actual grouping rules.
val songsByAlbum = songs.groupBy { it.rawAlbum.key }
val albums = songsByAlbum.map { AlbumImpl(it.key.value, settings, it.value) }
logD("Successfully built ${albums.size} albums")
logD("Successfully built ${albums.size} albums in ${System.currentTimeMillis() - start}ms")
return albums
}
@ -191,6 +193,7 @@ private class DeviceLibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings
albums: List<AlbumImpl>,
settings: MusicSettings
): List<ArtistImpl> {
val start = System.currentTimeMillis()
// Add every raw artist credited to each Song/Album to the grouping. This way,
// different multi-artist combinations are not treated as different artists.
// Songs and albums are grouped by artist and album artist respectively.
@ -210,11 +213,13 @@ private class DeviceLibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings
// Convert the combined mapping into artist instances.
val artists = musicByArtist.map { ArtistImpl(it.key.value, settings, it.value) }
logD("Successfully built ${artists.size} artists")
logD(
"Successfully built ${artists.size} artists in ${System.currentTimeMillis() - start}ms")
return artists
}
private fun buildGenres(songs: List<SongImpl>, settings: MusicSettings): List<GenreImpl> {
val start = System.currentTimeMillis()
// Add every raw genre credited to each Song to the grouping. This way,
// different multi-genre combinations are not treated as different genres.
val songsByGenre = mutableMapOf<RawGenre.Key, MutableList<SongImpl>>()
@ -226,7 +231,7 @@ private class DeviceLibraryImpl(rawSongs: List<RawSong>, settings: MusicSettings
// Convert the mapping into genre instances.
val genres = songsByGenre.map { GenreImpl(it.key.value, settings, it.value) }
logD("Successfully built ${genres.size} genres")
logD("Successfully built ${genres.size} genres in ${System.currentTimeMillis() - start}ms")
return genres
}
}

View file

@ -93,7 +93,9 @@ class SongImpl(private val rawSong: RawSong, musicSettings: MusicSettings) : Son
override val album: Album
get() = unlikelyToBeNull(_album)
override fun hashCode() = 31 * uid.hashCode() + rawSong.hashCode()
private val hashCode = 31 * uid.hashCode() + rawSong.hashCode()
override fun hashCode() = hashCode
override fun equals(other: Any?) =
other is SongImpl && uid == other.uid && rawSong == other.rawSong
override fun toString() = "Song(uid=$uid, name=$name)"
@ -253,22 +255,12 @@ class AlbumImpl(
override val durationMs: Long
override val dateAdded: Long
override fun hashCode(): Int {
var hashCode = uid.hashCode()
hashCode = 31 * hashCode + rawAlbum.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
return hashCode
}
override fun equals(other: Any?) =
other is AlbumImpl && uid == other.uid && rawAlbum == other.rawAlbum && songs == other.songs
override fun toString() = "Album(uid=$uid, name=$name)"
private val _artists = mutableListOf<ArtistImpl>()
override val artists: List<Artist>
get() = _artists
private var hashCode = uid.hashCode()
init {
var totalDuration: Long = 0
var earliestDateAdded: Long = Long.MAX_VALUE
@ -284,8 +276,16 @@ class AlbumImpl(
durationMs = totalDuration
dateAdded = earliestDateAdded
hashCode = 31 * hashCode + rawAlbum.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
}
override fun hashCode() = hashCode
override fun equals(other: Any?) =
other is AlbumImpl && uid == other.uid && rawAlbum == other.rawAlbum && songs == other.songs
override fun toString() = "Album(uid=$uid, name=$name)"
/**
* The [RawArtist] instances collated by the [Album]. The album artists of the song take
* priority, followed by the artists. If there are no artists, this field will be a single
@ -351,25 +351,10 @@ class ArtistImpl(
override val implicitAlbums: List<Album>
override val durationMs: Long?
// Note: Append song contents to MusicParent equality so that artists with
// the same UID but different songs are not equal.
override fun hashCode(): Int {
var hashCode = uid.hashCode()
hashCode = 31 * hashCode + rawArtist.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
return hashCode
}
override fun equals(other: Any?) =
other is ArtistImpl &&
uid == other.uid &&
rawArtist == other.rawArtist &&
songs == other.songs
override fun toString() = "Artist(uid=$uid, name=$name)"
override lateinit var genres: List<Genre>
private var hashCode = uid.hashCode()
init {
val distinctSongs = mutableSetOf<Song>()
val albumMap = mutableMapOf<Album, Boolean>()
@ -396,8 +381,23 @@ class ArtistImpl(
explicitAlbums = albums.filter { unlikelyToBeNull(albumMap[it]) }
implicitAlbums = albums.filterNot { unlikelyToBeNull(albumMap[it]) }
durationMs = songs.sumOf { it.durationMs }.nonZeroOrNull()
hashCode = 31 * hashCode + rawArtist.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
}
// Note: Append song contents to MusicParent equality so that artists with
// the same UID but different songs are not equal.
override fun hashCode() = hashCode
override fun equals(other: Any?) =
other is ArtistImpl &&
uid == other.uid &&
rawArtist == other.rawArtist &&
songs == other.songs
override fun toString() = "Artist(uid=$uid, name=$name)"
/**
* Returns the original position of this [Artist]'s [RawArtist] within the given [RawArtist]
* list. This can be used to create a consistent ordering within child [Artist] lists based on
@ -445,17 +445,7 @@ class GenreImpl(
override val artists: List<Artist>
override val durationMs: Long
override fun hashCode(): Int {
var hashCode = uid.hashCode()
hashCode = 31 * hashCode + rawGenre.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
return hashCode
}
override fun equals(other: Any?) =
other is GenreImpl && uid == other.uid && rawGenre == other.rawGenre && songs == other.songs
override fun toString() = "Genre(uid=$uid, name=$name)"
private var hashCode = uid.hashCode()
init {
val distinctAlbums = mutableSetOf<Album>()
@ -471,8 +461,17 @@ class GenreImpl(
artists = Sort(Sort.Mode.ByName, Sort.Direction.ASCENDING).artists(distinctArtists)
durationMs = totalDuration
hashCode = 31 * hashCode + rawGenre.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
}
override fun hashCode() = hashCode
override fun equals(other: Any?) =
other is GenreImpl && uid == other.uid && rawGenre == other.rawGenre && songs == other.songs
override fun toString() = "Genre(uid=$uid, name=$name)"
/**
* Returns the original position of this [Genre]'s [RawGenre] within the given [RawGenre] list.
* This can be used to create a consistent ordering within child [Genre] lists based on the

View file

@ -186,8 +186,8 @@ private abstract class BaseMediaStoreExtractor(
}
}
}
logD("Read ${genreNamesMap.size} genres from MediaStore")
logD("Read ${genreNamesMap.values.distinct().size} genres from MediaStore")
logD("Finished initialization in ${System.currentTimeMillis() - start}ms")
return wrapQuery(cursor, genreNamesMap)
}

View file

@ -73,10 +73,9 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
return format.format(date)
}
override fun hashCode() = tokens.hashCode()
override fun equals(other: Any?) = other is Date && compareTo(other) == 0
override fun hashCode() = tokens.hashCode()
override fun toString() = StringBuilder().appendDate().toString()
override fun compareTo(other: Date): Int {
for (i in 0 until max(tokens.size, other.tokens.size)) {
val ai = tokens.getOrNull(i)
@ -97,8 +96,6 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
return 0
}
override fun toString() = StringBuilder().appendDate().toString()
private fun StringBuilder.appendDate(): StringBuilder {
// Construct an ISO-8601 date, dropping precision that doesn't exist.
append(year.toStringFixed(4))

View file

@ -33,6 +33,17 @@ private constructor(
override val songs: List<Song>
) : Playlist {
override val durationMs = songs.sumOf { it.durationMs }
private var hashCode = uid.hashCode()
init {
hashCode = 31 * hashCode + name.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
}
override fun equals(other: Any?) =
other is PlaylistImpl && uid == other.uid && name == other.name && songs == other.songs
override fun hashCode() = hashCode
override fun toString() = "Playlist(uid=$uid, name=$name)"
/**
* Clone the data in this instance to a new [PlaylistImpl] with the given [name].
@ -57,18 +68,6 @@ private constructor(
*/
inline fun edit(edits: MutableList<Song>.() -> Unit) = edit(songs.toMutableList().apply(edits))
override fun equals(other: Any?) =
other is PlaylistImpl && uid == other.uid && name == other.name && songs == other.songs
override fun hashCode(): Int {
var hashCode = uid.hashCode()
hashCode = 31 * hashCode + name.hashCode()
hashCode = 31 * hashCode + songs.hashCode()
return hashCode
}
override fun toString() = "Playlist(uid=$uid, name=$name)"
companion object {
/**
* Create a new instance with a novel UID.