mirror of
https://github.com/bbara04/Purefin.git
synced 2026-04-01 01:30:08 +02:00
feat: add offline media repository and update dependency injection for media sources
This commit is contained in:
@@ -4,11 +4,22 @@ import dagger.Binds
|
|||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import hu.bbara.purefin.data.local.room.OnlineRepository
|
||||||
|
import hu.bbara.purefin.data.local.room.OfflineRepository
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
abstract class MediaRepositoryModule {
|
abstract class MediaRepositoryModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindMediaRepository(impl: InMemoryMediaRepository): MediaRepository
|
@OnlineRepository
|
||||||
|
abstract fun bindOnlineMediaRepository(impl: InMemoryMediaRepository): MediaRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@OfflineRepository
|
||||||
|
abstract fun bindOfflineMediaRepository(impl: OfflineMediaRepository): MediaRepository
|
||||||
|
|
||||||
|
// Default binding for backward compatibility (uses online repository)
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDefaultMediaRepository(impl: InMemoryMediaRepository): MediaRepository
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package hu.bbara.purefin.data
|
||||||
|
|
||||||
|
import hu.bbara.purefin.data.local.room.OfflineDatabase
|
||||||
|
import hu.bbara.purefin.data.local.room.OfflineRoomMediaLocalDataSource
|
||||||
|
import hu.bbara.purefin.data.model.Episode
|
||||||
|
import hu.bbara.purefin.data.model.Movie
|
||||||
|
import hu.bbara.purefin.data.model.Series
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline media repository for managing downloaded content.
|
||||||
|
* This repository only accesses the local offline database and does not make network calls.
|
||||||
|
*/
|
||||||
|
@Singleton
|
||||||
|
class OfflineMediaRepository @Inject constructor(
|
||||||
|
@OfflineDatabase private val localDataSource: OfflineRoomMediaLocalDataSource
|
||||||
|
) : MediaRepository {
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
|
// Offline repository is always ready (no network loading required)
|
||||||
|
private val _state: MutableStateFlow<MediaRepositoryState> = MutableStateFlow(MediaRepositoryState.Ready)
|
||||||
|
override val state: StateFlow<MediaRepositoryState> = _state.asStateFlow()
|
||||||
|
|
||||||
|
override val movies: StateFlow<Map<UUID, Movie>> = localDataSource.moviesFlow
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
||||||
|
|
||||||
|
override val series: StateFlow<Map<UUID, Series>> = localDataSource.seriesFlow
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
||||||
|
|
||||||
|
override val episodes: StateFlow<Map<UUID, Episode>> = localDataSource.episodesFlow
|
||||||
|
.stateIn(scope, SharingStarted.Eagerly, emptyMap())
|
||||||
|
|
||||||
|
override fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> {
|
||||||
|
return localDataSource.observeSeriesWithContent(seriesId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun ensureReady() {
|
||||||
|
// Offline repository is always ready - no initialization needed
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateWatchProgress(mediaId: UUID, positionMs: Long, durationMs: Long) {
|
||||||
|
if (durationMs <= 0) return
|
||||||
|
val progressPercent = (positionMs.toDouble() / durationMs.toDouble()) * 100.0
|
||||||
|
val watched = progressPercent >= 90.0
|
||||||
|
// Write to offline database - the reactive Flows propagate changes to UI automatically
|
||||||
|
localDataSource.updateWatchProgress(mediaId, progressPercent, watched)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun refreshHomeData() {
|
||||||
|
// No-op for offline repository - no network refresh available
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package hu.bbara.purefin.data.local.room
|
||||||
|
|
||||||
|
import javax.inject.Qualifier
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Qualifier for online database and its components.
|
||||||
|
* Used for the primary MediaDatabase that syncs with the Jellyfin server.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class OnlineDatabase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Qualifier for offline database and its components.
|
||||||
|
* Used for the OfflineMediaDatabase that stores downloaded content for offline viewing.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class OfflineDatabase
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Qualifier for the online media repository.
|
||||||
|
* Provides access to media synced from the Jellyfin server.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class OnlineRepository
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Qualifier for the offline media repository.
|
||||||
|
* Provides access to media downloaded for offline viewing.
|
||||||
|
*/
|
||||||
|
@Qualifier
|
||||||
|
@Retention(AnnotationRetention.BINARY)
|
||||||
|
annotation class OfflineRepository
|
||||||
@@ -7,31 +7,108 @@ import dagger.Provides
|
|||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.CastMemberDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.EpisodeDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.MovieDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.SeasonDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.SeriesDao
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object MediaDatabaseModule {
|
object MediaDatabaseModule {
|
||||||
|
|
||||||
|
// Online Database and DAOs
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDatabase(@ApplicationContext context: Context): MediaDatabase =
|
@OnlineDatabase
|
||||||
Room.databaseBuilder(context, MediaDatabase::class.java, "media_database")
|
fun provideOnlineDatabase(@ApplicationContext context: Context): MediaDatabase =
|
||||||
|
Room.inMemoryDatabaseBuilder(context, MediaDatabase::class.java)
|
||||||
.fallbackToDestructiveMigration()
|
.fallbackToDestructiveMigration()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideMovieDao(db: MediaDatabase) = db.movieDao()
|
@OnlineDatabase
|
||||||
|
fun provideOnlineMovieDao(@OnlineDatabase db: MediaDatabase) = db.movieDao()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSeriesDao(db: MediaDatabase) = db.seriesDao()
|
@OnlineDatabase
|
||||||
|
fun provideOnlineSeriesDao(@OnlineDatabase db: MediaDatabase) = db.seriesDao()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideSeasonDao(db: MediaDatabase) = db.seasonDao()
|
@OnlineDatabase
|
||||||
|
fun provideOnlineSeasonDao(@OnlineDatabase db: MediaDatabase) = db.seasonDao()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideEpisodeDao(db: MediaDatabase) = db.episodeDao()
|
@OnlineDatabase
|
||||||
|
fun provideOnlineEpisodeDao(@OnlineDatabase db: MediaDatabase) = db.episodeDao()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideCastMemberDao(db: MediaDatabase) = db.castMemberDao()
|
@OnlineDatabase
|
||||||
|
fun provideOnlineCastMemberDao(@OnlineDatabase db: MediaDatabase) = db.castMemberDao()
|
||||||
|
|
||||||
|
// Offline Database and DAOs
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineDatabase(@ApplicationContext context: Context): OfflineMediaDatabase =
|
||||||
|
Room.databaseBuilder(context, OfflineMediaDatabase::class.java, "offline_media_database")
|
||||||
|
.fallbackToDestructiveMigration()
|
||||||
|
.build()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineMovieDao(@OfflineDatabase db: OfflineMediaDatabase) = db.movieDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineSeriesDao(@OfflineDatabase db: OfflineMediaDatabase) = db.seriesDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineSeasonDao(@OfflineDatabase db: OfflineMediaDatabase) = db.seasonDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineEpisodeDao(@OfflineDatabase db: OfflineMediaDatabase) = db.episodeDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineCastMemberDao(@OfflineDatabase db: OfflineMediaDatabase) = db.castMemberDao()
|
||||||
|
|
||||||
|
// Data Sources
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@OnlineDatabase
|
||||||
|
fun provideOnlineDataSource(
|
||||||
|
@OnlineDatabase database: MediaDatabase,
|
||||||
|
@OnlineDatabase movieDao: MovieDao,
|
||||||
|
@OnlineDatabase seriesDao: SeriesDao,
|
||||||
|
@OnlineDatabase seasonDao: SeasonDao,
|
||||||
|
@OnlineDatabase episodeDao: EpisodeDao,
|
||||||
|
@OnlineDatabase castMemberDao: CastMemberDao
|
||||||
|
): RoomMediaLocalDataSource = RoomMediaLocalDataSource(
|
||||||
|
database, movieDao, seriesDao, seasonDao, episodeDao, castMemberDao
|
||||||
|
)
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
@OfflineDatabase
|
||||||
|
fun provideOfflineDataSource(
|
||||||
|
@OfflineDatabase database: OfflineMediaDatabase,
|
||||||
|
@OfflineDatabase movieDao: MovieDao,
|
||||||
|
@OfflineDatabase seriesDao: SeriesDao,
|
||||||
|
@OfflineDatabase seasonDao: SeasonDao,
|
||||||
|
@OfflineDatabase episodeDao: EpisodeDao,
|
||||||
|
@OfflineDatabase castMemberDao: CastMemberDao
|
||||||
|
): OfflineRoomMediaLocalDataSource = OfflineRoomMediaLocalDataSource(
|
||||||
|
database, movieDao, seriesDao, seasonDao, episodeDao, castMemberDao
|
||||||
|
)
|
||||||
|
|
||||||
|
// Default (unqualified) data source for backward compatibility
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideDefaultDataSource(
|
||||||
|
@OnlineDatabase dataSource: RoomMediaLocalDataSource
|
||||||
|
): RoomMediaLocalDataSource = dataSource
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package hu.bbara.purefin.data.local.room
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import androidx.room.TypeConverters
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.CastMemberDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.EpisodeDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.MovieDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.SeasonDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.SeriesDao
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [
|
||||||
|
MovieEntity::class,
|
||||||
|
SeriesEntity::class,
|
||||||
|
SeasonEntity::class,
|
||||||
|
EpisodeEntity::class,
|
||||||
|
CastMemberEntity::class
|
||||||
|
],
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
|
@TypeConverters(UuidConverters::class)
|
||||||
|
abstract class OfflineMediaDatabase : RoomDatabase() {
|
||||||
|
abstract fun movieDao(): MovieDao
|
||||||
|
abstract fun seriesDao(): SeriesDao
|
||||||
|
abstract fun seasonDao(): SeasonDao
|
||||||
|
abstract fun episodeDao(): EpisodeDao
|
||||||
|
abstract fun castMemberDao(): CastMemberDao
|
||||||
|
}
|
||||||
@@ -0,0 +1,322 @@
|
|||||||
|
package hu.bbara.purefin.data.local.room
|
||||||
|
|
||||||
|
import androidx.room.withTransaction
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.CastMemberDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.EpisodeDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.MovieDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.SeasonDao
|
||||||
|
import hu.bbara.purefin.data.local.room.dao.SeriesDao
|
||||||
|
import hu.bbara.purefin.data.model.CastMember
|
||||||
|
import hu.bbara.purefin.data.model.Episode
|
||||||
|
import hu.bbara.purefin.data.model.Movie
|
||||||
|
import hu.bbara.purefin.data.model.Season
|
||||||
|
import hu.bbara.purefin.data.model.Series
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class OfflineRoomMediaLocalDataSource(
|
||||||
|
private val database: OfflineMediaDatabase,
|
||||||
|
private val movieDao: MovieDao,
|
||||||
|
private val seriesDao: SeriesDao,
|
||||||
|
private val seasonDao: SeasonDao,
|
||||||
|
private val episodeDao: EpisodeDao,
|
||||||
|
private val castMemberDao: CastMemberDao
|
||||||
|
) {
|
||||||
|
|
||||||
|
// Lightweight Flows for list screens (home, library)
|
||||||
|
val moviesFlow: Flow<Map<UUID, Movie>> = movieDao.observeAll()
|
||||||
|
.map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } }
|
||||||
|
|
||||||
|
val seriesFlow: Flow<Map<UUID, Series>> = seriesDao.observeAll()
|
||||||
|
.map { entities ->
|
||||||
|
entities.associate { it.id to it.toDomain(seasons = emptyList(), cast = emptyList()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val episodesFlow: Flow<Map<UUID, Episode>> = episodeDao.observeAll()
|
||||||
|
.map { entities -> entities.associate { it.id to it.toDomain(cast = emptyList()) } }
|
||||||
|
|
||||||
|
// Full content Flow for series detail screen (scoped to one series)
|
||||||
|
fun observeSeriesWithContent(seriesId: UUID): Flow<Series?> =
|
||||||
|
seriesDao.observeWithContent(seriesId).map { relation ->
|
||||||
|
relation?.let {
|
||||||
|
it.series.toDomain(
|
||||||
|
seasons = it.seasons.map { swe ->
|
||||||
|
swe.season.toDomain(
|
||||||
|
episodes = swe.episodes.map { ep -> ep.toDomain(cast = emptyList()) }
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cast = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveMovies(movies: List<Movie>) {
|
||||||
|
database.withTransaction {
|
||||||
|
movieDao.upsertAll(movies.map { it.toEntity() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveSeries(seriesList: List<Series>) {
|
||||||
|
database.withTransaction {
|
||||||
|
seriesDao.upsertAll(seriesList.map { it.toEntity() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveSeriesContent(series: Series) {
|
||||||
|
database.withTransaction {
|
||||||
|
// First ensure the series exists before adding seasons/episodes/cast
|
||||||
|
seriesDao.upsert(series.toEntity())
|
||||||
|
|
||||||
|
episodeDao.deleteBySeriesId(series.id)
|
||||||
|
seasonDao.deleteBySeriesId(series.id)
|
||||||
|
|
||||||
|
series.seasons.forEach { season ->
|
||||||
|
seasonDao.upsert(season.toEntity())
|
||||||
|
season.episodes.forEach { episode ->
|
||||||
|
episodeDao.upsert(episode.toEntity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun saveEpisode(episode: Episode) {
|
||||||
|
database.withTransaction {
|
||||||
|
seriesDao.getById(episode.seriesId)
|
||||||
|
?: throw RuntimeException("Cannot add episode without series. Episode: $episode")
|
||||||
|
|
||||||
|
episodeDao.upsert(episode.toEntity())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMovies(): List<Movie> {
|
||||||
|
val movies = movieDao.getAll()
|
||||||
|
return movies.map { entity ->
|
||||||
|
val cast = castMemberDao.getByMovieId(entity.id).map { it.toDomain() }
|
||||||
|
entity.toDomain(cast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getMovie(id: UUID): Movie? {
|
||||||
|
val entity = movieDao.getById(id) ?: return null
|
||||||
|
val cast = castMemberDao.getByMovieId(id).map { it.toDomain() }
|
||||||
|
return entity.toDomain(cast)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeries(): List<Series> {
|
||||||
|
return seriesDao.getAll().mapNotNull { entity -> getSeriesInternal(entity.id, includeContent = false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeriesBasic(id: UUID): Series? = getSeriesInternal(id, includeContent = false)
|
||||||
|
|
||||||
|
suspend fun getSeriesWithContent(id: UUID): Series? = getSeriesInternal(id, includeContent = true)
|
||||||
|
|
||||||
|
private suspend fun getSeriesInternal(id: UUID, includeContent: Boolean): Series? {
|
||||||
|
val entity = seriesDao.getById(id) ?: return null
|
||||||
|
val cast = castMemberDao.getBySeriesId(id).map { it.toDomain() }
|
||||||
|
val seasons = if (includeContent) {
|
||||||
|
seasonDao.getBySeriesId(id).map { seasonEntity ->
|
||||||
|
val episodes = episodeDao.getBySeasonId(seasonEntity.id).map { episodeEntity ->
|
||||||
|
val episodeCast = castMemberDao.getByEpisodeId(episodeEntity.id).map { it.toDomain() }
|
||||||
|
episodeEntity.toDomain(episodeCast)
|
||||||
|
}
|
||||||
|
seasonEntity.toDomain(episodes)
|
||||||
|
}
|
||||||
|
} else emptyList()
|
||||||
|
return entity.toDomain(seasons, cast)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeason(seriesId: UUID, seasonId: UUID): Season? {
|
||||||
|
val seasonEntity = seasonDao.getById(seasonId) ?: return null
|
||||||
|
val episodes = episodeDao.getBySeasonId(seasonId).map { episodeEntity ->
|
||||||
|
val episodeCast = castMemberDao.getByEpisodeId(episodeEntity.id).map { it.toDomain() }
|
||||||
|
episodeEntity.toDomain(episodeCast)
|
||||||
|
}
|
||||||
|
return seasonEntity.toDomain(episodes)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getSeasons(seriesId: UUID): List<Season> {
|
||||||
|
return seasonDao.getBySeriesId(seriesId).map { seasonEntity ->
|
||||||
|
val episodes = episodeDao.getBySeasonId(seasonEntity.id).map { episodeEntity ->
|
||||||
|
val episodeCast = castMemberDao.getByEpisodeId(episodeEntity.id).map { it.toDomain() }
|
||||||
|
episodeEntity.toDomain(episodeCast)
|
||||||
|
}
|
||||||
|
seasonEntity.toDomain(episodes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getEpisode(seriesId: UUID, seasonId: UUID, episodeId: UUID): Episode? {
|
||||||
|
val episodeEntity = episodeDao.getById(episodeId) ?: return null
|
||||||
|
val cast = castMemberDao.getByEpisodeId(episodeId).map { it.toDomain() }
|
||||||
|
return episodeEntity.toDomain(cast)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getEpisodeById(episodeId: UUID): Episode? {
|
||||||
|
val episodeEntity = episodeDao.getById(episodeId) ?: return null
|
||||||
|
val cast = castMemberDao.getByEpisodeId(episodeId).map { it.toDomain() }
|
||||||
|
return episodeEntity.toDomain(cast)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun updateWatchProgress(mediaId: UUID, progress: Double?, watched: Boolean) {
|
||||||
|
movieDao.getById(mediaId)?.let {
|
||||||
|
movieDao.updateProgress(mediaId, progress, watched)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
episodeDao.getById(mediaId)?.let { episode ->
|
||||||
|
database.withTransaction {
|
||||||
|
episodeDao.updateProgress(mediaId, progress, watched)
|
||||||
|
val seasonUnwatched = episodeDao.countUnwatchedBySeason(episode.seasonId)
|
||||||
|
seasonDao.updateUnwatchedCount(episode.seasonId, seasonUnwatched)
|
||||||
|
val seriesUnwatched = episodeDao.countUnwatchedBySeries(episode.seriesId)
|
||||||
|
seriesDao.updateUnwatchedCount(episode.seriesId, seriesUnwatched)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun getEpisodesBySeries(seriesId: UUID): List<Episode> {
|
||||||
|
return episodeDao.getBySeriesId(seriesId).map { episodeEntity ->
|
||||||
|
val cast = castMemberDao.getByEpisodeId(episodeEntity.id).map { it.toDomain() }
|
||||||
|
episodeEntity.toDomain(cast)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Movie.toEntity() = MovieEntity(
|
||||||
|
id = id,
|
||||||
|
libraryId = libraryId,
|
||||||
|
title = title,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
year = year,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
format = format,
|
||||||
|
synopsis = synopsis,
|
||||||
|
heroImageUrl = heroImageUrl,
|
||||||
|
audioTrack = audioTrack,
|
||||||
|
subtitles = subtitles
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Series.toEntity() = SeriesEntity(
|
||||||
|
id = id,
|
||||||
|
libraryId = libraryId,
|
||||||
|
name = name,
|
||||||
|
synopsis = synopsis,
|
||||||
|
year = year,
|
||||||
|
heroImageUrl = heroImageUrl,
|
||||||
|
unwatchedEpisodeCount = unwatchedEpisodeCount,
|
||||||
|
seasonCount = seasonCount
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Season.toEntity() = SeasonEntity(
|
||||||
|
id = id,
|
||||||
|
seriesId = seriesId,
|
||||||
|
name = name,
|
||||||
|
index = index,
|
||||||
|
unwatchedEpisodeCount = unwatchedEpisodeCount,
|
||||||
|
episodeCount = episodeCount
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun Episode.toEntity() = EpisodeEntity(
|
||||||
|
id = id,
|
||||||
|
seriesId = seriesId,
|
||||||
|
seasonId = seasonId,
|
||||||
|
index = index,
|
||||||
|
title = title,
|
||||||
|
synopsis = synopsis,
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
format = format,
|
||||||
|
heroImageUrl = heroImageUrl
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun MovieEntity.toDomain(cast: List<CastMember>) = Movie(
|
||||||
|
id = id,
|
||||||
|
libraryId = libraryId,
|
||||||
|
title = title,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
year = year,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
format = format,
|
||||||
|
synopsis = synopsis,
|
||||||
|
heroImageUrl = heroImageUrl,
|
||||||
|
audioTrack = audioTrack,
|
||||||
|
subtitles = subtitles,
|
||||||
|
cast = cast
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun SeriesEntity.toDomain(seasons: List<Season>, cast: List<CastMember>) = Series(
|
||||||
|
id = id,
|
||||||
|
libraryId = libraryId,
|
||||||
|
name = name,
|
||||||
|
synopsis = synopsis,
|
||||||
|
year = year,
|
||||||
|
heroImageUrl = heroImageUrl,
|
||||||
|
unwatchedEpisodeCount = unwatchedEpisodeCount,
|
||||||
|
seasonCount = seasonCount,
|
||||||
|
seasons = seasons,
|
||||||
|
cast = cast
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun SeasonEntity.toDomain(episodes: List<Episode>) = Season(
|
||||||
|
id = id,
|
||||||
|
seriesId = seriesId,
|
||||||
|
name = name,
|
||||||
|
index = index,
|
||||||
|
unwatchedEpisodeCount = unwatchedEpisodeCount,
|
||||||
|
episodeCount = episodeCount,
|
||||||
|
episodes = episodes
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun EpisodeEntity.toDomain(cast: List<CastMember>) = Episode(
|
||||||
|
id = id,
|
||||||
|
seriesId = seriesId,
|
||||||
|
seasonId = seasonId,
|
||||||
|
index = index,
|
||||||
|
title = title,
|
||||||
|
synopsis = synopsis,
|
||||||
|
releaseDate = releaseDate,
|
||||||
|
rating = rating,
|
||||||
|
runtime = runtime,
|
||||||
|
progress = progress,
|
||||||
|
watched = watched,
|
||||||
|
format = format,
|
||||||
|
heroImageUrl = heroImageUrl,
|
||||||
|
cast = cast
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CastMember.toMovieEntity(movieId: UUID) = CastMemberEntity(
|
||||||
|
name = name,
|
||||||
|
role = role,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
movieId = movieId
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CastMember.toSeriesEntity(seriesId: UUID) = CastMemberEntity(
|
||||||
|
name = name,
|
||||||
|
role = role,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
seriesId = seriesId
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CastMember.toEpisodeEntity(episodeId: UUID) = CastMemberEntity(
|
||||||
|
name = name,
|
||||||
|
role = role,
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
episodeId = episodeId
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun CastMemberEntity.toDomain() = CastMember(
|
||||||
|
name = name,
|
||||||
|
role = role,
|
||||||
|
imageUrl = imageUrl
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -18,7 +18,7 @@ import javax.inject.Inject
|
|||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class RoomMediaLocalDataSource @Inject constructor(
|
class RoomMediaLocalDataSource(
|
||||||
private val database: MediaDatabase,
|
private val database: MediaDatabase,
|
||||||
private val movieDao: MovieDao,
|
private val movieDao: MovieDao,
|
||||||
private val seriesDao: SeriesDao,
|
private val seriesDao: SeriesDao,
|
||||||
|
|||||||
Reference in New Issue
Block a user