mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
refactor(data): remove redundant dedup guards and improve loading robustness
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
package hu.bbara.purefin.data.catalog
|
package hu.bbara.purefin.data.catalog
|
||||||
|
|
||||||
|
import hu.bbara.purefin.core.concurrency.SingleFlight
|
||||||
import hu.bbara.purefin.core.data.LocalMediaRepository
|
import hu.bbara.purefin.core.data.LocalMediaRepository
|
||||||
import hu.bbara.purefin.core.data.UserSessionRepository
|
import hu.bbara.purefin.core.data.UserSessionRepository
|
||||||
import hu.bbara.purefin.core.concurrency.SingleFlight
|
|
||||||
import hu.bbara.purefin.data.converter.toEpisode
|
import hu.bbara.purefin.data.converter.toEpisode
|
||||||
import hu.bbara.purefin.data.converter.toMovie
|
import hu.bbara.purefin.data.converter.toMovie
|
||||||
import hu.bbara.purefin.data.converter.toSeason
|
import hu.bbara.purefin.data.converter.toSeason
|
||||||
@@ -12,9 +12,6 @@ import hu.bbara.purefin.model.Episode
|
|||||||
import hu.bbara.purefin.model.Movie
|
import hu.bbara.purefin.model.Movie
|
||||||
import hu.bbara.purefin.model.Series
|
import hu.bbara.purefin.model.Series
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -36,8 +33,6 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
private val singleFlight: SingleFlight,
|
private val singleFlight: SingleFlight,
|
||||||
) : LocalMediaRepository {
|
) : LocalMediaRepository {
|
||||||
|
|
||||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
||||||
|
|
||||||
private val serverUrl = userSessionRepository.serverUrl
|
private val serverUrl = userSessionRepository.serverUrl
|
||||||
|
|
||||||
private val moviesState = MutableStateFlow<Map<UUID, Movie>>(emptyMap())
|
private val moviesState = MutableStateFlow<Map<UUID, Movie>>(emptyMap())
|
||||||
@@ -59,7 +54,6 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
episodesState.map { it[id] }.distinctUntilChanged()
|
episodesState.map { it[id] }.distinctUntilChanged()
|
||||||
|
|
||||||
override suspend fun loadMovie(id: UUID) = singleFlight.run("LocalMedia:loadMovie:$id") {
|
override suspend fun loadMovie(id: UUID) = singleFlight.run("LocalMedia:loadMovie:$id") {
|
||||||
if (moviesState.value.containsKey(id)) return@run
|
|
||||||
try {
|
try {
|
||||||
jellyfinApiClient.getItemInfo(id)?.let { item ->
|
jellyfinApiClient.getItemInfo(id)?.let { item ->
|
||||||
if (item.type != BaseItemKind.MOVIE) {
|
if (item.type != BaseItemKind.MOVIE) {
|
||||||
@@ -78,7 +72,6 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadSeries(id: UUID) = singleFlight.run("LocalMedia:loadSeries:$id") {
|
override suspend fun loadSeries(id: UUID) = singleFlight.run("LocalMedia:loadSeries:$id") {
|
||||||
if (seriesState.value.containsKey(id)) return@run
|
|
||||||
try {
|
try {
|
||||||
jellyfinApiClient.getItemInfo(id)?.let { item ->
|
jellyfinApiClient.getItemInfo(id)?.let { item ->
|
||||||
if (item.type != BaseItemKind.SERIES) {
|
if (item.type != BaseItemKind.SERIES) {
|
||||||
@@ -97,7 +90,6 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadEpisode(id: UUID) = singleFlight.run("LocalMedia:loadEpisode:$id") {
|
override suspend fun loadEpisode(id: UUID) = singleFlight.run("LocalMedia:loadEpisode:$id") {
|
||||||
if (episodesState.value.containsKey(id)) return@run
|
|
||||||
try {
|
try {
|
||||||
jellyfinApiClient.getItemInfo(id)?.let { item ->
|
jellyfinApiClient.getItemInfo(id)?.let { item ->
|
||||||
if (item.type != BaseItemKind.EPISODE) {
|
if (item.type != BaseItemKind.EPISODE) {
|
||||||
@@ -129,35 +121,35 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
|
|
||||||
override suspend fun loadSeasons(seriesId: UUID) = singleFlight.run("LocalMedia:loadSeasons:$seriesId") {
|
override suspend fun loadSeasons(seriesId: UUID) = singleFlight.run("LocalMedia:loadSeasons:$seriesId") {
|
||||||
try {
|
try {
|
||||||
loadSeasonsInternal(seriesId)
|
// Ensure the series is loaded
|
||||||
} catch (error: CancellationException) {
|
var series = seriesState.value[seriesId]
|
||||||
throw error
|
if (series == null) {
|
||||||
} catch (error: Exception) {
|
loadSeries(seriesId)
|
||||||
Timber.tag(TAG).e(error, "Failed to load content for series $seriesId")
|
series = seriesState.first()[seriesId] ?: throw RuntimeException("Series not found")
|
||||||
throw error
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun loadSeasonsInternal(seriesId: UUID) {
|
|
||||||
seriesState.value[seriesId]?.takeIf { it.seasons.isNotEmpty() }?.let { return }
|
|
||||||
|
|
||||||
val series = seriesState.value[seriesId] ?: throw RuntimeException("Series not found")
|
|
||||||
|
|
||||||
val updatedSeries = series.copy(
|
val updatedSeries = series.copy(
|
||||||
seasons = jellyfinApiClient.getSeasons(seriesId).map { it.toSeason() }
|
seasons = jellyfinApiClient.getSeasons(seriesId).map { it.toSeason() }
|
||||||
)
|
)
|
||||||
seriesState.update { it + (updatedSeries.id to updatedSeries) }
|
seriesState.update { it + (updatedSeries.id to updatedSeries) }
|
||||||
|
} catch (error: CancellationException) {
|
||||||
|
throw error
|
||||||
|
} catch (error: Exception) {
|
||||||
|
Timber.tag(TAG).e(error, "Failed to load seasons for series $seriesId")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun loadSeasonEpisodes(seriesId: UUID, seasonId: UUID) =
|
override suspend fun loadSeasonEpisodes(seriesId: UUID, seasonId: UUID) =
|
||||||
singleFlight.run("LocalMedia:loadSeasonEpisodes:$seriesId:$seasonId") {
|
singleFlight.run("LocalMedia:loadSeasonEpisodes:$seriesId:$seasonId") {
|
||||||
loadSeasons(seriesId)
|
try {
|
||||||
|
// Ensure the series and season is loaded
|
||||||
val series = seriesState.value[seriesId] ?: throw RuntimeException("Series not found")
|
var series = seriesState.value[seriesId]
|
||||||
val season = series.seasons.firstOrNull { it.id == seasonId } ?: return@run
|
if (series == null) {
|
||||||
if (season.episodes.isNotEmpty() || season.episodeCount == 0) {
|
loadSeries(seriesId)
|
||||||
return@run
|
series = seriesState.first()[seriesId] ?: throw RuntimeException("Series not found")
|
||||||
}
|
}
|
||||||
|
series.seasons.firstOrNull { it.id == seasonId }?.let { loadSeasons(seriesId) }
|
||||||
|
|
||||||
val serverUrl = userSessionRepository.serverUrl.first()
|
val serverUrl = userSessionRepository.serverUrl.first()
|
||||||
val episodes = jellyfinApiClient.getEpisodesInSeason(seriesId, seasonId)
|
val episodes = jellyfinApiClient.getEpisodesInSeason(seriesId, seasonId)
|
||||||
@@ -176,6 +168,12 @@ class InMemoryLocalMediaRepository @Inject constructor(
|
|||||||
current + (updatedSeries.id to updatedSeries)
|
current + (updatedSeries.id to updatedSeries)
|
||||||
}
|
}
|
||||||
episodesState.update { current -> current + episodes.associateBy { it.id } }
|
episodesState.update { current -> current + episodes.associateBy { it.id } }
|
||||||
|
} catch (error: CancellationException) {
|
||||||
|
throw error
|
||||||
|
} catch (error: Exception) {
|
||||||
|
Timber.tag(TAG).e(error, "Failed to load episodes for season $seasonId")
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long) {
|
override suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long) {
|
||||||
|
|||||||
Reference in New Issue
Block a user