Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Programme strategy #199

Merged
merged 2 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ object Profiles {
val VIEWMODEL_PLAYLIST = Profile("viewmodel-playlist")
val VIEWMODEL_SETTING = Profile("viewmodel-setting")
val VIEWMODEL_CHANNEL = Profile("viewmodel-channel")
val VIEWMODEL_PLAYLIST_CONFIGURATION = Profile("viewmodel-playlist-configuration", Message.LEVEL_INFO)
val VIEWMODEL_PLAYLIST_CONFIGURATION = Profile("viewmodel-playlist-configuration")

val REPOS_PLAYLIST = Profile("repos-playlist")
val REPOS_PLAYLIST = Profile("repos-playlist", Message.LEVEL_INFO)
val REPOS_CHANNEL = Profile("repos-channel")
val REPOS_PROGRAMME = Profile("repos-programme")
val REPOS_LEANBACK = Profile("repos-tv")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import androidx.room.Query
import androidx.room.Transaction
import com.m3u.data.database.model.DataSource
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.PlaylistWithCount
import com.m3u.data.database.model.PlaylistWithChannels
import com.m3u.data.database.model.PlaylistWithCount
import kotlinx.coroutines.flow.Flow

@Dao
Expand Down
29 changes: 24 additions & 5 deletions data/src/main/java/com/m3u/data/database/dao/ProgrammeDao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ interface ProgrammeDao {
@Query(
"""
SELECT * FROM programmes
WHERE epg_url in (:epgUrls)
WHERE epg_url = :epgUrl
AND
relation_id = :relationId
ORDER BY start
"""
)
fun pagingByEpgUrlsAndRelationId(
epgUrls: List<String>,
fun pagingProgrammes(
epgUrl: String?,
relationId: String
): PagingSource<Int, Programme>

Expand All @@ -40,6 +40,25 @@ interface ProgrammeDao {
@Query("SELECT * FROM programmes ORDER BY start")
fun observeAll(): Flow<List<Programme>>

@Query("SELECT id IS NOT NULL FROM programmes WHERE epg_url = :epgUrl LIMIT 1")
fun observeContainsEpgUrl(epgUrl: String): Flow<Boolean>

@Query("""
SELECT id IS NOT NULL
FROM programmes
WHERE epg_url = :epgUrl
AND relation_id = :relationId
AND start >= :start
AND `end` <= :end
LIMIT 1
""")
suspend fun checkEpgUrlIsValid(
epgUrl: String,
relationId: String,
start: Long,
end: Long,
): Boolean

@Query("DELETE FROM programmes WHERE epg_url = :epgUrl")
suspend fun deleteAllByEpgUrl(epgUrl: String)

Expand All @@ -62,12 +81,12 @@ interface ProgrammeDao {
"""
SELECT MIN(start) AS start_edge, MAX(`end`) AS end_edge
FROM programmes
WHERE epg_url in (:epgUrls)
WHERE epg_url = :epgUrl
AND relation_id = :relationId
"""
)
fun observeProgrammeRange(
epgUrls: List<String>,
epgUrl: String,
relationId: String
): Flow<ProgrammeRange>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,19 @@ interface PlaylistRepository {

suspend fun deleteEpgPlaylistAndProgrammes(epgUrl: String)

suspend fun onUpdateEpgPlaylist(useCase: UpdateEpgPlaylistUseCase)
suspend fun onUpdateEpgPlaylist(useCase: EpgPlaylistUseCase)
suspend fun onUpdatePlaylistAutoRefreshProgrammes(playlistUrl: String)

@Immutable
data class UpdateEpgPlaylistUseCase(
val playlistUrl: String,
val epgUrl: String,
val action: Boolean
)
sealed interface EpgPlaylistUseCase {
data class Check(
val playlistUrl: String,
val epgUrl: String,
val action: Boolean
): EpgPlaylistUseCase
data class Upward(
val playlistUrl: String,
val epgUrl: String
): EpgPlaylistUseCase
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -595,17 +595,25 @@ internal class PlaylistRepositoryImpl @Inject constructor(
playlistDao.removeEpgUrlForAllPlaylists(epgUrl)
}

override suspend fun onUpdateEpgPlaylist(useCase: PlaylistRepository.UpdateEpgPlaylistUseCase) {
val playlist = checkNotNull(playlistDao.get(useCase.playlistUrl)) {
"Cannot find playlist before update associated epg"
}
val epg = checkNotNull(playlistDao.get(useCase.epgUrl)) {
"Cannot find associated epg"
}
override suspend fun onUpdateEpgPlaylist(useCase: PlaylistRepository.EpgPlaylistUseCase) {
when (useCase) {
is PlaylistRepository.EpgPlaylistUseCase.Check -> {
playlistDao.updateEpgUrls(useCase.playlistUrl) { epgUrls ->
if (useCase.action) epgUrls + useCase.epgUrl
else epgUrls - useCase.epgUrl
}
}

playlistDao.updateEpgUrls(playlist.url) { epgUrls ->
if (useCase.action) epgUrls + epg.url
else epgUrls - epg.url
is PlaylistRepository.EpgPlaylistUseCase.Upward -> {
val epgUrl = useCase.epgUrl
playlistDao.updateEpgUrls(useCase.playlistUrl) { epgUrls ->
val index = epgUrls.indexOf(epgUrl)
if (index <= 0) epgUrls
else with(epgUrls) {
take(index - 1) + epgUrl + this[index - 1] + drop(index + 1)
}
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package com.m3u.data.repository.programme

import androidx.paging.PagingSource
import androidx.paging.PagingData
import com.m3u.data.database.model.Programme
import com.m3u.data.database.model.ProgrammeRange
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow

interface ProgrammeRepository {
fun pagingByEpgUrlsAndRelationId(
epgUrls: List<String>,
fun pagingProgrammes(
playlistUrl: String,
relationId: String
): PagingSource<Int, Programme>
): Flow<PagingData<Programme>>

fun observeProgrammeRange(
playlistUrl: String,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.m3u.data.repository.programme

import androidx.paging.PagingSource
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import com.m3u.core.architecture.dispatcher.Dispatcher
import com.m3u.core.architecture.dispatcher.M3uDispatchers.IO
import com.m3u.core.architecture.logger.Logger
Expand Down Expand Up @@ -36,6 +38,7 @@ import okhttp3.OkHttpClient
import okhttp3.Request
import java.util.zip.GZIPInputStream
import javax.inject.Inject
import kotlin.time.Duration.Companion.days

internal class ProgrammeRepositoryImpl @Inject constructor(
private val playlistDao: PlaylistDao,
Expand All @@ -49,20 +52,29 @@ internal class ProgrammeRepositoryImpl @Inject constructor(
private val logger = delegate.install(Profiles.REPOS_PROGRAMME)
override val refreshingEpgUrls = MutableStateFlow<List<String>>(emptyList())

override fun pagingByEpgUrlsAndRelationId(
epgUrls: List<String>,
override fun pagingProgrammes(
playlistUrl: String,
relationId: String
): PagingSource<Int, Programme> = programmeDao.pagingByEpgUrlsAndRelationId(epgUrls, relationId)
): Flow<PagingData<Programme>> = playlistDao
.observeByUrl(playlistUrl)
.map { playlist -> playlist?.epgUrlsOrXtreamXmlUrl() ?: emptyList() }
.map { epgUrls -> findValidEpgUrl(epgUrls, relationId, defaultProgrammeRange) }
.flatMapLatest { epgUrl ->
Pager(PagingConfig(15)) { programmeDao.pagingProgrammes(epgUrl, relationId) }.flow
}

override fun observeProgrammeRange(
playlistUrl: String,
relationId: String
): Flow<ProgrammeRange> = playlistDao.observeByUrl(playlistUrl).flatMapLatest { playlist ->
playlist ?: return@flatMapLatest flowOf()
programmeDao
.observeProgrammeRange(playlist.epgUrlsOrXtreamXmlUrl(), relationId)
.filterNot { (start, end) -> start == 0L || end == 0L }
}
): Flow<ProgrammeRange> = playlistDao.observeByUrl(playlistUrl)
.map { playlist -> playlist?.epgUrlsOrXtreamXmlUrl() ?: emptyList() }
.map { epgUrls -> findValidEpgUrl(epgUrls, relationId, defaultProgrammeRange) }
.flatMapLatest { epgUrl ->
epgUrl ?: return@flatMapLatest flowOf()
programmeDao
.observeProgrammeRange(epgUrl, relationId)
.filterNot { (start, end) -> start == 0L || end == 0L }
}

override fun observeProgrammeRange(playlistUrl: String): Flow<ProgrammeRange> =
playlistDao.observeByUrl(playlistUrl)
Expand All @@ -73,6 +85,14 @@ internal class ProgrammeRepositoryImpl @Inject constructor(
programmeDao.observeProgrammeRange(epgUrls)
}

private val defaultProgrammeRange: ProgrammeRange
get() = with(Clock.System.now()) {
ProgrammeRange(
this.minus(1.days).toEpochMilliseconds(),
this.plus(1.days).toEpochMilliseconds()
)
}

override fun checkOrRefreshProgrammesOrThrow(
vararg playlistUrls: String,
ignoreCache: Boolean
Expand All @@ -99,7 +119,7 @@ internal class ProgrammeRepositoryImpl @Inject constructor(
}

override suspend fun getProgrammeCurrently(channelId: Int): Programme? {
val channel = channelDao.get(channelId)?: return null
val channel = channelDao.get(channelId) ?: return null
val relationId = channel.relationId ?: return null
val playlist = playlistDao.get(channel.playlistUrl) ?: return null

Expand Down Expand Up @@ -172,4 +192,26 @@ internal class ProgrammeRepositoryImpl @Inject constructor(
}
}
.flowOn(ioDispatcher)

/**
* Attempts to find the first valid EPG URL from a list of URLs.
*
* This function iterates over the provided list of EPG URLs and checks
* if each URL is valid by querying from the database. The validity check
* uses the `relationId` and the start and end times from the `ProgrammeRange`.
* The first valid EPG URL found is returned. If no valid URLs are found,
* the function returns null.
*
* @param epgUrls A list of EPG URLs to check.
* @param relationId A unique identifier representing the relation for the EPG.
* @param range A `ProgrammeRange` object containing the start and end times to validate against.
* @return The first valid EPG URL, or null if none are valid.
*/
private suspend fun findValidEpgUrl(
epgUrls: List<String>,
relationId: String,
range: ProgrammeRange
): String? = epgUrls.firstOrNull { epgUrl ->
programmeDao.checkEpgUrlIsValid(epgUrl, relationId, range.start, range.end)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ import com.m3u.data.database.model.DataSource
import com.m3u.data.database.model.Playlist
import com.m3u.data.database.model.Programme
import com.m3u.data.database.model.ProgrammeRange
import com.m3u.data.database.model.epgUrlsOrXtreamXmlUrl
import com.m3u.data.database.model.isSeries
import com.m3u.data.database.model.isVod
import com.m3u.data.repository.channel.ChannelRepository
Expand Down Expand Up @@ -299,16 +298,12 @@ class ChannelViewModel @Inject constructor(
val relationId = channel.relationId ?: return@flatMapLatest flowOf(PagingData.empty())
val playlist = channel.playlistUrl.let { playlistRepository.get(it) }
playlist ?: return@flatMapLatest flowOf(PagingData.empty())
val epgUrls = playlist.epgUrlsOrXtreamXmlUrl()
Pager(PagingConfig(15)) {
programmeRepository.pagingByEpgUrlsAndRelationId(
epgUrls = epgUrls,
relationId = relationId
)
}
.flow
programmeRepository.pagingProgrammes(
playlistUrl = playlist.url,
relationId = relationId
)
.cachedIn(viewModelScope)
}
.cachedIn(viewModelScope)

private val defaultProgrammeRange: ProgrammeRange
get() = with(Clock.System.now()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,21 +213,15 @@ internal fun ProgramGuide(
)
}

// background
items(
count = 1,
layoutInfo = {
MinaBoxItem(
x = 0f,
y = 0f,
width = constraints.maxWidth.toFloat(),
height = with(range) {
(currentHeight * (end - start) / HOUR_LENGTH - padding)
}
)
}
) {
}
// current timeline background placeholder
items(1, layoutInfo = {
MinaBoxItem(
x = 0f,
y = currentTimelineOffset + padding * 2,
width = constraints.maxWidth.toFloat(),
height = constraints.maxHeight.toFloat()
)
}) {}
}

if (!tv) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import com.m3u.feature.foryou.components.PlaylistGallery
import com.m3u.feature.foryou.components.recommend.Recommend
import com.m3u.feature.foryou.components.recommend.RecommendGallery
import com.m3u.i18n.R.string
import com.m3u.material.ktx.composableOf
import com.m3u.material.ktx.interceptVolumeEvent
import com.m3u.material.ktx.tv
import com.m3u.material.ktx.thenIf
Expand Down Expand Up @@ -230,7 +231,7 @@ private fun ForyouScreen(
refreshingEpgUrls = refreshingEpgUrls,
onClick = navigateToPlaylist,
onLongClick = { mediaSheetValue = MediaSheetValue.ForyouScreen(it) },
header = header.takeIf { specs.isNotEmpty() },
header = composableOf(specs.isNotEmpty(), header),
contentPadding = contentPadding,
modifier = Modifier.fillMaxSize()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ private fun PlaylistConfigurationScreen(
xtreamUserInfo: Resource<XtreamInfo.UserInfo>,
onUpdatePlaylistTitle: (String) -> Unit,
onUpdatePlaylistUserAgent: (String?) -> Unit,
onUpdateEpgPlaylist: (PlaylistRepository.UpdateEpgPlaylistUseCase) -> Unit,
onUpdateEpgPlaylist: (PlaylistRepository.EpgPlaylistUseCase) -> Unit,
onUpdatePlaylistAutoRefreshProgrammes: () -> Unit,
onSyncProgrammes: () -> Unit,
onCancelSyncProgrammes: () -> Unit,
Expand Down
Loading
Loading