mirror of
https://github.com/tachiyomiorg/tachiyomi-extensions-inspector.git
synced 2025-01-26 07:15:30 +01:00
improve downloader
This commit is contained in:
parent
ae8d30593f
commit
10dee8b345
@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
|
|||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
import ir.armor.tachidesk.impl.ExtensionsList.extensionTableAsDataClass
|
import ir.armor.tachidesk.impl.ExtensionsList.extensionTableAsDataClass
|
||||||
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
|
|
||||||
import ir.armor.tachidesk.impl.util.PackageTools.EXTENSION_FEATURE
|
import ir.armor.tachidesk.impl.util.PackageTools.EXTENSION_FEATURE
|
||||||
import ir.armor.tachidesk.impl.util.PackageTools.LIB_VERSION_MAX
|
import ir.armor.tachidesk.impl.util.PackageTools.LIB_VERSION_MAX
|
||||||
import ir.armor.tachidesk.impl.util.PackageTools.LIB_VERSION_MIN
|
import ir.armor.tachidesk.impl.util.PackageTools.LIB_VERSION_MIN
|
||||||
@ -27,6 +26,7 @@ import ir.armor.tachidesk.impl.util.PackageTools.getSignatureHash
|
|||||||
import ir.armor.tachidesk.impl.util.PackageTools.loadExtensionSources
|
import ir.armor.tachidesk.impl.util.PackageTools.loadExtensionSources
|
||||||
import ir.armor.tachidesk.impl.util.PackageTools.trustedSignatures
|
import ir.armor.tachidesk.impl.util.PackageTools.trustedSignatures
|
||||||
import ir.armor.tachidesk.impl.util.await
|
import ir.armor.tachidesk.impl.util.await
|
||||||
|
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
|
||||||
import ir.armor.tachidesk.model.database.table.ExtensionTable
|
import ir.armor.tachidesk.model.database.table.ExtensionTable
|
||||||
import ir.armor.tachidesk.model.database.table.SourceTable
|
import ir.armor.tachidesk.model.database.table.SourceTable
|
||||||
import ir.armor.tachidesk.server.ApplicationDirs
|
import ir.armor.tachidesk.server.ApplicationDirs
|
||||||
|
@ -11,11 +11,11 @@ import eu.kanade.tachiyomi.network.GET
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import ir.armor.tachidesk.impl.MangaList.proxyThumbnailUrl
|
import ir.armor.tachidesk.impl.MangaList.proxyThumbnailUrl
|
||||||
import ir.armor.tachidesk.impl.Source.getSource
|
import ir.armor.tachidesk.impl.Source.getSource
|
||||||
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.clearCachedImage
|
|
||||||
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
|
|
||||||
import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource
|
import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource
|
||||||
import ir.armor.tachidesk.impl.util.await
|
import ir.armor.tachidesk.impl.util.await
|
||||||
import ir.armor.tachidesk.impl.util.awaitSingle
|
import ir.armor.tachidesk.impl.util.awaitSingle
|
||||||
|
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.clearCachedImage
|
||||||
|
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
|
||||||
import ir.armor.tachidesk.model.database.table.MangaStatus
|
import ir.armor.tachidesk.model.database.table.MangaStatus
|
||||||
import ir.armor.tachidesk.model.database.table.MangaTable
|
import ir.armor.tachidesk.model.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.model.dataclass.MangaDataClass
|
import ir.armor.tachidesk.model.dataclass.MangaDataClass
|
||||||
|
@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
|||||||
import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource
|
import ir.armor.tachidesk.impl.util.GetHttpSource.getHttpSource
|
||||||
import ir.armor.tachidesk.impl.util.awaitSingle
|
import ir.armor.tachidesk.impl.util.awaitSingle
|
||||||
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
|
import ir.armor.tachidesk.impl.util.storage.CachedImageResponse.getCachedImageResponse
|
||||||
import ir.armor.tachidesk.impl.util.storage.DiskUtil
|
import ir.armor.tachidesk.impl.util.storage.SafePath
|
||||||
import ir.armor.tachidesk.model.database.table.ChapterTable
|
import ir.armor.tachidesk.model.database.table.ChapterTable
|
||||||
import ir.armor.tachidesk.model.database.table.MangaTable
|
import ir.armor.tachidesk.model.database.table.MangaTable
|
||||||
import ir.armor.tachidesk.model.database.table.PageTable
|
import ir.armor.tachidesk.model.database.table.PageTable
|
||||||
@ -82,8 +82,8 @@ object Page {
|
|||||||
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() }
|
val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() }
|
||||||
|
|
||||||
val sourceDir = source.toString()
|
val sourceDir = source.toString()
|
||||||
val mangaDir = DiskUtil.buildValidFilename(mangaEntry[MangaTable.title])
|
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
||||||
val chapterDir = DiskUtil.buildValidFilename(
|
val chapterDir = SafePath.buildValidFilename(
|
||||||
when {
|
when {
|
||||||
chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
|
chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
|
||||||
else -> chapterEntry[ChapterTable.name]
|
else -> chapterEntry[ChapterTable.name]
|
||||||
|
@ -8,13 +8,9 @@ package ir.armor.tachidesk.impl.util.storage
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import okio.buffer
|
|
||||||
import okio.sink
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Paths
|
|
||||||
|
|
||||||
object CachedImageResponse {
|
object CachedImageResponse {
|
||||||
private fun pathToInputStream(path: String): InputStream {
|
private fun pathToInputStream(path: String): InputStream {
|
||||||
@ -45,18 +41,19 @@ object CachedImageResponse {
|
|||||||
val response = fetcher()
|
val response = fetcher()
|
||||||
|
|
||||||
if (response.code == 200) {
|
if (response.code == 200) {
|
||||||
val contentType = response.headers["content-type"]!!
|
val fullPath = "$filePath.tmp"
|
||||||
val fullPath = filePath + "." + contentType.substringAfter("image/")
|
val saveFile = File(fullPath)
|
||||||
|
response.body!!.source().saveTo(saveFile)
|
||||||
|
|
||||||
Files.newOutputStream(Paths.get(fullPath)).use { output ->
|
// find image type
|
||||||
response.body!!.source().use { input ->
|
val imageType = response.headers["content-type"]
|
||||||
output.sink().buffer().use {
|
?: ImageUtil.findImageType { saveFile.inputStream() }?.mime
|
||||||
it.writeAll(input)
|
?: "image/jpeg"
|
||||||
it.flush()
|
.substringAfter("image/")
|
||||||
}
|
|
||||||
}
|
saveFile.renameTo(File("$filePath.$imageType"))
|
||||||
}
|
|
||||||
return pathToInputStream(fullPath) to contentType
|
return pathToInputStream(fullPath) to imageType
|
||||||
} else {
|
} else {
|
||||||
throw Exception("request error! ${response.code}")
|
throw Exception("request error! ${response.code}")
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
package ir.armor.tachidesk.impl.util.storage
|
||||||
|
|
||||||
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/. */
|
||||||
|
|
||||||
|
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/ff369010074b058bb734ce24c66508300e6e9ac6/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt
|
||||||
|
object ImageUtil {
|
||||||
|
|
||||||
|
fun findImageType(openStream: () -> InputStream): ImageType? {
|
||||||
|
return openStream().use { findImageType(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findImageType(stream: InputStream): ImageType? {
|
||||||
|
try {
|
||||||
|
val bytes = ByteArray(8)
|
||||||
|
|
||||||
|
val length = if (stream.markSupported()) {
|
||||||
|
stream.mark(bytes.size)
|
||||||
|
stream.read(bytes, 0, bytes.size).also { stream.reset() }
|
||||||
|
} else {
|
||||||
|
stream.read(bytes, 0, bytes.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (length == -1) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes.compareWith(charByteArrayOf(0xFF, 0xD8, 0xFF))) {
|
||||||
|
return ImageType.JPG
|
||||||
|
}
|
||||||
|
if (bytes.compareWith(charByteArrayOf(0x89, 0x50, 0x4E, 0x47))) {
|
||||||
|
return ImageType.PNG
|
||||||
|
}
|
||||||
|
if (bytes.compareWith("GIF8".toByteArray())) {
|
||||||
|
return ImageType.GIF
|
||||||
|
}
|
||||||
|
if (bytes.compareWith("RIFF".toByteArray())) {
|
||||||
|
return ImageType.WEBP
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.compareWith(magic: ByteArray): Boolean {
|
||||||
|
return magic.indices.none { this[it] != magic[it] }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun charByteArrayOf(vararg bytes: Int): ByteArray {
|
||||||
|
return ByteArray(bytes.size).apply {
|
||||||
|
for (i in bytes.indices) {
|
||||||
|
set(i, bytes[i].toByte())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ImageType(val mime: String) {
|
||||||
|
JPG("image/jpeg"),
|
||||||
|
PNG("image/png"),
|
||||||
|
GIF("image/gif"),
|
||||||
|
WEBP("image/webp")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
package ir.armor.tachidesk.impl.util.storage
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 okio.BufferedSource
|
||||||
|
import okio.buffer
|
||||||
|
import okio.sink
|
||||||
|
import java.io.File
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/ff369010074b058bb734ce24c66508300e6e9ac6/app/src/main/java/eu/kanade/tachiyomi/util/storage/OkioExtensions.kt
|
||||||
|
/**
|
||||||
|
* Saves the given source to a file and closes it. Directories will be created if needed.
|
||||||
|
*
|
||||||
|
* @param file the file where the source is copied.
|
||||||
|
*/
|
||||||
|
fun BufferedSource.saveTo(file: File) {
|
||||||
|
try {
|
||||||
|
// Create parent dirs if needed
|
||||||
|
file.parentFile.mkdirs()
|
||||||
|
|
||||||
|
// Copy to destination
|
||||||
|
saveTo(file.outputStream())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
close()
|
||||||
|
file.delete()
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,7 @@ package ir.armor.tachidesk.impl.util.storage
|
|||||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/4cefbce7c34e724b409b6ba127f3c6c5c346ad8d/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt
|
// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/4cefbce7c34e724b409b6ba127f3c6c5c346ad8d/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt
|
||||||
object DiskUtil {
|
object SafePath {
|
||||||
/**
|
/**
|
||||||
* Mutate the given filename to make it valid for a FAT filesystem,
|
* Mutate the given filename to make it valid for a FAT filesystem,
|
||||||
* replacing any invalid characters with "_". This method doesn't allow hidden files (starting
|
* replacing any invalid characters with "_". This method doesn't allow hidden files (starting
|
||||||
@ -44,4 +44,4 @@ object DiskUtil {
|
|||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -302,6 +302,16 @@ object JavalinSetup {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// submit a chapter for download
|
||||||
|
app.put("/api/v1/manga/:mangaId/chapter/:chapterIndex/download") { ctx ->
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
// cancel a chapter download
|
||||||
|
app.delete("/api/v1/manga/:mangaId/chapter/:chapterIndex/download") { ctx ->
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
// global search, Not implemented yet
|
// global search, Not implemented yet
|
||||||
app.get("/api/v1/search/:searchTerm") { ctx ->
|
app.get("/api/v1/search/:searchTerm") { ctx ->
|
||||||
val searchTerm = ctx.pathParam("searchTerm")
|
val searchTerm = ctx.pathParam("searchTerm")
|
||||||
@ -432,5 +442,19 @@ object JavalinSetup {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Download queue stats
|
||||||
|
app.ws("/api/v1/downloads") { ws ->
|
||||||
|
ws.onConnect { ctx ->
|
||||||
|
// TODO: send current stat
|
||||||
|
// TODO: add to downlad subscribers
|
||||||
|
}
|
||||||
|
ws.onMessage {
|
||||||
|
// TODO: send current stat
|
||||||
|
}
|
||||||
|
ws.onClose { ctx ->
|
||||||
|
// TODO: remove from subscribers
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user