channels: Parallelize stats and persist them
The channels screen enumerated every channel and then fetched each channel's stats strictly one after another, with a row rebuild and a row re-save after every single one — with several dozen local channels, each stat call walking up to four video pages, that took minutes. Stats now fetch in parallel batches of six and are persisted alongside the rows snapshot, so an existing install paints the channels screen with full stats instantly. Closes #9
This commit is contained in:
parent
01c08e1dbb
commit
3d5fd44a59
2 changed files with 54 additions and 10 deletions
|
|
@ -67,6 +67,30 @@ object Snapshots {
|
|||
write(context, "chan-" + handle, json.encodeToString(VideoChannelDto.serializer(), channel))
|
||||
}
|
||||
|
||||
fun saveChannelStats(context: Context, baseUrlKey: String, stats: Map<String, net.jeena.jtubetv.data.api.ChannelStats>) = runCatching {
|
||||
write(
|
||||
context,
|
||||
"chanstats-" + baseUrlKey,
|
||||
json.encodeToString(
|
||||
kotlinx.serialization.serializer<Map<String, net.jeena.jtubetv.data.api.ChannelStats>>(),
|
||||
stats,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun loadChannelStats(
|
||||
context: Context,
|
||||
baseUrlKey: String,
|
||||
): Map<String, net.jeena.jtubetv.data.api.ChannelStats> =
|
||||
read(context, "chanstats-" + baseUrlKey)?.let { raw ->
|
||||
runCatching {
|
||||
json.decodeFromString(
|
||||
kotlinx.serialization.serializer<Map<String, net.jeena.jtubetv.data.api.ChannelStats>>(),
|
||||
raw,
|
||||
)
|
||||
}.getOrNull()
|
||||
} ?: emptyMap()
|
||||
|
||||
fun saveContinue(context: Context, key: String, items: List<net.jeena.jtubetv.ui.screens.ContinueItem>) = runCatching {
|
||||
write(
|
||||
context,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,8 @@ import androidx.tv.material3.Card
|
|||
import androidx.tv.material3.CardDefaults
|
||||
import coil.compose.AsyncImage
|
||||
import android.util.Log
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import net.jeena.jtubetv.AppGraph
|
||||
|
|
@ -125,11 +127,16 @@ class ChannelsViewModel(private val store: TokenStore) : ViewModel() {
|
|||
|
||||
init {
|
||||
// Snapshot restore (issue #9): paint the last session's channel rows
|
||||
// instantly; the streaming reload below then updates stats in place.
|
||||
// instantly, including the per-channel stats (the "minutes of loading"
|
||||
// complaint) — the streaming reload below then updates them in place.
|
||||
if (snapshotRows != null) {
|
||||
userRows = snapshotRows
|
||||
loading = false
|
||||
}
|
||||
if (snapshotContext != null) {
|
||||
statsMap.putAll(Snapshots.loadChannelStats(snapshotContext, snapshotKey))
|
||||
if (statsMap.isNotEmpty()) stats = statsMap.toMap()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -188,19 +195,32 @@ class ChannelsViewModel(private val store: TokenStore) : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
/** Fetch stats for [channels], re-sorting rows as data lands. */
|
||||
/**
|
||||
* Fetch stats for [channels] in parallel batches of 6 (each stat call walks
|
||||
* up to 4 video pages — strictly sequential with ~40 channels took minutes),
|
||||
* re-sorting the rows whenever a batch lands. Persisted per batch so the
|
||||
* next start starts warm.
|
||||
*/
|
||||
private fun channelStatsLoop(channels: List<VideoChannelDto>, host: String?) {
|
||||
val missing = channels.filter { !statsMap.containsKey(it.name + "@" + it.host) }
|
||||
viewModelScope.launch {
|
||||
for (channel in channels) {
|
||||
val key = channel.name + "@" + channel.host
|
||||
if (statsMap.containsKey(key)) continue
|
||||
val cs = repo.channelStats(key).getOrNull()
|
||||
if (cs == null) { android.util.Log.w("JTube", "stats FAIL " + key); continue }
|
||||
android.util.Log.w("JTube", "stats " + key + " videos=" + cs.videos + " plays=" + cs.plays)
|
||||
statsMap[key] = cs
|
||||
missing.chunked(6).forEach { batch ->
|
||||
batch.map { channel ->
|
||||
async {
|
||||
val key = channel.name + "@" + channel.host
|
||||
val cs = repo.channelStats(key).getOrNull()
|
||||
if (cs != null) Pair(key, cs) else null
|
||||
}
|
||||
}.awaitAll().filterNotNull().forEach { (key, cs) ->
|
||||
statsMap[key] = cs
|
||||
}
|
||||
stats = statsMap.toMap()
|
||||
rebuildRows(host)
|
||||
delay(120)
|
||||
snapshotContext?.let { ctx ->
|
||||
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
|
||||
Snapshots.saveChannelStats(ctx, snapshotKey, statsMap.toMap())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue