Skip to content

Commit

Permalink
feat: support refreshing series
Browse files Browse the repository at this point in the history
  • Loading branch information
lydavid committed Oct 13, 2024
1 parent 3388723 commit 6e003c1
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,8 @@ class SeriesDao(
disambiguation = disambiguation,
type = type,
)

fun delete(id: String) {
transacter.delete(id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,7 @@ SELECT
disambiguation,
type
FROM series WHERE id = ?;

delete:
DELETE FROM series
WHERE id = :id;
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
package ly.david.musicsearch.data.repository.series

import ly.david.musicsearch.data.musicbrainz.models.core.SeriesMusicBrainzModel
import ly.david.musicsearch.data.musicbrainz.api.MusicBrainzApi
import ly.david.musicsearch.shared.domain.series.SeriesDetailsModel
import ly.david.musicsearch.data.database.dao.SeriesDao
import ly.david.musicsearch.data.musicbrainz.api.LookupApi
import ly.david.musicsearch.data.musicbrainz.models.core.SeriesMusicBrainzModel
import ly.david.musicsearch.data.repository.internal.toRelationWithOrderList
import ly.david.musicsearch.shared.domain.relation.RelationRepository
import ly.david.musicsearch.shared.domain.series.SeriesDetailsModel
import ly.david.musicsearch.shared.domain.series.SeriesRepository

class SeriesRepositoryImpl(
private val musicBrainzApi: MusicBrainzApi,
private val seriesDao: SeriesDao,
private val relationRepository: RelationRepository,
private val lookupApi: LookupApi,
) : SeriesRepository {

override suspend fun lookupSeries(seriesId: String): SeriesDetailsModel {
override suspend fun lookupSeries(
seriesId: String,
forceRefresh: Boolean,
): SeriesDetailsModel {
if (forceRefresh) {
delete(seriesId)
}

val series = seriesDao.getSeriesForDetails(seriesId)
val urlRelations = relationRepository.getEntityUrlRelationships(seriesId)
val hasUrlsBeenSavedForEntity = relationRepository.hasUrlsBeenSavedFor(seriesId)
if (series != null && hasUrlsBeenSavedForEntity) {
if (series != null &&
hasUrlsBeenSavedForEntity &&
!forceRefresh
) {
return series.copy(
urls = urlRelations,
)
}

val seriesMusicBrainzModel = musicBrainzApi.lookupSeries(seriesId)
val seriesMusicBrainzModel = lookupApi.lookupSeries(seriesId)
cache(seriesMusicBrainzModel)
return lookupSeries(seriesId)
return lookupSeries(seriesId, false)
}

private fun delete(id: String) {
seriesDao.withTransaction {
seriesDao.delete(id)
relationRepository.deleteUrlRelationshipsByEntity(id)
}
}

private fun cache(series: SeriesMusicBrainzModel) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package ly.david.musicsearch.data.repository.series

import kotlinx.coroutines.test.runTest
import ly.david.data.test.api.FakeLookupApi
import ly.david.musicsearch.data.database.dao.EntityHasRelationsDao
import ly.david.musicsearch.data.database.dao.EntityHasUrlsDao
import ly.david.musicsearch.data.database.dao.RelationDao
import ly.david.musicsearch.data.database.dao.SeriesDao
import ly.david.musicsearch.data.musicbrainz.models.UrlMusicBrainzModel
import ly.david.musicsearch.data.musicbrainz.models.core.SeriesMusicBrainzModel
import ly.david.musicsearch.data.musicbrainz.models.relation.Direction
import ly.david.musicsearch.data.musicbrainz.models.relation.RelationMusicBrainzModel
import ly.david.musicsearch.data.musicbrainz.models.relation.SerializableMusicBrainzEntity
import ly.david.musicsearch.data.repository.KoinTestRule
import ly.david.musicsearch.data.repository.RelationRepositoryImpl
import ly.david.musicsearch.shared.domain.listitem.RelationListItemModel
import ly.david.musicsearch.shared.domain.network.MusicBrainzEntity
import ly.david.musicsearch.shared.domain.series.SeriesDetailsModel
import org.junit.Assert.assertEquals
import org.junit.Rule
import org.junit.Test
import org.koin.test.KoinTest
import org.koin.test.inject

class SeriesRepositoryImplTest : KoinTest {

@get:Rule(order = 0)
val koinTestRule = KoinTestRule()

private val entityHasRelationsDao: EntityHasRelationsDao by inject()
private val entityHasUrlsDao: EntityHasUrlsDao by inject()
private val relationDao: RelationDao by inject()
private val seriesDao: SeriesDao by inject()

private fun createRepositoryWithFakeNetworkData(
musicBrainzModel: SeriesMusicBrainzModel,
): SeriesRepositoryImpl {
val relationRepository = RelationRepositoryImpl(
lookupApi = object : FakeLookupApi() {
override suspend fun lookupSeries(
seriesId: String,
include: String?,
): SeriesMusicBrainzModel {
return musicBrainzModel
}
},
entityHasRelationsDao = entityHasRelationsDao,
entityHasUrlsDao = entityHasUrlsDao,
relationDao = relationDao,
)
return SeriesRepositoryImpl(
seriesDao = seriesDao,
relationRepository = relationRepository,
lookupApi = object : FakeLookupApi() {
override suspend fun lookupSeries(
seriesId: String,
include: String?,
): SeriesMusicBrainzModel {
return musicBrainzModel
}
},
)
}

@Test
fun `lookup is cached, and force refresh invalidates cache`() = runTest {
val sparseRepository = createRepositoryWithFakeNetworkData(
musicBrainzModel = SeriesMusicBrainzModel(
id = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
name = "Rolling Stone: 500 Greatest Albums of All Time: 2023 edition",
),
)
val sparseDetailsModel = sparseRepository.lookupSeries(
seriesId = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
forceRefresh = false,
)
assertEquals(
SeriesDetailsModel(
id = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
name = "Rolling Stone: 500 Greatest Albums of All Time: 2023 edition",
),
sparseDetailsModel,
)

val allDataRepository = createRepositoryWithFakeNetworkData(
musicBrainzModel = SeriesMusicBrainzModel(
id = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
name = "Rolling Stone: 500 Greatest Albums of All Time: 2023 edition",
type = "Release group series",
relations = listOf(
RelationMusicBrainzModel(
type = "official homepage",
typeId = "b79eb9a5-46df-492d-b107-1f1fea71b0eb",
direction = Direction.FORWARD,
targetType = SerializableMusicBrainzEntity.URL,
url = UrlMusicBrainzModel(
id = "e4a5db48-cae3-404f-921d-0f1c3947f874",
resource = "https://www.rollingstone.com/music/music-lists/best-albums-of-all-time-1062063/",
),
),
RelationMusicBrainzModel(
type = "wikidata",
typeId = "a1eecd98-f2f2-420b-ba8e-e5bc61697869",
direction = Direction.FORWARD,
targetType = SerializableMusicBrainzEntity.URL,
url = UrlMusicBrainzModel(
id = "61036cd9-8819-4f56-8739-d7f9bd16d675",
resource = "https://www.wikidata.org/wiki/Q240550",
),
),
),
),
)
var allDataArtistDetailsModel = allDataRepository.lookupSeries(
seriesId = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
forceRefresh = false,
)
assertEquals(
SeriesDetailsModel(
id = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
name = "Rolling Stone: 500 Greatest Albums of All Time: 2023 edition",
),
allDataArtistDetailsModel,
)
allDataArtistDetailsModel = allDataRepository.lookupSeries(
seriesId = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
forceRefresh = true,
)
assertEquals(
SeriesDetailsModel(
id = "bb3d9d84-75b8-4e67-8ad7-dcc38f764bf3",
name = "Rolling Stone: 500 Greatest Albums of All Time: 2023 edition",
type = "Release group series",
urls = listOf(
RelationListItemModel(
id = "61036cd9-8819-4f56-8739-d7f9bd16d675_1",
linkedEntityId = "61036cd9-8819-4f56-8739-d7f9bd16d675",
label = "Wikidata",
name = "https://www.wikidata.org/wiki/Q240550",
disambiguation = null,
attributes = "",
additionalInfo = null,
linkedEntity = MusicBrainzEntity.URL,
),
RelationListItemModel(
id = "e4a5db48-cae3-404f-921d-0f1c3947f874_0",
linkedEntityId = "e4a5db48-cae3-404f-921d-0f1c3947f874",
label = "official homepages",
name = "https://www.rollingstone.com/music/music-lists/best-albums-of-all-time-1062063/",
disambiguation = null,
attributes = "",
additionalInfo = null,
linkedEntity = MusicBrainzEntity.URL,
),
),
),
allDataArtistDetailsModel,
)
}
}
2 changes: 1 addition & 1 deletion docs/all_features.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ TODO: screenshot of list screen
| recording |||
| release |||
| release group | ✅️ ||
| series | ||
| series | ||
| work |||

- A collection does not have a details screen. It only has one tab which lists all of its contents. e.g. An artist collection lists all of its artists
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package ly.david.musicsearch.shared.domain.series

interface SeriesRepository {
suspend fun lookupSeries(seriesId: String): SeriesDetailsModel
suspend fun lookupSeries(
seriesId: String,
forceRefresh: Boolean,
): SeriesDetailsModel
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ internal class SeriesPresenter(

LaunchedEffect(forceRefreshDetails) {
try {
val seriesDetailsModel = repository.lookupSeries(screen.id)
val seriesDetailsModel = repository.lookupSeries(
seriesId = screen.id,
forceRefresh = forceRefreshDetails,
)
title = seriesDetailsModel.getNameWithDisambiguation()
series = seriesDetailsModel
isError = false
Expand Down

0 comments on commit 6e003c1

Please sign in to comment.