mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
Compare commits
2 Commits
d0e5e978be
...
7f83bb181b
| Author | SHA1 | Date | |
|---|---|---|---|
| 7f83bb181b | |||
| a2931b78f9 |
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,18 @@ 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.HomeCache
|
||||||
import java.util.Locale
|
import hu.bbara.purefin.data.offline.cache.toCachedEpisode
|
||||||
import java.util.UUID
|
import hu.bbara.purefin.data.offline.cache.toCachedItem
|
||||||
import java.util.concurrent.TimeUnit
|
import hu.bbara.purefin.data.offline.cache.toCachedLibrary
|
||||||
import javax.inject.Inject
|
import hu.bbara.purefin.data.offline.cache.toCachedMovie
|
||||||
import javax.inject.Singleton
|
import hu.bbara.purefin.data.offline.cache.toCachedSeries
|
||||||
|
import hu.bbara.purefin.data.offline.cache.toEpisode
|
||||||
|
import hu.bbara.purefin.data.offline.cache.toLibrary
|
||||||
|
import hu.bbara.purefin.data.offline.cache.toMedia
|
||||||
|
import hu.bbara.purefin.data.offline.cache.toMovie
|
||||||
|
import hu.bbara.purefin.data.offline.cache.toSeries
|
||||||
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 +35,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 +57,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 +83,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
|
Log.d(TAG, "Initializing home repository")
|
||||||
}
|
scope.launch { loadHomeCache() }
|
||||||
refreshHomeData()
|
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,23 +109,28 @@ 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.libraries.isNotEmpty()) {
|
||||||
|
val libraries = cache.libraries.mapNotNull { it.toLibrary() }
|
||||||
|
librariesState.value = libraries
|
||||||
|
onlineMediaRepository.upsertMovies(
|
||||||
|
libraries.filter { it.type == LibraryKind.MOVIES }.flatMap { it.movies.orEmpty() }
|
||||||
|
)
|
||||||
|
onlineMediaRepository.upsertSeries(
|
||||||
|
libraries.filter { it.type == LibraryKind.SERIES }.flatMap { it.series.orEmpty() }
|
||||||
|
)
|
||||||
|
}
|
||||||
if (cache.suggestions.isNotEmpty()) {
|
if (cache.suggestions.isNotEmpty()) {
|
||||||
suggestionsState.value = cache.suggestions.mapNotNull { it.toMedia() }
|
suggestionsState.value = cache.suggestions.mapNotNull { it.toMedia() }
|
||||||
}
|
}
|
||||||
@@ -135,9 +146,23 @@ class InMemoryAppContentRepository @Inject constructor(
|
|||||||
uuid to items.mapNotNull { it.toMedia() }
|
uuid to items.mapNotNull { it.toMedia() }
|
||||||
}.toMap()
|
}.toMap()
|
||||||
}
|
}
|
||||||
|
if (cache.movies.isNotEmpty()) {
|
||||||
|
onlineMediaRepository.upsertMovies(cache.movies.mapNotNull { it.toMovie() })
|
||||||
|
}
|
||||||
|
if (cache.series.isNotEmpty()) {
|
||||||
|
onlineMediaRepository.upsertSeries(cache.series.mapNotNull { it.toSeries() })
|
||||||
|
}
|
||||||
|
if (cache.episodes.isNotEmpty()) {
|
||||||
|
onlineMediaRepository.upsertEpisodes(cache.episodes.mapNotNull { it.toEpisode() })
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Home cache loaded")
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun persistHomeCache() {
|
private suspend fun persistHomeCache() {
|
||||||
|
val referencedMediaIds = collectReferencedMediaIds()
|
||||||
|
val movies = onlineMediaRepository.movies.value
|
||||||
|
val series = onlineMediaRepository.series.value
|
||||||
|
val episodes = onlineMediaRepository.episodes.value
|
||||||
val cache = HomeCache(
|
val cache = HomeCache(
|
||||||
suggestions = suggestionsState.value.map { it.toCachedItem() },
|
suggestions = suggestionsState.value.map { it.toCachedItem() },
|
||||||
continueWatching = continueWatchingState.value.map { it.toCachedItem() },
|
continueWatching = continueWatchingState.value.map { it.toCachedItem() },
|
||||||
@@ -145,29 +170,41 @@ class InMemoryAppContentRepository @Inject constructor(
|
|||||||
latestLibraryContent = latestLibraryContentState.value.map { (uuid, items) ->
|
latestLibraryContent = latestLibraryContentState.value.map { (uuid, items) ->
|
||||||
uuid.toString() to items.map { it.toCachedItem() }
|
uuid.toString() to items.map { it.toCachedItem() }
|
||||||
}.toMap(),
|
}.toMap(),
|
||||||
|
libraries = librariesState.value.map { it.toCachedLibrary() },
|
||||||
|
movies = referencedMediaIds.movieIds.mapNotNull { movies[it] }.map { it.toCachedMovie() },
|
||||||
|
series = referencedMediaIds.seriesIds.mapNotNull { series[it] }.map { it.toCachedSeries() },
|
||||||
|
episodes = referencedMediaIds.episodeIds.mapNotNull { episodes[it] }.map { it.toCachedEpisode() },
|
||||||
)
|
)
|
||||||
homeCacheDataStore.updateData { cache }
|
homeCacheDataStore.updateData { cache }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Media.toCachedItem(): CachedMediaItem = when (this) {
|
private fun collectReferencedMediaIds(): ReferencedHomeMediaIds {
|
||||||
is Media.MovieMedia -> CachedMediaItem(type = "MOVIE", id = movieId.toString())
|
val movieIds = mutableSetOf<UUID>()
|
||||||
is Media.SeriesMedia -> CachedMediaItem(type = "SERIES", id = seriesId.toString())
|
val seriesIds = mutableSetOf<UUID>()
|
||||||
is Media.SeasonMedia -> CachedMediaItem(type = "SEASON", id = seasonId.toString(), seriesId = seriesId.toString())
|
val episodeIds = mutableSetOf<UUID>()
|
||||||
is Media.EpisodeMedia -> CachedMediaItem(type = "EPISODE", id = episodeId.toString(), seriesId = seriesId.toString())
|
val referencedMedia = buildList {
|
||||||
|
addAll(suggestionsState.value)
|
||||||
|
addAll(continueWatchingState.value)
|
||||||
|
addAll(nextUpState.value)
|
||||||
|
latestLibraryContentState.value.values.forEach { addAll(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CachedMediaItem.toMedia(): Media? {
|
referencedMedia.forEach { media ->
|
||||||
val uuid = runCatching { UUID.fromString(id) }.getOrNull() ?: return null
|
when (media) {
|
||||||
val seriesUuid = seriesId?.let { runCatching { UUID.fromString(it) }.getOrNull() }
|
is Media.MovieMedia -> movieIds += media.movieId
|
||||||
return when (type) {
|
is Media.SeriesMedia -> seriesIds += media.seriesId
|
||||||
"MOVIE" -> Media.MovieMedia(movieId = uuid)
|
is Media.SeasonMedia -> seriesIds += media.seriesId
|
||||||
"SERIES" -> Media.SeriesMedia(seriesId = uuid)
|
is Media.EpisodeMedia -> episodeIds += media.episodeId
|
||||||
"SEASON" -> Media.SeasonMedia(seasonId = uuid, seriesId = seriesUuid ?: return null)
|
|
||||||
"EPISODE" -> Media.EpisodeMedia(episodeId = uuid, seriesId = seriesUuid ?: return null)
|
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return ReferencedHomeMediaIds(
|
||||||
|
movieIds = movieIds,
|
||||||
|
seriesIds = seriesIds,
|
||||||
|
episodeIds = episodeIds,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun loadLibraries() {
|
suspend fun loadLibraries() {
|
||||||
val librariesItem = runCatching { jellyfinApiClient.getLibraries() }
|
val librariesItem = runCatching { jellyfinApiClient.getLibraries() }
|
||||||
.getOrElse { error ->
|
.getOrElse { error ->
|
||||||
@@ -419,3 +456,9 @@ class InMemoryAppContentRepository @Inject constructor(
|
|||||||
private const val TAG = "InMemoryAppContentRepo"
|
private const val TAG = "InMemoryAppContentRepo"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private data class ReferencedHomeMediaIds(
|
||||||
|
val movieIds: Set<UUID>,
|
||||||
|
val seriesIds: Set<UUID>,
|
||||||
|
val episodeIds: Set<UUID>,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,6 +1,70 @@
|
|||||||
package hu.bbara.purefin.data.offline.cache
|
package hu.bbara.purefin.data.offline.cache
|
||||||
|
|
||||||
|
import hu.bbara.purefin.core.model.CastMember
|
||||||
|
import hu.bbara.purefin.core.model.Episode
|
||||||
|
import hu.bbara.purefin.core.model.Library
|
||||||
|
import hu.bbara.purefin.core.model.LibraryKind
|
||||||
|
import hu.bbara.purefin.core.model.Media
|
||||||
|
import hu.bbara.purefin.core.model.Movie
|
||||||
|
import hu.bbara.purefin.core.model.Series
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CachedCastMember(
|
||||||
|
val name: String,
|
||||||
|
val role: String,
|
||||||
|
val imageUrl: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CachedMovie(
|
||||||
|
val id: String,
|
||||||
|
val libraryId: String,
|
||||||
|
val title: String,
|
||||||
|
val progress: Double? = null,
|
||||||
|
val watched: Boolean,
|
||||||
|
val year: String,
|
||||||
|
val rating: String,
|
||||||
|
val runtime: String,
|
||||||
|
val format: String,
|
||||||
|
val synopsis: String,
|
||||||
|
val imageUrlPrefix: String,
|
||||||
|
val audioTrack: String,
|
||||||
|
val subtitles: String,
|
||||||
|
val cast: List<CachedCastMember> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CachedSeries(
|
||||||
|
val id: String,
|
||||||
|
val libraryId: String,
|
||||||
|
val name: String,
|
||||||
|
val synopsis: String,
|
||||||
|
val year: String,
|
||||||
|
val imageUrlPrefix: String,
|
||||||
|
val unwatchedEpisodeCount: Int,
|
||||||
|
val seasonCount: Int,
|
||||||
|
val cast: List<CachedCastMember> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CachedEpisode(
|
||||||
|
val id: String,
|
||||||
|
val seriesId: String,
|
||||||
|
val seasonId: String,
|
||||||
|
val index: Int,
|
||||||
|
val title: String,
|
||||||
|
val synopsis: String,
|
||||||
|
val releaseDate: String,
|
||||||
|
val rating: String,
|
||||||
|
val runtime: String,
|
||||||
|
val progress: Double? = null,
|
||||||
|
val watched: Boolean,
|
||||||
|
val format: String,
|
||||||
|
val imageUrlPrefix: String,
|
||||||
|
val cast: List<CachedCastMember> = emptyList()
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class CachedMediaItem(
|
data class CachedMediaItem(
|
||||||
@@ -9,10 +73,193 @@ data class CachedMediaItem(
|
|||||||
val seriesId: String? = null
|
val seriesId: String? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class CachedLibrary(
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val type: String,
|
||||||
|
val posterUrl: String,
|
||||||
|
val series: List<CachedSeries>? = null,
|
||||||
|
val movies: List<CachedMovie>? = null,
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class HomeCache(
|
data class HomeCache(
|
||||||
val suggestions: List<CachedMediaItem> = emptyList(),
|
val suggestions: List<CachedMediaItem> = emptyList(),
|
||||||
val continueWatching: List<CachedMediaItem> = emptyList(),
|
val continueWatching: List<CachedMediaItem> = emptyList(),
|
||||||
val nextUp: List<CachedMediaItem> = emptyList(),
|
val nextUp: List<CachedMediaItem> = emptyList(),
|
||||||
val latestLibraryContent: Map<String, List<CachedMediaItem>> = emptyMap()
|
val latestLibraryContent: Map<String, List<CachedMediaItem>> = emptyMap(),
|
||||||
|
val libraries: List<CachedLibrary> = emptyList(),
|
||||||
|
val movies: List<CachedMovie> = emptyList(),
|
||||||
|
val series: List<CachedSeries> = emptyList(),
|
||||||
|
val episodes: List<CachedEpisode> = emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun Library.toCachedLibrary() = CachedLibrary(
|
||||||
|
id = id.toString(),
|
||||||
|
name = name,
|
||||||
|
type = type.name,
|
||||||
|
posterUrl = posterUrl,
|
||||||
|
series = series?.map { it.toCachedSeries() },
|
||||||
|
movies = movies?.map { it.toCachedMovie() },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CachedLibrary.toLibrary(): Library? {
|
||||||
|
val libraryId = id.toUuidOrNull() ?: return null
|
||||||
|
return when (type) {
|
||||||
|
"MOVIES" -> Library(
|
||||||
|
id = libraryId,
|
||||||
|
name = name,
|
||||||
|
type = LibraryKind.MOVIES,
|
||||||
|
posterUrl = posterUrl,
|
||||||
|
movies = movies?.mapNotNull { it.toMovie() } ?: emptyList(),
|
||||||
|
)
|
||||||
|
"SERIES" -> Library(
|
||||||
|
id = libraryId,
|
||||||
|
name = name,
|
||||||
|
type = LibraryKind.SERIES,
|
||||||
|
posterUrl = posterUrl,
|
||||||
|
series = series?.mapNotNull { it.toSeries() } ?: emptyList(),
|
||||||
|
)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Media.toCachedItem(): CachedMediaItem = when (this) {
|
||||||
|
is Media.MovieMedia -> CachedMediaItem(type = "MOVIE", id = movieId.toString())
|
||||||
|
is Media.SeriesMedia -> CachedMediaItem(type = "SERIES", id = seriesId.toString())
|
||||||
|
is Media.SeasonMedia -> CachedMediaItem(type = "SEASON", id = seasonId.toString(), seriesId = seriesId.toString())
|
||||||
|
is Media.EpisodeMedia -> CachedMediaItem(type = "EPISODE", id = episodeId.toString(), seriesId = seriesId.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun CachedMediaItem.toMedia(): Media? {
|
||||||
|
val uuid = runCatching { UUID.fromString(id) }.getOrNull() ?: return null
|
||||||
|
val seriesUuid = seriesId?.let { runCatching { UUID.fromString(it) }.getOrNull() }
|
||||||
|
return when (type) {
|
||||||
|
"MOVIE" -> Media.MovieMedia(movieId = uuid)
|
||||||
|
"SERIES" -> Media.SeriesMedia(seriesId = uuid)
|
||||||
|
"SEASON" -> Media.SeasonMedia(seasonId = uuid, seriesId = seriesUuid ?: return null)
|
||||||
|
"EPISODE" -> Media.EpisodeMedia(episodeId = uuid, seriesId = seriesUuid ?: return null)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Movie.toCachedMovie() = CachedMovie(
|
||||||
|
id = id.toString(),
|
||||||
|
libraryId = libraryId.toString(),
|
||||||
|
title = title,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
year = year,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
format = format,
|
||||||
|
synopsis = synopsis,
|
||||||
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
|
audioTrack = audioTrack,
|
||||||
|
subtitles = subtitles,
|
||||||
|
cast = cast.map { it.toCachedCastMember() },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CachedMovie.toMovie(): Movie? {
|
||||||
|
val movieId = id.toUuidOrNull() ?: return null
|
||||||
|
val libraryUuid = libraryId.toUuidOrNull() ?: return null
|
||||||
|
return Movie(
|
||||||
|
id = movieId,
|
||||||
|
libraryId = libraryUuid,
|
||||||
|
title = title,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
year = year,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
format = format,
|
||||||
|
synopsis = synopsis,
|
||||||
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
|
audioTrack = audioTrack,
|
||||||
|
subtitles = subtitles,
|
||||||
|
cast = cast.map { it.toCastMember() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Series.toCachedSeries() = CachedSeries(
|
||||||
|
id = id.toString(),
|
||||||
|
libraryId = libraryId.toString(),
|
||||||
|
name = name,
|
||||||
|
synopsis = synopsis,
|
||||||
|
year = year,
|
||||||
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
|
unwatchedEpisodeCount = unwatchedEpisodeCount,
|
||||||
|
seasonCount = seasonCount,
|
||||||
|
cast = cast.map { it.toCachedCastMember() },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CachedSeries.toSeries(): Series? {
|
||||||
|
val seriesId = id.toUuidOrNull() ?: return null
|
||||||
|
val libraryUuid = libraryId.toUuidOrNull() ?: return null
|
||||||
|
return Series(
|
||||||
|
id = seriesId,
|
||||||
|
libraryId = libraryUuid,
|
||||||
|
name = name,
|
||||||
|
synopsis = synopsis,
|
||||||
|
year = year,
|
||||||
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
|
unwatchedEpisodeCount = unwatchedEpisodeCount,
|
||||||
|
seasonCount = seasonCount,
|
||||||
|
seasons = emptyList(),
|
||||||
|
cast = cast.map { it.toCastMember() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Episode.toCachedEpisode() = CachedEpisode(
|
||||||
|
id = id.toString(),
|
||||||
|
seriesId = seriesId.toString(),
|
||||||
|
seasonId = seasonId.toString(),
|
||||||
|
index = index,
|
||||||
|
title = title,
|
||||||
|
synopsis = synopsis,
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
format = format,
|
||||||
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
|
cast = cast.map { it.toCachedCastMember() },
|
||||||
|
)
|
||||||
|
|
||||||
|
fun CachedEpisode.toEpisode(): Episode? {
|
||||||
|
val episodeId = id.toUuidOrNull() ?: return null
|
||||||
|
val seriesUuid = seriesId.toUuidOrNull() ?: return null
|
||||||
|
val seasonUuid = seasonId.toUuidOrNull() ?: return null
|
||||||
|
return Episode(
|
||||||
|
id = episodeId,
|
||||||
|
seriesId = seriesUuid,
|
||||||
|
seasonId = seasonUuid,
|
||||||
|
index = index,
|
||||||
|
title = title,
|
||||||
|
synopsis = synopsis,
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
format = format,
|
||||||
|
imageUrlPrefix = imageUrlPrefix,
|
||||||
|
cast = cast.map { it.toCastMember() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun CastMember.toCachedCastMember() = CachedCastMember(
|
||||||
|
name = name,
|
||||||
|
role = role,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CachedCastMember.toCastMember() = CastMember(
|
||||||
|
name = name,
|
||||||
|
role = role,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun String.toUuidOrNull(): UUID? = runCatching { UUID.fromString(this) }.getOrNull()
|
||||||
|
|||||||
Reference in New Issue
Block a user