improve downloader

This commit is contained in:
Aria Moradi 2021-05-20 02:36:20 +04:30
parent ae8d30593f
commit 10dee8b345
8 changed files with 161 additions and 23 deletions

View File

@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory
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.LIB_VERSION_MAX
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.trustedSignatures
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.SourceTable
import ir.armor.tachidesk.server.ApplicationDirs

View File

@ -11,11 +11,11 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.SManga
import ir.armor.tachidesk.impl.MangaList.proxyThumbnailUrl
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.await
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.MangaTable
import ir.armor.tachidesk.model.dataclass.MangaDataClass

View File

@ -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.awaitSingle
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.MangaTable
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 sourceDir = source.toString()
val mangaDir = DiskUtil.buildValidFilename(mangaEntry[MangaTable.title])
val chapterDir = DiskUtil.buildValidFilename(
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
val chapterDir = SafePath.buildValidFilename(
when {
chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}"
else -> chapterEntry[ChapterTable.name]

View File

@ -8,13 +8,9 @@ package ir.armor.tachidesk.impl.util.storage
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import okhttp3.Response
import okio.buffer
import okio.sink
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.nio.file.Files
import java.nio.file.Paths
object CachedImageResponse {
private fun pathToInputStream(path: String): InputStream {
@ -45,18 +41,19 @@ object CachedImageResponse {
val response = fetcher()
if (response.code == 200) {
val contentType = response.headers["content-type"]!!
val fullPath = filePath + "." + contentType.substringAfter("image/")
val fullPath = "$filePath.tmp"
val saveFile = File(fullPath)
response.body!!.source().saveTo(saveFile)
Files.newOutputStream(Paths.get(fullPath)).use { output ->
response.body!!.source().use { input ->
output.sink().buffer().use {
it.writeAll(input)
it.flush()
}
}
}
return pathToInputStream(fullPath) to contentType
// find image type
val imageType = response.headers["content-type"]
?: ImageUtil.findImageType { saveFile.inputStream() }?.mime
?: "image/jpeg"
.substringAfter("image/")
saveFile.renameTo(File("$filePath.$imageType"))
return pathToInputStream(fullPath) to imageType
} else {
throw Exception("request error! ${response.code}")
}

View File

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

View File

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

View File

@ -8,7 +8,7 @@ package ir.armor.tachidesk.impl.util.storage
* 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
object DiskUtil {
object SafePath {
/**
* 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
@ -44,4 +44,4 @@ object DiskUtil {
else -> true
}
}
}
}

View File

@ -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
app.get("/api/v1/search/:searchTerm") { ctx ->
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
}
}
}
}