Skip to content

Commit

Permalink
feat: load image and wikipedia extract asynchronously after loading a…
Browse files Browse the repository at this point in the history
…rtist to reduce time spent looking at a fullscreen spinner (#1072)

closes #1063
  • Loading branch information
lydavid authored Aug 21, 2024
1 parent 864776e commit 363fe50
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import ly.david.musicsearch.data.database.Database
import ly.david.musicsearch.data.musicbrainz.models.core.ArtistMusicBrainzModel
import ly.david.musicsearch.shared.domain.LifeSpanUiModel
import ly.david.musicsearch.shared.domain.artist.ArtistDetailsModel
import ly.david.musicsearch.shared.domain.wikimedia.WikipediaExtract
import lydavidmusicsearchdatadatabase.Artist

class ArtistDao(
Expand Down Expand Up @@ -62,9 +61,6 @@ class ArtistDao(
begin: String?,
end: String?,
ended: Boolean?,
largeUrl: String?,
extract: String?,
wikipediaUrl: String?,
) = ArtistDetailsModel(
id = id,
name = name,
Expand All @@ -78,14 +74,5 @@ class ArtistDao(
end = end,
ended = ended,
),
imageUrl = largeUrl,
wikipediaExtract = extract?.run {
wikipediaUrl?.run {
WikipediaExtract(
extract = extract,
wikipediaUrl = wikipediaUrl,
)
}
},
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ class MbidWikipediaDaoImpl(
}
}

override fun get(mbid: String): WikipediaExtract? =
transacter.get(
mbid = mbid,
mapper = { _: String, extract: String, url: String ->
WikipediaExtract(
extract = extract,
wikipediaUrl = url,
)
},
).executeAsOneOrNull()

override fun deleteById(mbid: String) {
transacter.deleteById(mbid)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,8 @@ SELECT
a.country_code,
a.begin,
a.end,
a.ended,
mi.large_url,
mw.extract,
mw.url
a.ended
FROM artist a
LEFT JOIN mbid_image mi ON mi.mbid = a.id
LEFT JOIN mbid_wikipedia mw ON mw.mbid = a.id
WHERE a.id = :artistId
GROUP BY a.id;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ insert:
INSERT OR IGNORE INTO mbid_wikipedia
VALUES ?;

get:
SELECT * FROM mbid_wikipedia
WHERE mbid = :mbid
LIMIT 1;

deleteById:
DELETE FROM mbid_wikipedia
WHERE mbid = :mbid;
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,13 @@ import ly.david.musicsearch.data.musicbrainz.api.MusicBrainzApi
import ly.david.musicsearch.data.musicbrainz.models.core.ArtistMusicBrainzModel
import ly.david.musicsearch.data.repository.internal.toRelationWithOrderList
import ly.david.musicsearch.shared.domain.artist.ArtistDetailsModel
import ly.david.musicsearch.shared.domain.artist.ArtistImageRepository
import ly.david.musicsearch.shared.domain.artist.ArtistRepository
import ly.david.musicsearch.shared.domain.relation.RelationRepository
import ly.david.musicsearch.shared.domain.wikimedia.WikimediaRepository
import ly.david.musicsearch.shared.domain.wikimedia.WikipediaExtract

class ArtistRepositoryImpl(
private val musicBrainzApi: MusicBrainzApi,
private val artistDao: ArtistDao,
private val relationRepository: RelationRepository,
private val artistImageRepository: ArtistImageRepository,
private val wikimediaRepository: WikimediaRepository,
) : ArtistRepository {

override suspend fun lookupArtistDetails(
Expand All @@ -25,8 +20,6 @@ class ArtistRepositoryImpl(
): ArtistDetailsModel {
if (forceRefresh) {
relationRepository.deleteUrlRelationshipsByEntity(artistId)
artistImageRepository.deleteImage(artistId)
wikimediaRepository.deleteWikipediaExtract(artistId)
artistDao.delete(artistId)
}

Expand All @@ -42,10 +35,7 @@ class ArtistRepositoryImpl(
val artistWithUrls = artistDetailsModel.copy(
urls = urlRelations,
)
return artistWithUrls.copy(
imageUrl = fetchArtistImage(artistWithUrls),
wikipediaExtract = fetchWikipediaExtract(artistWithUrls),
)
return artistWithUrls
}

val artistMusicBrainzModel = musicBrainzApi.lookupArtist(artistId)
Expand All @@ -56,39 +46,6 @@ class ArtistRepositoryImpl(
)
}

private suspend fun fetchArtistImage(
artist: ArtistDetailsModel,
): String {
val imageUrl = artist.imageUrl
return if (imageUrl == null) {
val spotifyUrl =
artist.urls.firstOrNull { it.name.contains("open.spotify.com/artist/") }?.name ?: return ""
artistImageRepository.getArtistImageFromNetwork(
artistMbid = artist.id,
spotifyUrl = spotifyUrl,
)
} else {
imageUrl
}
}

private suspend fun fetchWikipediaExtract(
artist: ArtistDetailsModel,
): WikipediaExtract {
val wikipediaExtract = artist.wikipediaExtract
return if (wikipediaExtract == null) {
val wikidataUrl =
artist.urls.firstOrNull { it.name.contains("www.wikidata.org/wiki/") }?.name
?: return WikipediaExtract()
wikimediaRepository.getWikipediaExtractFromNetwork(
mbid = artist.id,
wikidataUrl = wikidataUrl,
)
} else {
wikipediaExtract
}
}

private fun cache(artist: ArtistMusicBrainzModel) {
artistDao.withTransaction {
artistDao.insert(artist)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,15 @@ package ly.david.musicsearch.data.spotify

import io.ktor.client.plugins.ClientRequestException
import ly.david.musicsearch.core.logging.Logger
import ly.david.musicsearch.shared.domain.image.ImageUrlDao
import ly.david.musicsearch.shared.domain.image.ImageUrls
import ly.david.musicsearch.data.spotify.api.SpotifyApi
import ly.david.musicsearch.data.spotify.api.SpotifyArtist
import ly.david.musicsearch.data.spotify.api.getLargeImageUrl
import ly.david.musicsearch.data.spotify.api.getThumbnailImageUrl
import ly.david.musicsearch.shared.domain.artist.ArtistDetailsModel
import ly.david.musicsearch.shared.domain.artist.ArtistImageRepository
import ly.david.musicsearch.shared.domain.image.ImageUrlDao
import ly.david.musicsearch.shared.domain.image.ImageUrls

/**
* Logic to retrieve release cover art path.
*/
class ArtistImageRepositoryImpl(
private val spotifyApi: SpotifyApi,
private val imageUrlDao: ImageUrlDao,
Expand All @@ -24,24 +23,36 @@ class ArtistImageRepositoryImpl(
*
* Also saves it to db.
*/
override suspend fun getArtistImageFromNetwork(
artistMbid: String,
spotifyUrl: String,
override suspend fun getArtistImageUrl(
artistDetailsModel: ArtistDetailsModel,
forceRefresh: Boolean,
): String {
if (forceRefresh) {
imageUrlDao.deleteAllUrlsById(artistDetailsModel.id)
}

val cachedImageUrls = imageUrlDao.getAllUrls(artistDetailsModel.id)
return if (cachedImageUrls.isNotEmpty()) {
return cachedImageUrls.first().largeUrl
} else {
getArtistImageUrlFromNetwork(artistDetailsModel)
}
}

private suspend fun getArtistImageUrlFromNetwork(
artistDetailsModel: ArtistDetailsModel,
): String {
return try {
val spotifyUrl =
artistDetailsModel.urls.firstOrNull { it.name.contains("open.spotify.com/artist/") }?.name
?: return ""
val spotifyArtistId = spotifyUrl.split("/").last()

val spotifyArtist = spotifyApi.getArtist(spotifyArtistId)
val thumbnailUrl = spotifyArtist.getThumbnailImageUrl()
val spotifyArtist: SpotifyArtist = spotifyApi.getArtist(spotifyArtistId)
val largeUrl = spotifyArtist.getLargeImageUrl()
imageUrlDao.saveUrls(
mbid = artistMbid,
imageUrls = listOf(
ImageUrls(
thumbnailUrl = thumbnailUrl,
largeUrl = largeUrl,
),
),
cache(
artistDetailsModel = artistDetailsModel,
spotifyArtist = spotifyArtist,
)
largeUrl
} catch (ex: ClientRequestException) {
Expand All @@ -50,9 +61,18 @@ class ArtistImageRepositoryImpl(
}
}

override fun deleteImage(
artistMbid: String,
private fun cache(
artistDetailsModel: ArtistDetailsModel,
spotifyArtist: SpotifyArtist,
) {
imageUrlDao.deleteAllUrlsById(artistMbid)
imageUrlDao.saveUrls(
mbid = artistDetailsModel.id,
imageUrls = listOf(
ImageUrls(
thumbnailUrl = spotifyArtist.getThumbnailImageUrl(),
largeUrl = spotifyArtist.getLargeImageUrl(),
),
),
)
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,59 @@
package ly.david.musicsearch.data.wikimedia

import io.ktor.client.plugins.ClientRequestException
import ly.david.musicsearch.core.logging.Logger
import ly.david.musicsearch.data.wikimedia.api.WikimediaApi
import ly.david.musicsearch.shared.domain.listitem.RelationListItemModel
import ly.david.musicsearch.shared.domain.wikimedia.MbidWikipediaDao
import ly.david.musicsearch.shared.domain.wikimedia.WikimediaRepository
import ly.david.musicsearch.shared.domain.wikimedia.WikipediaExtract

internal class WikimediaRepositoryImpl(
private val api: WikimediaApi,
private val wikimediaApi: WikimediaApi,
private val mbidWikipediaDao: MbidWikipediaDao,
private val logger: Logger,
) : WikimediaRepository {
override suspend fun getWikipediaExtractFromNetwork(

override suspend fun getWikipediaExtract(
mbid: String,
wikidataUrl: String,
urls: List<RelationListItemModel>,
forceRefresh: Boolean,
): WikipediaExtract {
val wikidataId = wikidataUrl.split("/").last()
val wikipediaExtract = api.getWikipediaExtract(wikidataId = wikidataId)
mbidWikipediaDao.save(mbid, wikipediaExtract)
return wikipediaExtract
if (forceRefresh) {
mbidWikipediaDao.deleteById(mbid)
}

return mbidWikipediaDao.get(mbid) ?: getWikipediaExtractFromNetwork(
mbid = mbid,
urls = urls,
)
}

override fun deleteWikipediaExtract(mbid: String) {
mbidWikipediaDao.deleteById(mbid)
private suspend fun getWikipediaExtractFromNetwork(
mbid: String,
urls: List<RelationListItemModel>,
): WikipediaExtract {
return try {
val wikidataUrl =
urls.firstOrNull { it.name.contains("www.wikidata.org/wiki/") }?.name
?: return WikipediaExtract()
val wikidataId = wikidataUrl.split("/").last()
val wikipediaExtract = wikimediaApi.getWikipediaExtract(wikidataId = wikidataId)
cache(mbid, wikipediaExtract)
wikipediaExtract
} catch (ex: ClientRequestException) {
logger.e(ex)
WikipediaExtract()
}
}

private fun cache(
mbid: String,
wikipediaExtract: WikipediaExtract,
) {
mbidWikipediaDao.save(
mbid = mbid,
wikipediaExtract = wikipediaExtract,
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ data class ArtistDetailsModel(
override val gender: String? = null,
override val countryCode: String? = null,
val lifeSpan: LifeSpanUiModel? = null,
val urls: List<RelationListItemModel> = listOf(),
val imageUrl: String? = null,
val wikipediaExtract: WikipediaExtract? = null,
val urls: List<RelationListItemModel> = listOf(),
) : Artist
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
package ly.david.musicsearch.shared.domain.artist

interface ArtistImageRepository {
suspend fun getArtistImageFromNetwork(
artistMbid: String,
spotifyUrl: String,
suspend fun getArtistImageUrl(
artistDetailsModel: ArtistDetailsModel,
forceRefresh: Boolean,
): String

fun deleteImage(
artistMbid: String,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@ interface MbidWikipediaDao {
wikipediaExtract: WikipediaExtract,
)

fun get(mbid: String): WikipediaExtract?

fun deleteById(mbid: String)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
package ly.david.musicsearch.shared.domain.wikimedia

import ly.david.musicsearch.shared.domain.listitem.RelationListItemModel

interface WikimediaRepository {

/**
* Given a [wikidataUrl] (e.g. https://www.wikidata.org/wiki/Q20019100),
* Given [urls] that may include a Wikidata url (e.g. https://www.wikidata.org/wiki/Q20019100),
* return a Wikipedia extract for display.
* Also, cache it for the given [mbid].
*
* If cached, we return this data together with the entity via SQL joins, rather than here.
*/
suspend fun getWikipediaExtractFromNetwork(
suspend fun getWikipediaExtract(
mbid: String,
wikidataUrl: String,
urls: List<RelationListItemModel>,
forceRefresh: Boolean,
): WikipediaExtract

/**
* Delete the cached Wikipedia extract for the given [mbid].
*/
fun deleteWikipediaExtract(
mbid: String,
)
}
Loading

0 comments on commit 363fe50

Please sign in to comment.