mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-10 23:21:59 +01:00
address final review: coalesce sync requests, fix page-cap edge
- SyncManager now uses a conflated request channel instead of tryLock, so a request that arrives during a sync (e.g. add-source switching the active server) is queued and runs afterwards against the then-active source instead of being dropped. - Paged loops count raw server rows, not accepted entities, so an empty-id row on an otherwise full page no longer ends the scan early and prunes the unseen tail. - The cap now fails when the page just past it is non-empty, not only when it is full, matching the bound the error message reports. - fetchRank rethrows the cap instead of downgrading an over-cap album scan to empty ranks. - Docs: the mutex lives in SyncManager, and the prune still holds the existing-id set.
This commit is contained in:
@@ -224,6 +224,52 @@ class SubsonicSourceTest {
|
||||
assertEquals(1000, albums.size)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun albumsAbortWhenThePageJustOverTheCapIsNotEmpty() =
|
||||
runBlocking {
|
||||
server.dispatcher =
|
||||
object : Dispatcher() {
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
val url = request.requestUrl!!
|
||||
if (url.encodedPath != "/rest/getAlbumList2.view") return MockResponse().setResponseCode(404)
|
||||
if (url.queryParameter("type") != "newest") return MockResponse().setBody(emptyAlbumList())
|
||||
return when (url.queryParameter("offset")) {
|
||||
"0", "500" -> MockResponse().setBody(albumPage(url.queryParameter("offset")!!.toInt(), 500))
|
||||
else -> MockResponse().setBody(albumPage(1000, 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val failure =
|
||||
runCatching {
|
||||
SubsonicSource(1, client(), maxPages = 2).albums().toList()
|
||||
}
|
||||
|
||||
assertEquals(true, failure.exceptionOrNull() is SubsonicException)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyIdRowsDoNotEndPaginationEarly() =
|
||||
runBlocking {
|
||||
server.dispatcher =
|
||||
object : Dispatcher() {
|
||||
override fun dispatch(request: RecordedRequest): MockResponse {
|
||||
val url = request.requestUrl!!
|
||||
if (url.encodedPath != "/rest/getAlbumList2.view") return MockResponse().setResponseCode(404)
|
||||
if (url.queryParameter("type") != "newest") return MockResponse().setBody(emptyAlbumList())
|
||||
return when (url.queryParameter("offset")) {
|
||||
"0" -> MockResponse().setBody(albumPageWithEmptyId(0, 500))
|
||||
else -> MockResponse().setBody(albumPage(500, 3))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val albums = SubsonicSource(1, client()).albums().toList().flatten()
|
||||
|
||||
assertEquals(502, albums.size)
|
||||
assertEquals("al-502", albums.last().id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun playlistEntriesWithoutIdsAreSkipped() =
|
||||
runBlocking {
|
||||
@@ -293,6 +339,16 @@ class SubsonicSourceTest {
|
||||
} +
|
||||
"</albumList2></subsonic-response>"
|
||||
|
||||
private fun albumPageWithEmptyId(
|
||||
offset: Int,
|
||||
count: Int,
|
||||
) = "<subsonic-response status=\"ok\"><albumList2>" +
|
||||
"<album id=\"\" name=\"No id\" artist=\"Artist\" artistId=\"ar1\" songCount=\"1\"/>" +
|
||||
(offset until offset + count - 1).joinToString("") {
|
||||
"<album id=\"al-$it\" name=\"Album $it\" artist=\"Artist\" artistId=\"ar1\" songCount=\"1\"/>"
|
||||
} +
|
||||
"</albumList2></subsonic-response>"
|
||||
|
||||
private fun songPage(
|
||||
prefix: String,
|
||||
offset: Int,
|
||||
|
||||
Reference in New Issue
Block a user