mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 03:14:40 -05:00
Compare commits
4 Commits
762d5bdbe6
...
c0618fcc5c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c0618fcc5c | ||
|
|
9686f75a2d | ||
|
|
4d5307f15b | ||
|
|
779229a48a |
@@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
||||
- (**WebUI**) Handle serving non-default webui with "bundled"
|
||||
- (**WebUI**) Wait until WebUI is ready to open in browser
|
||||
- (**Downloads**) Truncate filenames by byte length to prevent "File name too long" IO errors
|
||||
- (**Downloads**) Fix being unable to find downloads after manga was renamed during an update
|
||||
- (**Downloads**) Fix preserving chapter download states during an update
|
||||
- (**Extension**) Do not indicate an update is available when the extension is not installed
|
||||
- (**Chapter**) Fix losing chapter data on failed chapter list update
|
||||
- (**Chapter**) Fix database error when fetching chapter updates
|
||||
|
||||
@@ -88,4 +88,6 @@ object SettingsRegistry {
|
||||
fun get(name: String): SettingMetadata? = settings[name]
|
||||
|
||||
fun getAll(): Map<String, SettingMetadata> = settings.toMap()
|
||||
|
||||
fun clear() = settings.clear()
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ object Chapter {
|
||||
val deletedChapterNumbers = TreeSet<Float>()
|
||||
val deletedReadChapterNumbers = TreeSet<Float>()
|
||||
val deletedBookmarkedChapterNumbers = TreeSet<Float>()
|
||||
val deletedDownloadedChapterNumberInfoMap = mutableMapOf<Float, MutableMap<String?, Int>>()
|
||||
val deletedDownloadedChapterNumberToChapter = mutableMapOf<Float, ChapterDataClass>()
|
||||
val deletedChapterNumberDateFetchMap = mutableMapOf<Float, Long>()
|
||||
|
||||
// clear any orphaned/duplicate chapters that are in the db but not in `chapterList`
|
||||
@@ -247,13 +247,7 @@ object Chapter {
|
||||
if (!chapterUrls.contains(dbChapter.url)) {
|
||||
if (dbChapter.read) deletedReadChapterNumbers.add(dbChapter.chapterNumber)
|
||||
if (dbChapter.bookmarked) deletedBookmarkedChapterNumbers.add(dbChapter.chapterNumber)
|
||||
if (dbChapter.downloaded) {
|
||||
val pageCountByScanlator =
|
||||
deletedDownloadedChapterNumberInfoMap.getOrPut(
|
||||
dbChapter.chapterNumber,
|
||||
) { mutableMapOf() }
|
||||
pageCountByScanlator[dbChapter.scanlator] = dbChapter.pageCount
|
||||
}
|
||||
if (dbChapter.downloaded) deletedDownloadedChapterNumberToChapter[dbChapter.chapterNumber] = dbChapter
|
||||
deletedChapterNumbers.add(dbChapter.chapterNumber)
|
||||
deletedChapterNumberDateFetchMap[dbChapter.chapterNumber] = dbChapter.fetchedAt
|
||||
dbChapter.id
|
||||
@@ -292,18 +286,24 @@ object Chapter {
|
||||
this[ChapterTable.isRead] = chapter.chapterNumber in deletedReadChapterNumbers
|
||||
this[ChapterTable.isBookmarked] = chapter.chapterNumber in deletedBookmarkedChapterNumbers
|
||||
|
||||
// only preserve download status for chapters of the same scanlator, otherwise,
|
||||
// the downloaded files won't be found anyway
|
||||
val downloadedChapterInfo = deletedDownloadedChapterNumberInfoMap[chapter.chapterNumber]
|
||||
val pageCount = downloadedChapterInfo?.get(chapter.scanlator)
|
||||
if (pageCount != null) {
|
||||
this[ChapterTable.isDownloaded] = true
|
||||
this[ChapterTable.pageCount] = pageCount
|
||||
}
|
||||
// Try to use the fetch date of the original entry to not pollute 'Updates' tab
|
||||
deletedChapterNumberDateFetchMap[chapter.chapterNumber]?.let {
|
||||
this[ChapterTable.fetchedAt] = it
|
||||
}
|
||||
|
||||
val deletedChapter = deletedDownloadedChapterNumberToChapter[chapter.chapterNumber]!!
|
||||
|
||||
val hasDownloadedPages = deletedChapter.pageCount > 0
|
||||
val isSameName = deletedChapter.name == chapter.name
|
||||
val isSameScanlator = deletedChapter.scanlator == chapter.scanlator
|
||||
|
||||
// Only preserve download status for chapters with the same name and of the same scanlator; otherwise,
|
||||
// the downloaded files won't be found anyway
|
||||
val isDownloadPreservable = hasDownloadedPages && isSameName && isSameScanlator
|
||||
if (isDownloadPreservable) {
|
||||
this[ChapterTable.isDownloaded] = true
|
||||
this[ChapterTable.pageCount] = deletedChapter.pageCount
|
||||
}
|
||||
}
|
||||
}.forEach { insertedChapters.add(ChapterTable.toDataClass(it)) }
|
||||
}
|
||||
@@ -313,12 +313,30 @@ object Chapter {
|
||||
.apply {
|
||||
chaptersToUpdate.forEach {
|
||||
addBatch(EntityID(it.id, ChapterTable))
|
||||
|
||||
val currentChapter = chaptersInDb.find { dbChapter -> dbChapter.id == it.id }!!
|
||||
|
||||
this[ChapterTable.name] = it.name
|
||||
this[ChapterTable.date_upload] = it.uploadDate
|
||||
this[ChapterTable.chapter_number] = it.chapterNumber
|
||||
this[ChapterTable.scanlator] = it.scanlator
|
||||
this[ChapterTable.sourceOrder] = it.index
|
||||
this[ChapterTable.realUrl] = it.realUrl
|
||||
this[ChapterTable.isDownloaded] = currentChapter.downloaded
|
||||
this[ChapterTable.pageCount] = currentChapter.pageCount
|
||||
|
||||
if (!currentChapter.downloaded) {
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val isSameScanlator = currentChapter.scanlator == it.scanlator
|
||||
val isSameName = currentChapter.name == it.name
|
||||
|
||||
val isDownloadPreservable = isSameName && isSameScanlator
|
||||
if (!isDownloadPreservable) {
|
||||
this[ChapterTable.isDownloaded] = false
|
||||
this[ChapterTable.pageCount] = -1
|
||||
}
|
||||
}
|
||||
}.toExecutable()
|
||||
.execute(this@transaction)
|
||||
|
||||
@@ -133,7 +133,7 @@ object Manga {
|
||||
""
|
||||
}
|
||||
if (remoteTitle.isNotEmpty() && remoteTitle != mangaEntry[MangaTable.title]) {
|
||||
val canUpdateTitle = updateMangaDownloadDir(mangaId, remoteTitle)
|
||||
val canUpdateTitle = updateMangaDownloadDir(mangaEntry[MangaTable.title], source.toString(), remoteTitle)
|
||||
|
||||
if (canUpdateTitle) {
|
||||
it[MangaTable.title] = remoteTitle
|
||||
|
||||
@@ -24,14 +24,22 @@ private val applicationDirs: ApplicationDirs by injectLazy()
|
||||
|
||||
private val logger = KotlinLogging.logger { }
|
||||
|
||||
private fun getMangaDir(
|
||||
title: String,
|
||||
sourceName: String,
|
||||
): String {
|
||||
val sourceDir = SafePath.buildValidFilename(sourceName)
|
||||
val mangaDir = SafePath.buildValidFilename(title)
|
||||
|
||||
return "$sourceDir/$mangaDir"
|
||||
}
|
||||
|
||||
private fun getMangaDir(mangaId: Int): String =
|
||||
transaction {
|
||||
val mangaEntry = MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val sourceDir = SafePath.buildValidFilename(source.toString())
|
||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
||||
"$sourceDir/$mangaDir"
|
||||
getMangaDir(mangaEntry[MangaTable.title], source.toString())
|
||||
}
|
||||
|
||||
private fun getChapterDir(
|
||||
@@ -62,8 +70,18 @@ private fun getChapterDir(
|
||||
|
||||
fun getThumbnailDownloadPath(mangaId: Int): String = applicationDirs.thumbnailDownloadsRoot + "/$mangaId"
|
||||
|
||||
fun getMangaDownloadDir(
|
||||
title: String,
|
||||
sourceName: String,
|
||||
): String = applicationDirs.mangaDownloadsRoot + "/" + getMangaDir(title, sourceName)
|
||||
|
||||
fun getMangaDownloadDir(mangaId: Int): String = applicationDirs.mangaDownloadsRoot + "/" + getMangaDir(mangaId)
|
||||
|
||||
fun getMangaCacheDir(
|
||||
title: String,
|
||||
sourceName: String,
|
||||
): String = applicationDirs.tempMangaCacheRoot + "/" + getMangaDir(title, sourceName)
|
||||
|
||||
fun getChapterDownloadPath(
|
||||
mangaId: Int,
|
||||
chapterId: Int,
|
||||
@@ -79,38 +97,21 @@ fun getChapterCachePath(
|
||||
chapterId: Int,
|
||||
): String = applicationDirs.tempMangaCacheRoot + "/" + getChapterDir(mangaId, chapterId)
|
||||
|
||||
/** return value says if rename/move was successful */
|
||||
fun updateMangaDownloadDir(
|
||||
mangaId: Int,
|
||||
newTitle: String,
|
||||
private fun updateDownloadDir(
|
||||
currentDir: String,
|
||||
newDir: String,
|
||||
): Boolean {
|
||||
// Get current manga directory (uses its own transaction)
|
||||
val currentMangaDir = getMangaDir(mangaId)
|
||||
|
||||
// Build new directory path
|
||||
val newMangaDir =
|
||||
transaction {
|
||||
val mangaEntry = MangaTable.selectAll().where { MangaTable.id eq mangaId }.first()
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
val sourceDir = SafePath.buildValidFilename(source.toString())
|
||||
val newMangaDirName = SafePath.buildValidFilename(newTitle)
|
||||
"$sourceDir/$newMangaDirName"
|
||||
}
|
||||
|
||||
val oldDir = "${applicationDirs.downloadsRoot}/$currentMangaDir"
|
||||
val newDir = "${applicationDirs.downloadsRoot}/$newMangaDir"
|
||||
|
||||
val oldDirFile = File(oldDir)
|
||||
val currentDirFile = File(currentDir)
|
||||
val newDirFile = File(newDir)
|
||||
|
||||
if (!oldDirFile.exists()) {
|
||||
if (!currentDirFile.exists()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return try {
|
||||
Files.move(oldDirFile.toPath(), newDirFile.toPath())
|
||||
Files.move(currentDirFile.toPath(), newDirFile.toPath())
|
||||
|
||||
if (oldDirFile.exists()) {
|
||||
if (currentDirFile.exists()) {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -118,9 +119,31 @@ fun updateMangaDownloadDir(
|
||||
return false
|
||||
}
|
||||
|
||||
true
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "updateMangaDownloadDir: failed to rename manga download folder from \"$oldDir\" to \"$newDir\"" }
|
||||
logger.error(e) { "updateDownloadDir: failed to rename download folder from \"$currentDir\" to \"$newDir\"" }
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/** return value says if rename/move was successful */
|
||||
fun updateMangaDownloadDir(
|
||||
title: String,
|
||||
sourceName: String,
|
||||
newTitle: String,
|
||||
): Boolean {
|
||||
val currentDownloadDir = getMangaDownloadDir(title, sourceName)
|
||||
val newDownloadDir = getMangaDownloadDir(newTitle, sourceName)
|
||||
|
||||
val renamed = updateDownloadDir(currentDownloadDir, newDownloadDir)
|
||||
|
||||
val tryToKeepCachedFilesUsable = renamed
|
||||
if (tryToKeepCachedFilesUsable) {
|
||||
val currentCacheDir = getMangaCacheDir(title, sourceName)
|
||||
val newCacheDir = getMangaCacheDir(newTitle, sourceName)
|
||||
|
||||
updateDownloadDir(currentCacheDir, newCacheDir)
|
||||
}
|
||||
|
||||
return renamed
|
||||
}
|
||||
|
||||
@@ -7,12 +7,21 @@ package suwayomi.tachidesk.manga.model.dataclass
|
||||
* 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 java.util.Objects
|
||||
import kotlin.math.min
|
||||
|
||||
open class PaginatedList<T>(
|
||||
val page: List<T>,
|
||||
val hasNextPage: Boolean,
|
||||
)
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is PaginatedList<T>) return false
|
||||
return page == other.page && hasNextPage == other.hasNextPage
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = Objects.hash(page, hasNextPage)
|
||||
}
|
||||
|
||||
const val PAGINATION_FACTOR = 50
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import io.github.config4k.toConfig
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import io.javalin.json.JavalinJackson
|
||||
import io.javalin.json.JavalinJackson3
|
||||
import io.javalin.json.JsonMapper
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
@@ -222,7 +222,7 @@ fun serverModule(applicationDirs: ApplicationDirs): Module =
|
||||
module {
|
||||
single { applicationDirs }
|
||||
single<IUpdater> { Updater() }
|
||||
single<JsonMapper> { JavalinJackson() }
|
||||
single<JsonMapper> { JavalinJackson3() }
|
||||
}
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
|
||||
@@ -144,14 +144,15 @@ object DBManager {
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
fun databaseUp() {
|
||||
fun databaseUp(givenDb: Database? = null) {
|
||||
val db =
|
||||
try {
|
||||
DBManager.setupDatabase()
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Failed to setup Database" }
|
||||
return
|
||||
}
|
||||
givenDb
|
||||
?: try {
|
||||
DBManager.setupDatabase()
|
||||
} catch (e: Exception) {
|
||||
logger.error(e) { "Failed to setup Database" }
|
||||
return
|
||||
}
|
||||
|
||||
logger.info {
|
||||
"Using ${db.vendor} database version ${db.version}"
|
||||
|
||||
@@ -1,17 +1,22 @@
|
||||
package masstest
|
||||
|
||||
import android.os.Looper
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.koin.core.context.stopKoin
|
||||
import suwayomi.tachidesk.manga.impl.Source
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.server.applicationSetup
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import suwayomi.tachidesk.test.BASE_PATH
|
||||
import suwayomi.tachidesk.test.setLoggingEnabled
|
||||
import xyz.nulldev.ts.config.CONFIG_PREFIX
|
||||
@@ -25,8 +30,11 @@ class CloudFlareTest {
|
||||
fun setup() {
|
||||
val dataRoot = File(BASE_PATH).absolutePath
|
||||
System.setProperty("$CONFIG_PREFIX.server.rootDir", dataRoot)
|
||||
Looper.clearMainLooperForTest()
|
||||
SettingsRegistry.clear()
|
||||
applicationSetup()
|
||||
setLoggingEnabled(false)
|
||||
return
|
||||
|
||||
runBlocking {
|
||||
val extensions = ExtensionsList.getExtensionList()
|
||||
@@ -48,9 +56,15 @@ class CloudFlareTest {
|
||||
setLoggingEnabled(true)
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun teardown() {
|
||||
stopKoin()
|
||||
}
|
||||
|
||||
private val logger = KotlinLogging.logger {}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
fun `test nhentai browse`() =
|
||||
runTest {
|
||||
assert(nhentai.getPopularManga(1).mangas.isNotEmpty()) {
|
||||
|
||||
@@ -7,6 +7,7 @@ package masstest
|
||||
* 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 android.os.Looper
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
@@ -17,9 +18,11 @@ import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import org.junit.jupiter.api.AfterAll
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.koin.core.context.stopKoin
|
||||
import suwayomi.tachidesk.manga.impl.Source.getSourceList
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.installExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension
|
||||
@@ -28,6 +31,7 @@ import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrNull
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
||||
import suwayomi.tachidesk.server.applicationSetup
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import suwayomi.tachidesk.test.BASE_PATH
|
||||
import suwayomi.tachidesk.test.setLoggingEnabled
|
||||
import xyz.nulldev.ts.config.CONFIG_PREFIX
|
||||
@@ -51,6 +55,8 @@ class TestExtensionCompatibility {
|
||||
fun setup() {
|
||||
val dataRoot = File(BASE_PATH).absolutePath
|
||||
System.setProperty("$CONFIG_PREFIX.server.rootDir", dataRoot)
|
||||
Looper.clearMainLooperForTest()
|
||||
SettingsRegistry.clear()
|
||||
applicationSetup()
|
||||
setLoggingEnabled(false)
|
||||
|
||||
@@ -72,12 +78,22 @@ class TestExtensionCompatibility {
|
||||
}
|
||||
}
|
||||
}
|
||||
sources = getSourceList().map { getCatalogueSourceOrNull(it.id.toLong())!! as HttpSource }
|
||||
sources =
|
||||
getSourceList()
|
||||
.filter {
|
||||
// filter local source
|
||||
it.id.toLong() != 0L
|
||||
}.map { getCatalogueSourceOrNull(it.id.toLong())!! as HttpSource }
|
||||
}
|
||||
setLoggingEnabled(true)
|
||||
File("$BASE_PATH/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
fun teardown() {
|
||||
stopKoin()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun runTest() {
|
||||
runBlocking(Dispatchers.Default) {
|
||||
|
||||
56
server/src/test/kotlin/suwayomi/tachidesk/LooperTest.kt
Normal file
56
server/src/test/kotlin/suwayomi/tachidesk/LooperTest.kt
Normal file
@@ -0,0 +1,56 @@
|
||||
package suwayomi.tachidesk
|
||||
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotEquals
|
||||
import kotlin.test.assertTrue
|
||||
import kotlin.text.StringBuilder
|
||||
|
||||
class LooperThread : Thread() {
|
||||
var mHandler: Handler? = null
|
||||
val latch = CountDownLatch(1)
|
||||
|
||||
override fun run() {
|
||||
Looper.prepare()
|
||||
mHandler = Handler(Looper.myLooper())
|
||||
latch.countDown()
|
||||
Looper.loop()
|
||||
}
|
||||
}
|
||||
|
||||
class LooperTest {
|
||||
@Test
|
||||
fun multiplePostWork() {
|
||||
val thread = LooperThread()
|
||||
thread.start()
|
||||
val sb = StringBuilder()
|
||||
val latch = CountDownLatch(1)
|
||||
assertTrue(thread.latch.await(5, TimeUnit.SECONDS))
|
||||
|
||||
thread.mHandler!!.post {
|
||||
Thread.sleep(100)
|
||||
sb.append("a_b_c")
|
||||
}
|
||||
thread.mHandler!!.post {
|
||||
Thread.sleep(100)
|
||||
sb.append("_d_e_f")
|
||||
}
|
||||
thread.mHandler!!.post {
|
||||
Thread.sleep(100)
|
||||
sb.append("_g_h_i")
|
||||
latch.countDown()
|
||||
}
|
||||
|
||||
assertNotEquals("a_b_c_d_e_f_g_h_i", sb.toString())
|
||||
assertTrue(latch.await(5, TimeUnit.SECONDS))
|
||||
|
||||
assertEquals("a_b_c_d_e_f_g_h_i", sb.toString())
|
||||
thread.mHandler!!.looper.quit()
|
||||
// thread.join()
|
||||
}
|
||||
}
|
||||
@@ -18,19 +18,22 @@ import suwayomi.tachidesk.test.clearTables
|
||||
class CategoryControllerTest : ApplicationTest() {
|
||||
@Test
|
||||
fun categoryReorder() {
|
||||
clearTables(
|
||||
CategoryTable,
|
||||
)
|
||||
Category.createCategory("foo")
|
||||
Category.createCategory("bar")
|
||||
val cats = Category.getCategoryList()
|
||||
val foo = cats.asSequence().filter { it.name == "foo" }.first()
|
||||
val bar = cats.asSequence().filter { it.name == "bar" }.first()
|
||||
assertEquals(1, foo.order)
|
||||
assertEquals(2, bar.order)
|
||||
assertEquals(0, foo.order)
|
||||
assertEquals(1, bar.order)
|
||||
Category.reorderCategory(1, 2)
|
||||
val catsReordered = Category.getCategoryList()
|
||||
val fooReordered = catsReordered.asSequence().filter { it.name == "foo" }.first()
|
||||
val barReordered = catsReordered.asSequence().filter { it.name == "bar" }.first()
|
||||
assertEquals(2, fooReordered.order)
|
||||
assertEquals(1, barReordered.order)
|
||||
assertEquals(1, fooReordered.order)
|
||||
assertEquals(0, barReordered.order)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
|
||||
@@ -35,7 +35,7 @@ class CategoryMangaTest : ApplicationTest() {
|
||||
CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID)[0].unreadCount,
|
||||
"Manga should not have any unread chapters",
|
||||
)
|
||||
createChapters(mangaId, 10, false)
|
||||
createChapters(mangaId, 10, false, start = 11)
|
||||
assertEquals(
|
||||
10,
|
||||
CategoryManga.getCategoryMangaList(DEFAULT_CATEGORY_ID)[0].unreadCount,
|
||||
|
||||
@@ -12,9 +12,12 @@ import eu.kanade.tachiyomi.createAppModule
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.local.LocalSource
|
||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||
import org.jetbrains.exposed.v1.core.DatabaseConfig
|
||||
import org.jetbrains.exposed.v1.core.ExperimentalKeywordApi
|
||||
import org.jetbrains.exposed.v1.jdbc.Database
|
||||
import org.junit.jupiter.api.BeforeAll
|
||||
import org.koin.core.context.startKoin
|
||||
import org.koin.core.context.stopKoin
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import suwayomi.tachidesk.server.JavalinSetup
|
||||
import suwayomi.tachidesk.server.ServerConfig
|
||||
@@ -22,7 +25,9 @@ import suwayomi.tachidesk.server.androidCompat
|
||||
import suwayomi.tachidesk.server.database.databaseUp
|
||||
import suwayomi.tachidesk.server.serverConfig
|
||||
import suwayomi.tachidesk.server.serverModule
|
||||
import suwayomi.tachidesk.server.settings.SettingsRegistry
|
||||
import suwayomi.tachidesk.server.util.AppMutex.handleAppMutex
|
||||
import suwayomi.tachidesk.server.util.ConfigTypeRegistration
|
||||
import suwayomi.tachidesk.server.util.SystemTray
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@@ -55,6 +60,13 @@ open class ApplicationTest {
|
||||
private var initializedTheApp = false
|
||||
|
||||
fun testingSetup() {
|
||||
// register Tachidesk's config which is dubbed "ServerConfig"
|
||||
SettingsRegistry.clear()
|
||||
ConfigTypeRegistration.registerCustomTypes()
|
||||
GlobalConfigManager.registerModule(
|
||||
ServerConfig.register { GlobalConfigManager.config },
|
||||
)
|
||||
|
||||
// Application dirs
|
||||
val applicationDirs = ApplicationDirs()
|
||||
|
||||
@@ -72,13 +84,9 @@ open class ApplicationTest {
|
||||
File(it).mkdirs()
|
||||
}
|
||||
|
||||
// register Tachidesk's config which is dubbed "ServerConfig"
|
||||
GlobalConfigManager.registerModule(
|
||||
ServerConfig.register { GlobalConfigManager.config },
|
||||
)
|
||||
|
||||
// initialize Koin modules
|
||||
val app = App()
|
||||
stopKoin()
|
||||
startKoin {
|
||||
modules(
|
||||
createAppModule(app),
|
||||
@@ -128,14 +136,14 @@ open class ApplicationTest {
|
||||
}
|
||||
|
||||
// create system tray
|
||||
if (serverConfig.systemTrayEnabled.value) {
|
||||
try {
|
||||
SystemTray.create()
|
||||
} catch (e: Throwable) {
|
||||
// cover both java.lang.Exception and java.lang.Error
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
// if (serverConfig.systemTrayEnabled.value) {
|
||||
// try {
|
||||
// SystemTray.create()
|
||||
// } catch (e: Throwable) {
|
||||
// // cover both java.lang.Exception and java.lang.Error
|
||||
// e.printStackTrace()
|
||||
// }
|
||||
// }
|
||||
|
||||
// Disable jetty's logging
|
||||
System.setProperty("org.eclipse.jetty.util.log.announce", "false")
|
||||
@@ -154,8 +162,16 @@ open class ApplicationTest {
|
||||
// fixes #119 , ref: https://github.com/Suwayomi/Suwayomi-Server/issues/119#issuecomment-894681292 , source Id calculation depends on String.lowercase()
|
||||
Locale.setDefault(Locale.ENGLISH)
|
||||
|
||||
val dbConfig =
|
||||
DatabaseConfig {
|
||||
useNestedTransactions = true
|
||||
@OptIn(ExperimentalKeywordApi::class)
|
||||
preserveKeywordCasing = false
|
||||
defaultSchema = null
|
||||
}
|
||||
|
||||
// in-memory database, don't discard database between connections/transactions
|
||||
val db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", "org.h2.Driver")
|
||||
val db = Database.connect("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;", "org.h2.Driver", databaseConfig = dbConfig)
|
||||
|
||||
databaseUp(db)
|
||||
|
||||
|
||||
@@ -55,8 +55,9 @@ fun createChapters(
|
||||
mangaId: Int,
|
||||
amount: Int,
|
||||
read: Boolean,
|
||||
start: Int = 1,
|
||||
) {
|
||||
val list = listOf((0 until amount)).flatten().map { 1 }
|
||||
val list = listOf((0 until amount)).flatten().map { it + start }
|
||||
transaction {
|
||||
ChapterTable
|
||||
.batchInsert(list) {
|
||||
|
||||
Reference in New Issue
Block a user