fix sqlite locking fuckery by replacing it with h2

This commit is contained in:
Aria Moradi
2021-02-04 00:32:01 +03:30
parent 172f83f5b3
commit d766206343
6 changed files with 130 additions and 172 deletions

View File

@@ -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"))

View File

@@ -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)
} }
} }

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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}")
}
} }
} }

View File

@@ -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
// } // }
// } // }
} }