refactor: simplify AppContentRepository initialization logic.

This commit is contained in:
2026-04-21 21:34:22 +02:00
parent d0e5e978be
commit a2931b78f9
2 changed files with 34 additions and 41 deletions

View File

@@ -2,8 +2,8 @@ package hu.bbara.purefin.core.data
import hu.bbara.purefin.core.model.Library import hu.bbara.purefin.core.model.Library
import hu.bbara.purefin.core.model.Media import hu.bbara.purefin.core.model.Media
import java.util.UUID
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import java.util.UUID
interface HomeRepository { interface HomeRepository {
val libraries: StateFlow<List<Library>> val libraries: StateFlow<List<Library>>
@@ -11,6 +11,6 @@ interface HomeRepository {
val continueWatching: StateFlow<List<Media>> val continueWatching: StateFlow<List<Media>>
val nextUp: StateFlow<List<Media>> val nextUp: StateFlow<List<Media>>
val latestLibraryContent: StateFlow<Map<UUID, List<Media>>> val latestLibraryContent: StateFlow<Map<UUID, List<Media>>>
suspend fun ensureReady() fun ensureReady()
suspend fun refreshHomeData() fun refreshHomeData()
} }

View File

@@ -4,12 +4,9 @@ import android.util.Log
import androidx.datastore.core.DataStore import androidx.datastore.core.DataStore
import hu.bbara.purefin.core.data.HomeRepository import hu.bbara.purefin.core.data.HomeRepository
import hu.bbara.purefin.core.data.NetworkMonitor import hu.bbara.purefin.core.data.NetworkMonitor
import hu.bbara.purefin.core.image.ArtworkKind
import hu.bbara.purefin.data.offline.cache.CachedMediaItem
import hu.bbara.purefin.data.offline.cache.HomeCache
import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.core.data.session.UserSessionRepository import hu.bbara.purefin.core.data.session.UserSessionRepository
import hu.bbara.purefin.core.image.ArtworkKind
import hu.bbara.purefin.core.image.ImageUrlBuilder
import hu.bbara.purefin.core.model.Episode import hu.bbara.purefin.core.model.Episode
import hu.bbara.purefin.core.model.Library import hu.bbara.purefin.core.model.Library
import hu.bbara.purefin.core.model.LibraryKind import hu.bbara.purefin.core.model.LibraryKind
@@ -17,13 +14,9 @@ import hu.bbara.purefin.core.model.Media
import hu.bbara.purefin.core.model.Movie import hu.bbara.purefin.core.model.Movie
import hu.bbara.purefin.core.model.Season import hu.bbara.purefin.core.model.Season
import hu.bbara.purefin.core.model.Series import hu.bbara.purefin.core.model.Series
import java.time.LocalDateTime import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
import java.time.format.DateTimeFormatter import hu.bbara.purefin.data.offline.cache.CachedMediaItem
import java.util.Locale import hu.bbara.purefin.data.offline.cache.HomeCache
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@@ -33,11 +26,18 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemDto
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType import org.jellyfin.sdk.model.api.CollectionType
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.Locale
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.concurrent.atomics.AtomicBoolean
import kotlin.concurrent.atomics.ExperimentalAtomicApi
@Singleton @Singleton
class InMemoryAppContentRepository @Inject constructor( class InMemoryAppContentRepository @Inject constructor(
@@ -48,10 +48,10 @@ class InMemoryAppContentRepository @Inject constructor(
private val networkMonitor: NetworkMonitor, private val networkMonitor: NetworkMonitor,
) : HomeRepository { ) : HomeRepository {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val ensureReadyMutex = Mutex()
private var refreshJob: Job? = null private var refreshJob: Job? = null
private var cacheHydrated = false
private var initialized = false @OptIn(ExperimentalAtomicApi::class)
private val initialized = AtomicBoolean(false)
private val librariesState = MutableStateFlow<List<Library>>(emptyList()) private val librariesState = MutableStateFlow<List<Library>>(emptyList())
override val libraries: StateFlow<List<Library>> = librariesState.asStateFlow() override val libraries: StateFlow<List<Library>> = librariesState.asStateFlow()
@@ -74,27 +74,24 @@ class InMemoryAppContentRepository @Inject constructor(
} }
} }
override suspend fun ensureReady() { @OptIn(ExperimentalAtomicApi::class)
ensureReadyMutex.withLock { override fun ensureReady() {
hydrateCacheIfNeeded() if (!initialized.compareAndSet(expectedValue = false, newValue = true)) {
if (initialized) { return
return
}
initialized = true
} }
refreshHomeData() Log.d(TAG, "Initializing home repository")
scope.launch { loadHomeCache() }
scope.launch { refreshHomeData() }
} }
override suspend fun refreshHomeData() { @Synchronized
ensureReadyMutex.withLock { override fun refreshHomeData() {
hydrateCacheIfNeeded()
}
if (refreshJob?.isActive == true) { if (refreshJob?.isActive == true) {
refreshJob?.join()
return return
} }
val job = scope.launch { val job = scope.launch {
runCatching { runCatching {
Log.d(TAG, "Refreshing home data")
if (!networkMonitor.isOnline.first()) { if (!networkMonitor.isOnline.first()) {
return@runCatching return@runCatching
} }
@@ -103,22 +100,17 @@ class InMemoryAppContentRepository @Inject constructor(
loadContinueWatching() loadContinueWatching()
loadNextUp() loadNextUp()
loadLatestLibraryContent() loadLatestLibraryContent()
Log.d(TAG, "Home refresh successful")
persistHomeCache() persistHomeCache()
}.onFailure { error -> }.onFailure { error ->
Log.w(TAG, "Home refresh failed; keeping cached content", error) Log.w(TAG, "Home refresh failed; keeping cached content", error)
} }
} }
refreshJob = job refreshJob = job
job.join()
} }
private suspend fun hydrateCacheIfNeeded() { private suspend fun loadHomeCache() {
if (cacheHydrated) return Log.d(TAG, "Loading home cache")
loadFromCache()
cacheHydrated = true
}
private suspend fun loadFromCache() {
val cache = homeCacheDataStore.data.first() val cache = homeCacheDataStore.data.first()
if (cache.suggestions.isNotEmpty()) { if (cache.suggestions.isNotEmpty()) {
suggestionsState.value = cache.suggestions.mapNotNull { it.toMedia() } suggestionsState.value = cache.suggestions.mapNotNull { it.toMedia() }
@@ -135,6 +127,7 @@ class InMemoryAppContentRepository @Inject constructor(
uuid to items.mapNotNull { it.toMedia() } uuid to items.mapNotNull { it.toMedia() }
}.toMap() }.toMap()
} }
Log.d(TAG, "Home cache loaded")
} }
private suspend fun persistHomeCache() { private suspend fun persistHomeCache() {