calling HttpSource.imageRequest now

This commit is contained in:
Aria Moradi 2021-02-04 03:42:30 +03:30
parent 8f28c3b74b
commit 63a444bd81
18 changed files with 219 additions and 69 deletions

View File

@ -4,8 +4,10 @@ package eu.kanade.tachiyomi.network
// import eu.kanade.tachiyomi.BuildConfig
// import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import android.content.Context
import okhttp3.Dispatcher
// import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import java.util.concurrent.Executors
// import okhttp3.dnsoverhttps.DnsOverHttps
// import okhttp3.logging.HttpLoggingInterceptor
// import uy.kohesive.injekt.injectLazy
@ -26,7 +28,8 @@ class NetworkHelper(context: Context) {
.cookieJar(cookieManager)
// .cache(Cache(cacheDir, cacheSize))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.readTimeout(5, TimeUnit.MINUTES)
.writeTimeout(5, TimeUnit.MINUTES)
// .dispatcher(Dispatcher(Executors.newFixedThreadPool(1)))
// .addInterceptor(UserAgentInterceptor())

View File

@ -80,17 +80,18 @@ fun Call.asObservable(): Observable<Response> {
// }
fun Call.asObservableSuccess(): Observable<Response> {
return asObservable().doOnNext { response ->
if (!response.isSuccessful) {
response.close()
throw Exception("HTTP error ${response.code}")
}
}
return asObservable()
// .doOnNext { response ->
// if (!response.isSuccessful) {
// response.close()
// throw Exception("HTTP error ${response.code}")
// }
// }
}
// fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
// val progressClient = newBuilder()
// .cache(null)
// .cache(nasObservableSuccessull)
// .addNetworkInterceptor { chain ->
// val originalResponse = chain.proceed(chain.request())
// originalResponse.newBuilder()
@ -104,7 +105,7 @@ fun Call.asObservableSuccess(): Observable<Response> {
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder()
.cache(null)
// .cache(null)
// .addNetworkInterceptor { chain ->
// val originalResponse = chain.proceed(chain.request())
// originalResponse.newBuilder()

View File

@ -311,7 +311,7 @@ abstract class HttpSource : CatalogueSource {
*
* @param page the chapter whose page list has to be fetched
*/
protected open fun imageRequest(page: Page): Request {
open fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers)
}

View File

@ -7,12 +7,12 @@ package ir.armor.tachidesk
import eu.kanade.tachiyomi.App
import io.javalin.Javalin
import ir.armor.tachidesk.util.applicationSetup
import ir.armor.tachidesk.util.getChapter
import ir.armor.tachidesk.util.getChapterList
import ir.armor.tachidesk.util.getExtensionList
import ir.armor.tachidesk.util.getManga
import ir.armor.tachidesk.util.getMangaList
// import ir.armor.tachidesk.util.getMangaUpdateQueueThread
import ir.armor.tachidesk.util.getPages
import ir.armor.tachidesk.util.getPageImage
import ir.armor.tachidesk.util.getSource
import ir.armor.tachidesk.util.getSourceList
import ir.armor.tachidesk.util.getThumbnail
@ -119,6 +119,14 @@ class Main {
ctx.json(getManga(mangaId))
}
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
val mangaId = ctx.pathParam("mangaId").toInt()
val result = getThumbnail(mangaId)
ctx.result(result.first)
ctx.header("content-type", result.second)
}
app.get("/api/v1/manga/:mangaId/chapters") { ctx ->
val mangaId = ctx.pathParam("mangaId").toInt()
ctx.json(getChapterList(mangaId))
@ -127,13 +135,14 @@ class Main {
app.get("/api/v1/manga/:mangaId/chapter/:chapterId") { ctx ->
val chapterId = ctx.pathParam("chapterId").toInt()
val mangaId = ctx.pathParam("mangaId").toInt()
ctx.json(getPages(chapterId, mangaId))
ctx.json(getChapter(chapterId, mangaId))
}
app.get("api/v1/manga/:mangaId/thumbnail") { ctx ->
app.get("/api/v1/manga/:mangaId/chapter/:chapterId/page/:index") { ctx ->
val chapterId = ctx.pathParam("chapterId").toInt()
val mangaId = ctx.pathParam("mangaId").toInt()
println("got request for: $mangaId")
val result = getThumbnail(mangaId)
val index = ctx.pathParam("index").toInt()
val result = getPageImage(mangaId, chapterId, index)
ctx.result(result.first)
ctx.header("content-type", result.second)

View File

@ -8,6 +8,7 @@ import ir.armor.tachidesk.Config
import ir.armor.tachidesk.database.table.ChapterTable
import ir.armor.tachidesk.database.table.ExtensionsTable
import ir.armor.tachidesk.database.table.MangaTable
import ir.armor.tachidesk.database.table.PageTable
import ir.armor.tachidesk.database.table.SourceTable
import org.jetbrains.exposed.sql.Database
import org.jetbrains.exposed.sql.SchemaUtils
@ -30,5 +31,6 @@ fun makeDataBaseTables() {
SchemaUtils.create(SourceTable)
SchemaUtils.create(MangaTable)
SchemaUtils.create(ChapterTable)
SchemaUtils.create(PageTable)
}
}

View File

@ -12,4 +12,5 @@ data class ChapterDataClass(
val chapter_number: Float,
val scanlator: String?,
val mangaId: Int,
val pageCount: Int? = null,
)

View File

@ -1,5 +1,9 @@
package ir.armor.tachidesk.database.table
/* 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.jetbrains.exposed.dao.id.IntIdTable
object ChapterTable : IntIdTable() {

View File

@ -1,5 +1,9 @@
package ir.armor.tachidesk.database.table
/* 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.jetbrains.exposed.dao.id.IntIdTable
object ExtensionsTable : IntIdTable() {

View File

@ -1,5 +1,9 @@
package ir.armor.tachidesk.database.table
/* 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 eu.kanade.tachiyomi.source.model.SManga
import org.jetbrains.exposed.dao.id.IntIdTable

View File

@ -0,0 +1,15 @@
package ir.armor.tachidesk.database.table
/* 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.jetbrains.exposed.dao.id.IntIdTable
object PageTable : IntIdTable() {
val index = integer("index")
val url = varchar("url", 2048)
val imageUrl = varchar("imageUrl", 2048).nullable()
val chapter = reference("chapter", ChapterTable)
}

View File

@ -1,5 +1,9 @@
package ir.armor.tachidesk.database.table
/* 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.jetbrains.exposed.dao.id.IdTable
object SourceTable : IdTable<Long>() {

View File

@ -4,14 +4,14 @@ package ir.armor.tachidesk.util
* 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 eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.database.dataclass.ChapterDataClass
import ir.armor.tachidesk.database.dataclass.PageDataClass
import ir.armor.tachidesk.database.table.ChapterTable
import ir.armor.tachidesk.database.table.MangaTable
import ir.armor.tachidesk.database.table.PageTable
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
@ -57,14 +57,14 @@ fun getChapterList(mangaId: Int): List<ChapterDataClass> {
}
}
fun getPages(chapterId: Int, mangaId: Int): Pair<ChapterDataClass, List<PageDataClass>> {
fun getChapter(chapterId: Int, mangaId: Int): ChapterDataClass {
return transaction {
val chapterEntry = ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!!
assert(mangaId == chapterEntry[ChapterTable.manga].value) // sanity check
val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
val pagesList = source.fetchPageList(
val pageList = source.fetchPageList(
SChapter.create().apply {
url = chapterEntry[ChapterTable.url]
name = chapterEntry[ChapterTable.name]
@ -78,22 +78,24 @@ fun getPages(chapterId: Int, mangaId: Int): Pair<ChapterDataClass, List<PageData
chapterEntry[ChapterTable.date_upload],
chapterEntry[ChapterTable.chapter_number],
chapterEntry[ChapterTable.scanlator],
mangaId
mangaId,
pageList.count()
)
val pages = pagesList.map {
PageDataClass(
it.index,
getTrueImageUrl(it, source)
)
pageList.forEach { page ->
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq page.index) }.firstOrNull() }
if (pageEntry == null) {
transaction {
PageTable.insert {
it[index] = page.index
it[url] = page.url
it[imageUrl] = page.imageUrl
it[this.chapter] = chapterId
}
}
}
}
return@transaction Pair(chapter, pages)
return@transaction chapter
}
}
fun getTrueImageUrl(page: Page, source: HttpSource): String {
return if (page.imageUrl == null) {
source.fetchImageUrl(page).toBlocking().first()!!
} else page.imageUrl!!
}

View File

@ -1,15 +1,23 @@
package ir.armor.tachidesk.util
/* 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 okio.BufferedSource
import okio.buffer
import okio.sink
import java.io.BufferedInputStream
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.OutputStream
import java.nio.file.Files
import java.nio.file.Paths
fun writeStream(fileStream: InputStream, path: String) {
Files.newOutputStream(Paths.get(path)).use { os ->
val buffer = ByteArray(1024)
val buffer = ByteArray(128 * 1024)
var len: Int
while (fileStream.read(buffer).also { len = it } > 0) {
os.write(buffer, 0, len)
@ -28,3 +36,17 @@ fun findFileNameStartingWith(directoryPath: String, fileName: String): String? {
}
return null
}
/**
* Saves the given source to an output stream and closes both resources.
*
* @param stream the stream where the source is copied.
*/
fun BufferedSource.saveTo(stream: OutputStream) {
use { input ->
stream.sink().buffer().use {
it.writeAll(input)
it.flush()
}
}
}

View File

@ -85,14 +85,14 @@ fun getManga(mangaId: Int, proxyThumbnail: Boolean = true): MangaDataClass {
fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
var filePath = Config.thumbnailsRoot + "/$mangaId"
var filePath = "${Config.thumbnailsRoot}/$mangaId."
val potentialCache = findFileNameStartingWith(Config.thumbnailsRoot, mangaId.toString())
if (potentialCache != null) {
println("using cached thumbnail file")
return Pair(
pathToInputStream(potentialCache),
"image/${potentialCache.substringAfter("$mangaId.")}"
"image/${potentialCache.substringAfter(filePath)}"
)
}
@ -108,11 +108,9 @@ fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
GET(thumbnailUrl, source.headers)
).execute()
println(response.code)
if (response.code == 200) {
val contentType = response.headers["content-type"]!!
filePath += "." + contentType.substringAfter("image/")
filePath += contentType.substringAfter("image/")
writeStream(response.body!!.byteStream(), filePath)
@ -126,9 +124,9 @@ fun getThumbnail(mangaId: Int): Pair<InputStream, String> {
}
fun getMangaDir(mangaId: Int): String {
val mangaEntry = MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!!
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val sourceId = mangaEntry[MangaTable.sourceReference].value
val sourceEntry = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!!
val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! }
val mangaTitle = mangaEntry[MangaTable.title]
val sourceName = sourceEntry[SourceTable.name]

View File

@ -1,15 +1,15 @@
package ir.armor.tachidesk.util
/* 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 eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.online.HttpSource
import okhttp3.FormBody
import okhttp3.OkHttpClient
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) {
private fun clientBuilder(): OkHttpClient = clientBuilder(0)

View File

@ -0,0 +1,88 @@
package ir.armor.tachidesk.util
/* 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 eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import ir.armor.tachidesk.database.table.ChapterTable
import ir.armor.tachidesk.database.table.MangaTable
import ir.armor.tachidesk.database.table.PageTable
import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction
import org.jetbrains.exposed.sql.update
import java.io.File
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Paths
fun getTrueImageUrl(page: Page, source: HttpSource): String {
if (page.imageUrl == null) {
page.imageUrl = source.fetchImageUrl(page).toBlocking().first()!!
}
return page.imageUrl!!
}
fun getPageImage(mangaId: Int, chapterId: Int, index: Int): Pair<InputStream, String> {
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.firstOrNull()!! }
val source = getHttpSource(mangaEntry[MangaTable.sourceReference].value)
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.firstOrNull()!! }
val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.firstOrNull()!! }
val tachiPage = Page(
pageEntry[PageTable.index],
pageEntry[PageTable.url],
pageEntry[PageTable.imageUrl]
)
if (pageEntry[PageTable.imageUrl] == null) {
transaction {
PageTable.update({ (PageTable.chapter eq chapterId) and (PageTable.index eq index) }) {
it[imageUrl] = getTrueImageUrl(tachiPage, source)
}
}
}
val saveDir = getMangaDir(mangaId) + "/" + chapterEntry[ChapterTable.chapter_number]
File(saveDir).mkdirs()
var filePath = "$saveDir/$index."
val potentialCache = findFileNameStartingWith(saveDir, index.toString())
if (potentialCache != null) {
println("using cached page file for $index")
return Pair(
pathToInputStream(potentialCache),
"image/${potentialCache.substringAfter("$filePath")}"
)
}
// Note: line bellow somehow closes the okhttp reqest!
// val response = source.fetchImage(tachiPage).toBlocking().first()
// Note: also this which is the same
// val response = source.client.newCallWithProgress(source.imageRequest(tachiPage), tachiPage)
// .asObservableSuccess().toBlocking().first()
val response = source.client.newCall(source.imageRequest(tachiPage)).execute()
if (response.code == 200) {
val contentType = response.headers["content-type"]!!
filePath += contentType.substringAfter("image/")
Files.newOutputStream(Paths.get(filePath)).use { os ->
response.body!!.source().saveTo(os)
}
// writeStream(response.body!!.source(), filePath)
return Pair(
pathToInputStream(filePath),
contentType
)
} else {
throw Exception("request error! ${response.code}")
}
}

View File

@ -14,44 +14,36 @@ const style = {
backgroundColor: '#343a40',
} as React.CSSProperties;
interface IPage {
index: number
imageUrl: string
}
interface IData {
first: IChapter
second: IPage[]
}
const range = (n:number) => Array.from({ length: n }, (value, key) => key);
export default function Reader() {
const { setTitle } = useContext(NavBarTitle);
const [pages, setPages] = useState<IPage[]>([]);
const [pageCount, setPageCount] = useState<number>(-1);
const { chapterId, mangaId } = useParams<{chapterId: string, mangaId: string}>();
useEffect(() => {
fetch(`http://127.0.0.1:4567/api/v1/manga/${mangaId}/chapter/${chapterId}`)
.then((response) => response.json())
.then((data:IData) => {
setTitle(data.first.name);
setPages(data.second);
.then((data:IChapter) => {
setTitle(data.name);
setPageCount(data.pageCount);
});
}, []);
pages.sort((a, b) => (a.index - b.index));
let mapped;
if (pages.length === 0) {
mapped = <h3>wait</h3>;
} else {
mapped = pages.map(({ imageUrl }) => (
<div style={{ margin: '0 auto' }}>
<img src={imageUrl} alt="f" style={{ maxWidth: '100%' }} />
if (pageCount === -1) {
return (
<div style={style}>
<h3>wait</h3>
</div>
));
);
}
const mapped = range(pageCount).map((index) => (
<div style={{ margin: '0 auto' }}>
<img src={`http://127.0.0.1:4567/api/v1/manga/${mangaId}/chapter/${chapterId}/page/${index}`} alt="f" style={{ maxWidth: '100%' }} />
</div>
));
return (
<div style={style}>
{mapped}

View File

@ -34,4 +34,5 @@ interface IChapter {
chapter_number: number
scanlator: String
mangaId: number
pageCount: number
}