mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 03:14:40 -05:00
fix sqlite locking fuckery by replacing it with h2
This commit is contained in:
@@ -80,6 +80,8 @@ dependencies {
|
|||||||
implementation ("org.jetbrains.exposed:exposed-dao:$exposed_version")
|
implementation ("org.jetbrains.exposed:exposed-dao:$exposed_version")
|
||||||
implementation ("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
implementation ("org.jetbrains.exposed:exposed-jdbc:$exposed_version")
|
||||||
implementation ("org.xerial:sqlite-jdbc:3.30.1")
|
implementation ("org.xerial:sqlite-jdbc:3.30.1")
|
||||||
|
implementation ("com.h2database:h2:1.4.199")
|
||||||
|
|
||||||
|
|
||||||
// AndroidCompat
|
// AndroidCompat
|
||||||
implementation(project(":AndroidCompat"))
|
implementation(project(":AndroidCompat"))
|
||||||
|
|||||||
@@ -50,10 +50,10 @@ class WrappedCookie private constructor(val cookie: Cookie) {
|
|||||||
if (other !is WrappedCookie) return false
|
if (other !is WrappedCookie) return false
|
||||||
|
|
||||||
return other.cookie.name == cookie.name &&
|
return other.cookie.name == cookie.name &&
|
||||||
other.cookie.domain == cookie.domain &&
|
other.cookie.domain == cookie.domain &&
|
||||||
other.cookie.path == cookie.path &&
|
other.cookie.path == cookie.path &&
|
||||||
other.cookie.secure == cookie.secure &&
|
other.cookie.secure == cookie.secure &&
|
||||||
other.cookie.hostOnly == cookie.hostOnly
|
other.cookie.hostOnly == cookie.hostOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hashCode(): Int {
|
override fun hashCode(): Int {
|
||||||
@@ -69,4 +69,4 @@ class WrappedCookie private constructor(val cookie: Cookie) {
|
|||||||
companion object {
|
companion object {
|
||||||
fun wrap(cookie: Cookie) = WrappedCookie(cookie)
|
fun wrap(cookie: Cookie) = WrappedCookie(cookie)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import ir.armor.tachidesk.util.getChapterList
|
|||||||
import ir.armor.tachidesk.util.getExtensionList
|
import ir.armor.tachidesk.util.getExtensionList
|
||||||
import ir.armor.tachidesk.util.getManga
|
import ir.armor.tachidesk.util.getManga
|
||||||
import ir.armor.tachidesk.util.getMangaList
|
import ir.armor.tachidesk.util.getMangaList
|
||||||
import ir.armor.tachidesk.util.getMangaUpdateQueueThread
|
// import ir.armor.tachidesk.util.getMangaUpdateQueueThread
|
||||||
import ir.armor.tachidesk.util.getPages
|
import ir.armor.tachidesk.util.getPages
|
||||||
import ir.armor.tachidesk.util.getSource
|
import ir.armor.tachidesk.util.getSource
|
||||||
import ir.armor.tachidesk.util.getSourceList
|
import ir.armor.tachidesk.util.getSourceList
|
||||||
@@ -57,7 +57,7 @@ class Main {
|
|||||||
// start app
|
// start app
|
||||||
androidCompat.startApp(App())
|
androidCompat.startApp(App())
|
||||||
|
|
||||||
Thread(getMangaUpdateQueueThread).start()
|
// Thread(getMangaUpdateQueueThread).start()
|
||||||
|
|
||||||
val app = Javalin.create { config ->
|
val app = Javalin.create { config ->
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -15,13 +15,15 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
|||||||
|
|
||||||
object DBMangaer {
|
object DBMangaer {
|
||||||
val db by lazy {
|
val db by lazy {
|
||||||
Database.connect("jdbc:sqlite:${Config.dataRoot}/database.db", "org.sqlite.JDBC")
|
Database.connect("jdbc:h2:${Config.dataRoot}/database.h2", "org.h2.Driver")
|
||||||
|
// Database.connect("jdbc:sqlite:${Config.dataRoot}/database.sqlite3", "org.sqlite.JDBC")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun makeDataBaseTables() {
|
fun makeDataBaseTables() {
|
||||||
// mention db object to connect
|
// mention db object to connect
|
||||||
DBMangaer.db
|
val db = DBMangaer.db
|
||||||
|
db.useNestedTransactions = true
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
SchemaUtils.create(ExtensionsTable)
|
SchemaUtils.create(ExtensionsTable)
|
||||||
|
|||||||
@@ -12,164 +12,116 @@ import ir.armor.tachidesk.database.table.MangaStatus
|
|||||||
import ir.armor.tachidesk.database.table.MangaTable
|
import ir.armor.tachidesk.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.database.table.SourceTable
|
import ir.armor.tachidesk.database.table.SourceTable
|
||||||
import org.jetbrains.exposed.sql.select
|
import org.jetbrains.exposed.sql.select
|
||||||
import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
import org.jetbrains.exposed.sql.update
|
import org.jetbrains.exposed.sql.update
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
|
||||||
|
|
||||||
val getMangaUpdateQueue = ArrayBlockingQueue<Pair<Int, SManga?>>(1000)
|
|
||||||
@Volatile
|
|
||||||
var getMangaCount = 0
|
|
||||||
|
|
||||||
val getMangaUpdateQueueThread = Runnable {
|
|
||||||
while (true) {
|
|
||||||
val p = getMangaUpdateQueue.take()
|
|
||||||
println("took ${p.first}")
|
|
||||||
while (getMangaCount > 0) {
|
|
||||||
println("count is $getMangaCount")
|
|
||||||
Thread.sleep(1000)
|
|
||||||
}
|
|
||||||
val mangaId = p.first
|
|
||||||
println("working on $mangaId")
|
|
||||||
val fetchedManga = p.second!!
|
|
||||||
try {
|
|
||||||
transaction {
|
|
||||||
println("transaction start $mangaId")
|
|
||||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
|
||||||
|
|
||||||
it[MangaTable.initialized] = true
|
|
||||||
|
|
||||||
it[MangaTable.artist] = fetchedManga.artist
|
|
||||||
it[MangaTable.author] = fetchedManga.author
|
|
||||||
it[MangaTable.description] = fetchedManga.description
|
|
||||||
it[MangaTable.genre] = fetchedManga.genre
|
|
||||||
it[MangaTable.status] = fetchedManga.status
|
|
||||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
|
||||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
|
||||||
}
|
|
||||||
println("transaction end $mangaId")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
println(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
|
fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
|
||||||
synchronized(getMangaCount) {
|
var mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||||
getMangaCount++
|
|
||||||
}
|
return if (mangaEntry[MangaTable.initialized]) {
|
||||||
return try {
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
mangaEntry[MangaTable.sourceReference].value,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.url],
|
||||||
|
mangaEntry[MangaTable.title],
|
||||||
|
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url],
|
||||||
|
|
||||||
|
true,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.artist],
|
||||||
|
mangaEntry[MangaTable.author],
|
||||||
|
mangaEntry[MangaTable.description],
|
||||||
|
mangaEntry[MangaTable.genre],
|
||||||
|
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
||||||
|
)
|
||||||
|
} else { // initialize manga
|
||||||
|
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
|
||||||
|
val fetchedManga = source.fetchMangaDetails(
|
||||||
|
SManga.create().apply {
|
||||||
|
url = mangaEntry[MangaTable.url]
|
||||||
|
title = mangaEntry[MangaTable.title]
|
||||||
|
}
|
||||||
|
).toBlocking().first()
|
||||||
|
|
||||||
transaction {
|
transaction {
|
||||||
var mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||||
|
|
||||||
return@transaction if (mangaEntry[MangaTable.initialized]) {
|
it[MangaTable.initialized] = true
|
||||||
println("${mangaEntry[MangaTable.title]} is initialized")
|
|
||||||
println("${mangaEntry[MangaTable.thumbnail_url]}")
|
|
||||||
MangaDataClass(
|
|
||||||
mangaId,
|
|
||||||
mangaEntry[MangaTable.sourceReference].value,
|
|
||||||
|
|
||||||
mangaEntry[MangaTable.url],
|
it[MangaTable.artist] = fetchedManga.artist
|
||||||
mangaEntry[MangaTable.title],
|
it[MangaTable.author] = fetchedManga.author
|
||||||
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else mangaEntry[MangaTable.thumbnail_url],
|
it[MangaTable.description] = fetchedManga.description
|
||||||
|
it[MangaTable.genre] = fetchedManga.genre
|
||||||
true,
|
it[MangaTable.status] = fetchedManga.status
|
||||||
|
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty())
|
||||||
mangaEntry[MangaTable.artist],
|
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||||
mangaEntry[MangaTable.author],
|
|
||||||
mangaEntry[MangaTable.description],
|
|
||||||
mangaEntry[MangaTable.genre],
|
|
||||||
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
|
|
||||||
)
|
|
||||||
} else { // initialize manga
|
|
||||||
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
|
|
||||||
val fetchedManga = source.fetchMangaDetails(
|
|
||||||
SManga.create().apply {
|
|
||||||
url = mangaEntry[MangaTable.url]
|
|
||||||
title = mangaEntry[MangaTable.title]
|
|
||||||
}
|
|
||||||
).toBlocking().first()
|
|
||||||
|
|
||||||
// update database
|
|
||||||
// TODO: sqlite gets fucked here
|
|
||||||
println("putting $mangaId")
|
|
||||||
getMangaUpdateQueue.put(Pair(mangaId, fetchedManga))
|
|
||||||
|
|
||||||
// mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
|
||||||
val newThumbnail =
|
|
||||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url!!.isNotEmpty()) {
|
|
||||||
fetchedManga.thumbnail_url
|
|
||||||
} else mangaEntry[MangaTable.thumbnail_url]
|
|
||||||
|
|
||||||
MangaDataClass(
|
|
||||||
mangaId,
|
|
||||||
mangaEntry[MangaTable.sourceReference].value,
|
|
||||||
|
|
||||||
mangaEntry[MangaTable.url],
|
|
||||||
mangaEntry[MangaTable.title],
|
|
||||||
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail,
|
|
||||||
|
|
||||||
true,
|
|
||||||
|
|
||||||
fetchedManga.artist,
|
|
||||||
fetchedManga.author,
|
|
||||||
fetchedManga.description,
|
|
||||||
fetchedManga.genre,
|
|
||||||
MangaStatus.valueOf(fetchedManga.status).name,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
synchronized(getMangaCount) {
|
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||||
getMangaCount--
|
val newThumbnail = mangaEntry[MangaTable.thumbnail_url]
|
||||||
}
|
|
||||||
|
MangaDataClass(
|
||||||
|
mangaId,
|
||||||
|
mangaEntry[MangaTable.sourceReference].value,
|
||||||
|
|
||||||
|
mangaEntry[MangaTable.url],
|
||||||
|
mangaEntry[MangaTable.title],
|
||||||
|
if (proxyThumbnail) proxyThumbnailUrl(mangaId) else newThumbnail,
|
||||||
|
|
||||||
|
true,
|
||||||
|
|
||||||
|
fetchedManga.artist,
|
||||||
|
fetchedManga.author,
|
||||||
|
fetchedManga.description,
|
||||||
|
fetchedManga.genre,
|
||||||
|
MangaStatus.valueOf(fetchedManga.status).name,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
|
fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
|
||||||
return transaction {
|
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
|
||||||
var mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
|
var filePath = Config.thumbnailsRoot + "/$mangaId"
|
||||||
var filePath = Config.thumbnailsRoot + "/$mangaId"
|
|
||||||
|
|
||||||
val potentialCache = findFileNameStartingWith(Config.thumbnailsRoot, mangaId.toString())
|
val potentialCache = findFileNameStartingWith(Config.thumbnailsRoot, mangaId.toString())
|
||||||
if (potentialCache != null) {
|
if (potentialCache != null) {
|
||||||
println("using cached thumbnail file")
|
println("using cached thumbnail file")
|
||||||
return@transaction Pair(
|
return Pair(
|
||||||
pathToInputStream(potentialCache),
|
pathToInputStream(potentialCache),
|
||||||
"image/${potentialCache.substringAfter("$mangaId.")}"
|
"image/${potentialCache.substringAfter("$mangaId.")}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val sourceId = mangaEntry[MangaTable.sourceReference].value
|
val sourceId = mangaEntry[MangaTable.sourceReference].value
|
||||||
println("getting source for $mangaId")
|
println("getting source for $mangaId")
|
||||||
val source = getHttpSource(sourceId)
|
val source = getHttpSource(sourceId)
|
||||||
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
var thumbnailUrl = mangaEntry[MangaTable.thumbnail_url]
|
||||||
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
|
if (thumbnailUrl == null || thumbnailUrl.isEmpty()) {
|
||||||
thumbnailUrl = getManga(mangaId, proxyThumbnail = false).thumbnailUrl!!
|
thumbnailUrl = getManga(mangaId, proxyThumbnail = false).thumbnailUrl!!
|
||||||
}
|
}
|
||||||
println(thumbnailUrl)
|
println(thumbnailUrl)
|
||||||
val response = source.client.newCall(
|
val response = source.client.newCall(
|
||||||
GET(thumbnailUrl, source.headers)
|
GET(thumbnailUrl, source.headers)
|
||||||
).execute()
|
).execute()
|
||||||
|
|
||||||
println(response.code)
|
println(response.code)
|
||||||
|
|
||||||
if (response.code == 200) {
|
if (response.code == 200) {
|
||||||
val contentType = response.headers["content-type"]!!
|
val contentType = response.headers["content-type"]!!
|
||||||
filePath += "." + contentType.substringAfter("image/")
|
filePath += "." + contentType.substringAfter("image/")
|
||||||
|
|
||||||
writeStream(response.body!!.byteStream(), filePath)
|
writeStream(response.body!!.byteStream(), filePath)
|
||||||
|
|
||||||
return@transaction Pair(
|
return Pair(
|
||||||
pathToInputStream(filePath),
|
pathToInputStream(filePath),
|
||||||
contentType
|
contentType
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw Exception("request error! ${response.code}")
|
throw Exception("request error! ${response.code}")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,30 +1,32 @@
|
|||||||
package ir.armor.tachidesk.util
|
package ir.armor.tachidesk.util
|
||||||
|
|
||||||
import com.android.dx.util.ExceptionWithContext.withContext
|
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
|
|
||||||
|
/* 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/. */
|
||||||
|
|
||||||
class MangaDexHelper(private val mangaDexSource: HttpSource) {
|
class MangaDexHelper(private val mangaDexSource: HttpSource) {
|
||||||
|
|
||||||
private fun clientBuilder(): OkHttpClient = clientBuilder(0)
|
private fun clientBuilder(): OkHttpClient = clientBuilder(0)
|
||||||
|
|
||||||
private fun clientBuilder(
|
private fun clientBuilder(
|
||||||
r18Toggle: Int,
|
r18Toggle: Int,
|
||||||
okHttpClient: OkHttpClient = mangaDexSource.network.client
|
okHttpClient: OkHttpClient = mangaDexSource.network.client
|
||||||
): OkHttpClient = okHttpClient.newBuilder()
|
): OkHttpClient = okHttpClient.newBuilder()
|
||||||
.addNetworkInterceptor { chain ->
|
.addNetworkInterceptor { chain ->
|
||||||
val originalCookies = chain.request().header("Cookie") ?: ""
|
val originalCookies = chain.request().header("Cookie") ?: ""
|
||||||
val newReq = chain
|
val newReq = chain
|
||||||
.request()
|
.request()
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle)}")
|
.header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle)}")
|
||||||
.build()
|
.build()
|
||||||
chain.proceed(newReq)
|
chain.proceed(newReq)
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
private fun cookiesHeader(r18Toggle: Int): String {
|
private fun cookiesHeader(r18Toggle: Int): String {
|
||||||
val cookies = mutableMapOf<String, String>()
|
val cookies = mutableMapOf<String, String>()
|
||||||
@@ -33,9 +35,9 @@ class MangaDexHelper(private val mangaDexSource: HttpSource) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun buildCookies(cookies: Map<String, String>) =
|
private fun buildCookies(cookies: Map<String, String>) =
|
||||||
cookies.entries.joinToString(separator = "; ", postfix = ";") {
|
cookies.entries.joinToString(separator = "; ", postfix = ";") {
|
||||||
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun isLogged(): Boolean {
|
// fun isLogged(): Boolean {
|
||||||
// val httpUrl = mangaDexSource.baseUrl.toHttpUrlOrNull()!!
|
// val httpUrl = mangaDexSource.baseUrl.toHttpUrlOrNull()!!
|
||||||
@@ -44,21 +46,21 @@ class MangaDexHelper(private val mangaDexSource: HttpSource) {
|
|||||||
|
|
||||||
fun login(username: String, password: String, twoFactorCode: String = ""): Boolean {
|
fun login(username: String, password: String, twoFactorCode: String = ""): Boolean {
|
||||||
val formBody = FormBody.Builder()
|
val formBody = FormBody.Builder()
|
||||||
.add("login_username", username)
|
.add("login_username", username)
|
||||||
.add("login_password", password)
|
.add("login_password", password)
|
||||||
.add("no_js", "1")
|
.add("no_js", "1")
|
||||||
.add("remember_me", "1")
|
.add("remember_me", "1")
|
||||||
|
|
||||||
twoFactorCode.let {
|
twoFactorCode.let {
|
||||||
formBody.add("two_factor", it)
|
formBody.add("two_factor", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
val response = clientBuilder().newCall(
|
val response = clientBuilder().newCall(
|
||||||
POST(
|
POST(
|
||||||
"${mangaDexSource.baseUrl}/ajax/actions.ajax.php?function=login",
|
"${mangaDexSource.baseUrl}/ajax/actions.ajax.php?function=login",
|
||||||
mangaDexSource.headers,
|
mangaDexSource.headers,
|
||||||
formBody.build()
|
formBody.build()
|
||||||
)
|
)
|
||||||
).execute()
|
).execute()
|
||||||
return response.body!!.string().isEmpty()
|
return response.body!!.string().isEmpty()
|
||||||
}
|
}
|
||||||
@@ -90,4 +92,4 @@ class MangaDexHelper(private val mangaDexSource: HttpSource) {
|
|||||||
// false
|
// false
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user