Skip to content

Commit

Permalink
feat: manually parse multi value tags (#93)
Browse files Browse the repository at this point in the history
  • Loading branch information
zyrouge committed Dec 27, 2023
1 parent 63e31fd commit 651e2c5
Show file tree
Hide file tree
Showing 48 changed files with 499 additions and 414 deletions.
10 changes: 7 additions & 3 deletions .phrasey/schema.toml
Original file line number Diff line number Diff line change
Expand Up @@ -461,9 +461,9 @@ parameters = ["x"]
name = "XSongs"
parameters = ["x"]

[[keys]]
name = "UnknownAlbumId"
parameters = ["id"]
# [[keys]]
# name = "UnknownAlbumId"
# parameters = ["id"]

[[keys]]
name = "XArtists"
Expand Down Expand Up @@ -603,3 +603,7 @@ name = "ReplaceArtwork"

[[keys]]
name = "SeparatePage"

[[keys]]
name = "UnknownAlbumX"
parameters = ["x"]
33 changes: 23 additions & 10 deletions app/src/main/java/io/github/zyrouge/symphony/services/Settings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ object SettingsKeys {
const val fontScale = "font_scale"
const val contentScale = "content_scale"
const val nowPlayingLyricsLayout = "now_playing_lyrics_layout"
const val tagSeparators = "tag_separators"
}

object SettingsDefaults {
Expand Down Expand Up @@ -131,6 +132,7 @@ object SettingsDefaults {
const val fontScale = 1.0f
const val contentScale = 1.0f
val nowPlayingLyricsLayout = NowPlayingLyricsLayout.ReplaceArtwork
val tagSeparators = setOf<String>(";", "/", ",", "+")
}

class SettingsManager(private val symphony: Symphony) {
Expand Down Expand Up @@ -243,6 +245,8 @@ class SettingsManager(private val symphony: Symphony) {
val contentScale = _contentScale.asStateFlow()
private val _nowPlayingLyricsLayout = MutableStateFlow(getNowPlayingLyricsLayout())
val nowPlayingLyricsLayout = _nowPlayingLyricsLayout.asStateFlow()
private val _tagSeparators = MutableStateFlow(getTagSeparators())
val tagSeparators = _tagSeparators.asStateFlow()

fun getThemeMode() = getSharedPreferences().getString(SettingsKeys.themeMode, null)
?.let { ThemeMode.valueOf(it) }
Expand Down Expand Up @@ -638,13 +642,13 @@ class SettingsManager(private val symphony: Symphony) {
?.toSet()
?: SettingsDefaults.homeTabs

fun setHomeTabs(tabs: Set<HomePages>) {
fun setHomeTabs(values: Set<HomePages>) {
getSharedPreferences().edit {
putString(SettingsKeys.homeTabs, tabs.joinToString(",") { it.name })
putString(SettingsKeys.homeTabs, values.joinToString(",") { it.name })
}
_homeTabs.updateUsingValue(getHomeTabs())
if (getHomeLastTab() !in tabs) {
setHomeLastTab(tabs.first())
if (getHomeLastTab() !in values) {
setHomeLastTab(values.first())
}
}

Expand All @@ -666,16 +670,15 @@ class SettingsManager(private val symphony: Symphony) {
?.toSet()
?: SettingsDefaults.forYouContents

fun setForYouContents(forYouContents: Set<ForYou>) {
fun setForYouContents(values: Set<ForYou>) {
getSharedPreferences().edit {
putString(SettingsKeys.forYouContents, forYouContents.joinToString(",") { it.name })
putString(SettingsKeys.forYouContents, values.joinToString(",") { it.name })
}
_forYouContents.updateUsingValue(getForYouContents())
}

fun getBlacklistFolders() = getSharedPreferences()
fun getBlacklistFolders(): Set<String> = getSharedPreferences()
.getStringSet(SettingsKeys.blacklistFolders, null)
?.toSet<String>()
?: SettingsDefaults.blacklistFolders

fun setBlacklistFolders(values: Set<String>) {
Expand All @@ -685,9 +688,8 @@ class SettingsManager(private val symphony: Symphony) {
_blacklistFolders.updateUsingValue(getBlacklistFolders())
}

fun getWhitelistFolders() = getSharedPreferences()
fun getWhitelistFolders(): Set<String> = getSharedPreferences()
.getStringSet(SettingsKeys.whitelistFolders, null)
?.toSet<String>()
?: SettingsDefaults.whitelistFolders

fun setWhitelistFolders(values: Set<String>) {
Expand Down Expand Up @@ -847,6 +849,17 @@ class SettingsManager(private val symphony: Symphony) {
_nowPlayingLyricsLayout.updateUsingValue(getNowPlayingLyricsLayout())
}

fun getTagSeparators(): Set<String> = getSharedPreferences()
.getStringSet(SettingsKeys.tagSeparators, null)
?: SettingsDefaults.tagSeparators

fun setTagSeparators(values: List<String>) {
getSharedPreferences().edit {
putStringSet(SettingsKeys.tagSeparators, values.toSet())
}
_tagSeparators.updateUsingValue(getTagSeparators())
}

private fun getSharedPreferences() = symphony.applicationContext.getSharedPreferences(
SettingsKeys.identifier,
Context.MODE_PRIVATE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class SongCache(val symphony: Symphony) {
}

companion object {
const val defaultSeparator = ";"

private const val LAST_MODIFIED = "0"
private const val ALBUM_ARTIST = "1"
private const val BITRATE = "2"
Expand All @@ -51,9 +53,9 @@ class SongCache(val symphony: Symphony) {

fun fromSong(song: Song) = Attributes(
lastModified = song.dateModified,
albumArtist = song.additional.albumArtist,
albumArtist = song.additional.albumArtists.joinToString(defaultSeparator),
bitrate = song.additional.bitrate,
genre = song.additional.genre,
genre = song.additional.genres.joinToString(defaultSeparator),
bitsPerSample = song.additional.bitsPerSample,
samplingRate = song.additional.samplingRate,
codec = song.additional.codec,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@ import io.github.zyrouge.symphony.Symphony

@Immutable
data class Album(
val id: Long,
val name: String,
val artist: String?,
val artists: MutableSet<String>,
var numberOfTracks: Int,
) {
fun createArtworkImageRequest(symphony: Symphony) =
symphony.groove.album.createArtworkImageRequest(id)
symphony.groove.album.createArtworkImageRequest(name)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ enum class AlbumArtistSortBy {
class AlbumArtistRepository(private val symphony: Symphony) {
private val cache = ConcurrentHashMap<String, AlbumArtist>()
private val songIdsCache = ConcurrentHashMap<String, ConcurrentSet<Long>>()
private val albumIdsCache = ConcurrentHashMap<String, ConcurrentSet<Long>>()
private val albumNamesCache = ConcurrentHashMap<String, ConcurrentSet<String>>()
private val searcher = FuzzySearcher<String>(
options = listOf(FuzzySearchOption({ get(it)?.name }))
options = listOf(FuzzySearchOption({ v -> get(v)?.name?.let { compareString(it) } }))
)

val isUpdating get() = symphony.groove.mediaStore.isUpdating
Expand All @@ -37,31 +37,34 @@ class AlbumArtistRepository(private val symphony: Symphony) {
}

internal fun onSong(song: Song) {
if (song.additional.albumArtist == null) return
songIdsCache.compute(song.additional.albumArtist) { _, value ->
value?.apply { add(song.id) }
?: ConcurrentSet(song.id)
}
var nNumberOfAlbums = 0
albumIdsCache.compute(song.additional.albumArtist) { _, value ->
nNumberOfAlbums = (value?.size ?: 0) + 1
value?.apply { add(song.albumId) }
?: ConcurrentSet(song.albumId)
}
cache.compute(song.additional.albumArtist) { _, value ->
value?.apply {
numberOfAlbums = nNumberOfAlbums
numberOfTracks++
} ?: run {
_all.update {
it + song.additional.albumArtist
song.additional.albumArtists.forEach { albumArtist ->
songIdsCache.compute(albumArtist) { _, value ->
value?.apply { add(song.id) }
?: ConcurrentSet(song.id)
}
var nNumberOfAlbums = 0
song.album?.let { album ->
albumNamesCache.compute(albumArtist) { _, value ->
nNumberOfAlbums = (value?.size ?: 0) + 1
value?.apply { add(album) }
?: ConcurrentSet(album)
}
}
cache.compute(albumArtist) { _, value ->
value?.apply {
numberOfAlbums = nNumberOfAlbums
numberOfTracks++
} ?: run {
_all.update {
it + albumArtist
}
emitCount()
AlbumArtist(
name = albumArtist,
numberOfAlbums = 1,
numberOfTracks = 1,
)
}
emitCount()
AlbumArtist(
name = song.additional.albumArtist,
numberOfAlbums = 1,
numberOfTracks = 1,
)
}
}
}
Expand All @@ -76,30 +79,29 @@ class AlbumArtistRepository(private val symphony: Symphony) {
emitCount()
}

fun getArtworkUri(artistName: String) =
albumIdsCache[artistName]?.firstOrNull()?.let {
symphony.groove.album.getArtworkUri(it)
} ?: symphony.groove.album.getDefaultArtworkUri()
fun getArtworkUri(artistName: String) = songIdsCache[artistName]?.firstOrNull()
?.let { symphony.groove.song.getArtworkUri(it) }
?: symphony.groove.album.getDefaultArtworkUri()

fun createArtworkImageRequest(artistName: String) = createHandyImageRequest(
symphony.applicationContext,
image = getArtworkUri(artistName),
fallback = Assets.placeholderId,
)

fun search(albumArtistIds: List<String>, terms: String, limit: Int = 7) = searcher
.search(terms, albumArtistIds, maxLength = limit)
fun search(albumArtistNames: List<String>, terms: String, limit: Int = 7) = searcher
.search(terms, albumArtistNames, maxLength = limit)

fun sort(
albumArtistIds: List<String>,
albumArtistNames: List<String>,
by: AlbumArtistSortBy,
reverse: Boolean,
): List<String> {
val sorted = when (by) {
AlbumArtistSortBy.CUSTOM -> albumArtistIds
AlbumArtistSortBy.ARTIST_NAME -> albumArtistIds.sortedBy { get(it)?.name }
AlbumArtistSortBy.TRACKS_COUNT -> albumArtistIds.sortedBy { get(it)?.numberOfTracks }
AlbumArtistSortBy.ALBUMS_COUNT -> albumArtistIds.sortedBy { get(it)?.numberOfTracks }
AlbumArtistSortBy.CUSTOM -> albumArtistNames
AlbumArtistSortBy.ARTIST_NAME -> albumArtistNames.sortedBy { get(it)?.name }
AlbumArtistSortBy.TRACKS_COUNT -> albumArtistNames.sortedBy { get(it)?.numberOfTracks }
AlbumArtistSortBy.ALBUMS_COUNT -> albumArtistNames.sortedBy { get(it)?.numberOfTracks }
}
return if (reverse) sorted.reversed() else sorted
}
Expand All @@ -110,6 +112,6 @@ class AlbumArtistRepository(private val symphony: Symphony) {

fun get(artistName: String) = cache[artistName]
fun get(artistNames: List<String>) = artistNames.mapNotNull { get(it) }
fun getAlbumIds(artistName: String) = albumIdsCache[artistName]?.toList() ?: emptyList()
fun getAlbumNames(artistName: String) = albumNamesCache[artistName]?.toList() ?: emptyList()
fun getSongIds(artistName: String) = songIdsCache[artistName]?.toList() ?: emptyList()
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package io.github.zyrouge.symphony.services.groove

import android.content.ContentUris
import android.provider.MediaStore
import io.github.zyrouge.symphony.Symphony
import io.github.zyrouge.symphony.ui.helpers.Assets
import io.github.zyrouge.symphony.ui.helpers.createHandyImageRequest
Expand All @@ -21,17 +19,17 @@ enum class AlbumSortBy {
}

class AlbumRepository(private val symphony: Symphony) {
private val cache = ConcurrentHashMap<Long, Album>()
private val songIdsCache = ConcurrentHashMap<Long, ConcurrentSet<Long>>()
private val searcher = FuzzySearcher<Long>(
private val cache = ConcurrentHashMap<String, Album>()
private val songIdsCache = ConcurrentHashMap<String, ConcurrentSet<Long>>()
private val searcher = FuzzySearcher<String>(
options = listOf(
FuzzySearchOption({ get(it)?.name }, 3),
FuzzySearchOption({ get(it)?.artist })
FuzzySearchOption({ v -> get(v)?.name?.let { compareString(it) } }, 3),
FuzzySearchOption({ v -> get(v)?.artists?.let { compareIterable(it) } })
)
)

val isUpdating get() = symphony.groove.mediaStore.isUpdating
private val _all = MutableStateFlow<List<Long>>(emptyList())
private val _all = MutableStateFlow<List<String>>(emptyList())
val all = _all.asStateFlow()
private val _count = MutableStateFlow(0)
val count = _count.asStateFlow()
Expand All @@ -41,22 +39,22 @@ class AlbumRepository(private val symphony: Symphony) {
}

internal fun onSong(song: Song) {
if (song.albumName == null || song.artistName == null) return
songIdsCache.compute(song.albumId) { _, value ->
if (song.album == null) return
songIdsCache.compute(song.album) { _, value ->
value?.apply { add(song.id) } ?: ConcurrentSet(song.id)
}
cache.compute(song.albumId) { _, value ->
cache.compute(song.album) { _, value ->
value?.apply {
artists.addAll(song.artists)
numberOfTracks++
} ?: run {
_all.update {
it + song.albumId
it + song.album
}
emitCount()
Album(
id = song.albumId,
name = song.albumName,
artist = song.artistName,
name = song.album,
artists = song.artists.toMutableSet(),
numberOfTracks = 1,
)
}
Expand All @@ -76,30 +74,29 @@ class AlbumRepository(private val symphony: Symphony) {

fun getDefaultArtworkUri() = Assets.getPlaceholderUri(symphony.applicationContext)

fun getArtworkUri(albumId: Long) = ContentUris.withAppendedId(
MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI,
albumId
)
fun getArtworkUri(albumName: String) = songIdsCache[albumName]?.firstOrNull()
?.let { symphony.groove.song.getArtworkUri(it) }
?: symphony.groove.album.getDefaultArtworkUri()

fun createArtworkImageRequest(albumId: Long) = createHandyImageRequest(
fun createArtworkImageRequest(albumName: String) = createHandyImageRequest(
symphony.applicationContext,
image = getArtworkUri(albumId),
image = getArtworkUri(albumName),
fallback = Assets.placeholderId,
)

fun search(albumIds: List<Long>, terms: String, limit: Int = 7) = searcher
.search(terms, albumIds, maxLength = limit)
fun search(albumNames: List<String>, terms: String, limit: Int = 7) = searcher
.search(terms, albumNames, maxLength = limit)

fun sort(
albumIds: List<Long>,
albumNames: List<String>,
by: AlbumSortBy,
reverse: Boolean
): List<Long> {
reverse: Boolean,
): List<String> {
val sorted = when (by) {
AlbumSortBy.CUSTOM -> albumIds
AlbumSortBy.ALBUM_NAME -> albumIds.sortedBy { get(it)?.name }
AlbumSortBy.ARTIST_NAME -> albumIds.sortedBy { get(it)?.artist }
AlbumSortBy.TRACKS_COUNT -> albumIds.sortedBy { get(it)?.numberOfTracks }
AlbumSortBy.CUSTOM -> albumNames
AlbumSortBy.ALBUM_NAME -> albumNames.sortedBy { get(it)?.name }
AlbumSortBy.ARTIST_NAME -> albumNames.sortedBy { get(it)?.artists?.firstOrNull() }
AlbumSortBy.TRACKS_COUNT -> albumNames.sortedBy { get(it)?.numberOfTracks }
}
return if (reverse) sorted.reversed() else sorted
}
Expand All @@ -108,7 +105,7 @@ class AlbumRepository(private val symphony: Symphony) {
fun ids() = cache.keys.toList()
fun values() = cache.values.toList()

fun get(id: Long) = cache[id]
fun get(ids: List<Long>) = ids.mapNotNull { get(it) }.toList()
fun getSongIds(albumId: Long) = songIdsCache[albumId]?.toList() ?: emptyList()
fun get(albumName: String) = cache[albumName]
fun get(albumNames: List<String>) = albumNames.mapNotNull { get(it) }.toList()
fun getSongIds(albumName: String) = songIdsCache[albumName]?.toList() ?: emptyList()
}
Loading

0 comments on commit 651e2c5

Please sign in to comment.