mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-16 20:40:30 +00:00
fix(simkl): fetch historical episode state
This commit is contained in:
parent
002a54e022
commit
b4eba6e0c5
3 changed files with 62 additions and 45 deletions
|
|
@ -16,18 +16,11 @@ internal class SimklSyncEngine(
|
|||
}
|
||||
if (current.watermark == null) return initialSync()
|
||||
|
||||
val delta = remote.fetchAllItems(
|
||||
SimklAllItemsRequest(
|
||||
dateFrom = current.watermark,
|
||||
includeEpisodeDetails = true,
|
||||
),
|
||||
)
|
||||
val delta = remote.fetchAllItems(SimklAllItemsRequest.Changes(current.watermark))
|
||||
var entries = mergeDelta(current.entries, delta)
|
||||
|
||||
if (hasRemovalActivityChanged(current.activities, activities)) {
|
||||
val authoritativeIds = remote.fetchAllItems(
|
||||
SimklAllItemsRequest(idsOnly = true),
|
||||
)
|
||||
val authoritativeIds = remote.fetchAllItems(SimklAllItemsRequest.CurrentIds)
|
||||
entries = reconcileRemovedEntries(entries, authoritativeIds)
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +45,7 @@ internal class SimklSyncEngine(
|
|||
val entries = buildList {
|
||||
SimklMediaType.entries.forEach { type ->
|
||||
addAll(
|
||||
remote.fetchAllItems(SimklAllItemsRequest(type = type))
|
||||
remote.fetchAllItems(SimklAllItemsRequest.Bootstrap(type))
|
||||
.entriesFor(type),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,23 @@ import kotlinx.serialization.json.JsonArray
|
|||
import kotlinx.serialization.json.JsonNull
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
|
||||
internal data class SimklAllItemsRequest(
|
||||
val type: SimklMediaType? = null,
|
||||
val dateFrom: String? = null,
|
||||
val includeEpisodeDetails: Boolean = false,
|
||||
val idsOnly: Boolean = false,
|
||||
)
|
||||
internal sealed interface SimklAllItemsRequest {
|
||||
val type: SimklMediaType?
|
||||
|
||||
data class Bootstrap(
|
||||
override val type: SimklMediaType,
|
||||
) : SimklAllItemsRequest
|
||||
|
||||
data class Changes(
|
||||
val dateFrom: String,
|
||||
) : SimklAllItemsRequest {
|
||||
override val type: SimklMediaType? = null
|
||||
}
|
||||
|
||||
data object CurrentIds : SimklAllItemsRequest {
|
||||
override val type: SimklMediaType? = null
|
||||
}
|
||||
}
|
||||
|
||||
internal interface SimklSyncRemote {
|
||||
suspend fun fetchActivities(): SimklActivities
|
||||
|
|
@ -38,17 +49,22 @@ internal class SimklApiSyncRemote(
|
|||
override suspend fun fetchAllItems(request: SimklAllItemsRequest): SimklAllItemsResponse {
|
||||
val path = request.type?.let { type -> "/sync/all-items/${type.apiValue}" }
|
||||
?: "/sync/all-items"
|
||||
val query = buildMap {
|
||||
request.dateFrom?.let { value -> put("date_from", value) }
|
||||
when {
|
||||
request.idsOnly -> put("extended", "simkl_ids_only")
|
||||
request.includeEpisodeDetails -> {
|
||||
put("extended", "full_anime_seasons")
|
||||
put("episode_watched_at", "yes")
|
||||
put("episode_tvdb_id", "yes")
|
||||
put("include_all_episodes", "original")
|
||||
}
|
||||
}
|
||||
val query = when (request) {
|
||||
is SimklAllItemsRequest.Bootstrap -> mapOf(
|
||||
"extended" to "full",
|
||||
"episode_watched_at" to "yes",
|
||||
"include_all_episodes" to "yes",
|
||||
)
|
||||
is SimklAllItemsRequest.Changes -> mapOf(
|
||||
"date_from" to request.dateFrom,
|
||||
"extended" to "full_anime_seasons",
|
||||
"episode_watched_at" to "yes",
|
||||
"episode_tvdb_id" to "yes",
|
||||
"include_all_episodes" to "yes",
|
||||
)
|
||||
SimklAllItemsRequest.CurrentIds -> mapOf(
|
||||
"extended" to "simkl_ids_only",
|
||||
)
|
||||
}
|
||||
return client.execute(
|
||||
SimklApiRequest(
|
||||
|
|
|
|||
|
|
@ -50,9 +50,14 @@ class SimklSyncEngineTest {
|
|||
assertEquals(1, result.playback.size)
|
||||
assertEquals(500L, result.lastSyncedAtEpochMs)
|
||||
assertTrue(remote.isExhausted)
|
||||
assertTrue(remote.allItemsRequests.all { request ->
|
||||
request.dateFrom == null && !request.includeEpisodeDetails && !request.idsOnly
|
||||
})
|
||||
assertEquals(
|
||||
listOf<SimklAllItemsRequest>(
|
||||
SimklAllItemsRequest.Bootstrap(SimklMediaType.SHOWS),
|
||||
SimklAllItemsRequest.Bootstrap(SimklMediaType.MOVIES),
|
||||
SimklAllItemsRequest.Bootstrap(SimklMediaType.ANIME),
|
||||
),
|
||||
remote.allItemsRequests,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -112,9 +117,13 @@ class SimklSyncEngineTest {
|
|||
)
|
||||
assertEquals("3", result.playback.single().media?.ids?.idValue("simkl"))
|
||||
assertEquals("v2", result.watermark)
|
||||
assertTrue(remote.allItemsRequests[0].includeEpisodeDetails)
|
||||
assertEquals("v1", remote.allItemsRequests[0].dateFrom)
|
||||
assertTrue(remote.allItemsRequests[1].idsOnly)
|
||||
assertEquals(
|
||||
listOf<SimklAllItemsRequest>(
|
||||
SimklAllItemsRequest.Changes("v1"),
|
||||
SimklAllItemsRequest.CurrentIds,
|
||||
),
|
||||
remote.allItemsRequests,
|
||||
)
|
||||
assertTrue(remote.isExhausted)
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +148,7 @@ class SimklSyncEngineTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `remote keeps initial pull minimal and adds rich flags only to dated delta`() = runBlocking {
|
||||
fun `remote applies documented bootstrap delta and reconciliation parameters`() = runBlocking {
|
||||
var now = 0L
|
||||
val urls = mutableListOf<String>()
|
||||
val engine = SimklHttpEngine { _, url, _, _ ->
|
||||
|
|
@ -156,21 +165,20 @@ class SimklSyncEngineTest {
|
|||
)
|
||||
val remote = SimklApiSyncRemote(client)
|
||||
|
||||
remote.fetchAllItems(SimklAllItemsRequest(type = SimklMediaType.SHOWS))
|
||||
remote.fetchAllItems(
|
||||
SimklAllItemsRequest(
|
||||
dateFrom = "2026-05-08T14:23:11Z",
|
||||
includeEpisodeDetails = true,
|
||||
),
|
||||
)
|
||||
remote.fetchAllItems(SimklAllItemsRequest(idsOnly = true))
|
||||
remote.fetchAllItems(SimklAllItemsRequest.Bootstrap(SimklMediaType.SHOWS))
|
||||
remote.fetchAllItems(SimklAllItemsRequest.Changes("2026-05-08T14:23:11Z"))
|
||||
remote.fetchAllItems(SimklAllItemsRequest.CurrentIds)
|
||||
|
||||
assertFalse("date_from=" in urls[0])
|
||||
assertFalse("extended=" in urls[0])
|
||||
assertTrue("extended=full" in urls[0])
|
||||
assertTrue("episode_watched_at=yes" in urls[0])
|
||||
assertTrue("include_all_episodes=yes" in urls[0])
|
||||
assertFalse("episode_tvdb_id=" in urls[0])
|
||||
assertTrue("date_from=2026-05-08T14%3A23%3A11Z" in urls[1])
|
||||
assertTrue("extended=full_anime_seasons" in urls[1])
|
||||
assertTrue("episode_watched_at=yes" in urls[1])
|
||||
assertTrue("include_all_episodes=original" in urls[1])
|
||||
assertTrue("episode_tvdb_id=yes" in urls[1])
|
||||
assertTrue("include_all_episodes=yes" in urls[1])
|
||||
assertTrue("extended=simkl_ids_only" in urls[2])
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +198,7 @@ class SimklSyncEngineTest {
|
|||
)
|
||||
|
||||
val response = SimklApiSyncRemote(client).fetchAllItems(
|
||||
SimklAllItemsRequest(type = SimklMediaType.ANIME),
|
||||
SimklAllItemsRequest.Bootstrap(SimklMediaType.ANIME),
|
||||
)
|
||||
|
||||
assertTrue(response.entriesFor(SimklMediaType.ANIME).isEmpty())
|
||||
|
|
|
|||
Loading…
Reference in a new issue