diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt index b29fc2cf..b650364c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/MangaAPI.kt @@ -112,7 +112,7 @@ object MangaAPI { } path("update") { - get("recentChapters", UpdateController::recentChapters) + get("recentChapters/{pageNum}", UpdateController::recentChapters) } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt index e464bab7..57609f5d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt @@ -14,9 +14,11 @@ import suwayomi.tachidesk.server.JavalinSetup.future object UpdateController { /** get recently updated manga chapters */ fun recentChapters(ctx: Context) { + val pageNum = ctx.pathParam("pageNum").toInt() + ctx.future( future { - Chapter.getRecentChapters() + Chapter.getRecentChapters(pageNum) } ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt index 0447588d..8fe4b5df 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -27,6 +27,8 @@ import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogue import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass +import suwayomi.tachidesk.manga.model.dataclass.PaginatedList +import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom import suwayomi.tachidesk.manga.model.table.ChapterMetaTable import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.MangaTable @@ -321,17 +323,19 @@ object Chapter { } } - fun getRecentChapters(): List { - return transaction { - (ChapterTable innerJoin MangaTable) - .select { (MangaTable.inLibrary eq true) and (ChapterTable.fetchedAt greater MangaTable.inLibraryAt) } - .orderBy(ChapterTable.fetchedAt to SortOrder.DESC) - .map { - MangaChapterDataClass( - MangaTable.toDataClass(it), - ChapterTable.toDataClass(it) - ) - } + fun getRecentChapters(pageNum: Int): PaginatedList { + return paginatedFrom(pageNum) { + transaction { + (ChapterTable innerJoin MangaTable) + .select { (MangaTable.inLibrary eq true) and (ChapterTable.fetchedAt greater MangaTable.inLibraryAt) } + .orderBy(ChapterTable.fetchedAt to SortOrder.DESC) + .map { + MangaChapterDataClass( + MangaTable.toDataClass(it), + ChapterTable.toDataClass(it) + ) + } + } } } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt new file mode 100644 index 00000000..1466351d --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/PaginatedList.kt @@ -0,0 +1,40 @@ +package suwayomi.tachidesk.manga.model.dataclass + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import kotlin.math.min + +data class PaginatedList( + val page: List, + val hasNextPage: Boolean, +) + +const val PaginationFactor = 50 + +fun paginatedFrom( + pageNum: Int, + paginationFactor: Int = PaginationFactor, + lister: () -> List +): PaginatedList { + val list = lister() + val lastIndex = list.size - 1 + + val lowerIndex = pageNum * paginationFactor + val higherIndex = (pageNum + 1) * paginationFactor - 1 + + if (lowerIndex > lastIndex) { + return PaginatedList(emptyList(), false) + } + + val sliced = list.slice(lowerIndex..min(lastIndex, higherIndex)) + + return PaginatedList( + sliced, + higherIndex < lastIndex + ) +} diff --git a/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt b/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt new file mode 100644 index 00000000..a429c3bb --- /dev/null +++ b/server/src/test/kotlin/suwayomi/tachidesk/manga/model/PaginatedListTest.kt @@ -0,0 +1,105 @@ +package suwayomi.tachidesk.manga.model + +/* + * Copyright (C) Contributors to the Suwayomi project + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import suwayomi.tachidesk.manga.model.dataclass.PaginatedList +import suwayomi.tachidesk.manga.model.dataclass.PaginationFactor +import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom +import suwayomi.tachidesk.test.ApplicationTest + +class PaginatedListTest : ApplicationTest() { + @Test + fun `empty list`() { + val paginated = paginatedFrom(0) { listIndicesOf(0, 0) } + + assertEquals( + PaginatedList(emptyList(), false), + paginated + ) + } + + @Test + fun `size smaller than PaginationFactor`() { + val paginated = paginatedFrom(0) { listIndicesOf(0, PaginationFactor - 1) } + + assertEquals( + PaginatedList(listIndicesOf(0, PaginationFactor - 1), false), + paginated, + ) + } + + @Test + fun `one less than two times PaginationFactor`() { + val masterLister = { listIndicesOf(0, PaginationFactor * 2 - 1) } + + val firstPage = paginatedFrom(0, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(0, PaginationFactor), true), + firstPage, + ) + + val secondPage = paginatedFrom(1, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(PaginationFactor, PaginationFactor * 2 - 1), false), + secondPage, + ) + } + + @Test + fun `two times PaginationFactor`() { + val masterLister = { listIndicesOf(0, PaginationFactor * 2) } + + val firstPage = paginatedFrom(0, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(0, PaginationFactor), true), + firstPage, + ) + + val secondPage = paginatedFrom(1, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(PaginationFactor, PaginationFactor * 2), false), + secondPage, + ) + } + + @Test + fun `one more than two times PaginationFactor`() { + val masterLister = { listIndicesOf(0, PaginationFactor * 2 + 1) } + + val firstPage = paginatedFrom(0, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(0, PaginationFactor), true), + firstPage, + ) + + val secondPage = paginatedFrom(1, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(PaginationFactor, PaginationFactor * 2), true), + secondPage, + ) + + val thirdPage = paginatedFrom(2, lister = masterLister) + + assertEquals( + PaginatedList(listIndicesOf(PaginationFactor * 2, PaginationFactor * 2 + 1), false), + thirdPage, + ) + } + + private fun listIndicesOf(first: Int, last: Int): List { + return (first until last).toList() + } +}