From 9cde46b5dae73ad6a1086abb1292c2a4b9c085ff Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Wed, 19 May 2021 23:03:40 +0430 Subject: [PATCH] Fix chpater names, closes #81 --- .../ir/armor/tachidesk/impl/Extension.kt | 2 +- .../kotlin/ir/armor/tachidesk/impl/Manga.kt | 4 +- .../kotlin/ir/armor/tachidesk/impl/Page.kt | 41 ++++---- .../tachidesk/impl/util/MangaDexHelper.kt | 99 ------------------- .../impl/util/{ => lang}/RxCoroutineBridge.kt | 0 .../util/{ => storage}/CachedImageResponse.kt | 2 +- .../tachidesk/impl/util/storage/DiskUtil.kt | 47 +++++++++ 7 files changed, 69 insertions(+), 126 deletions(-) delete mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/util/MangaDexHelper.kt rename server/src/main/kotlin/ir/armor/tachidesk/impl/util/{ => lang}/RxCoroutineBridge.kt (100%) rename server/src/main/kotlin/ir/armor/tachidesk/impl/util/{ => storage}/CachedImageResponse.kt (98%) create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/DiskUtil.kt diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt index 3a5bbab..83fcc61 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Extension.kt @@ -15,7 +15,7 @@ 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.CachedImageResponse.getCachedImageResponse +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 diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt index 1602ad4..88c732e 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Manga.kt @@ -11,8 +11,8 @@ 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.CachedImageResponse.clearCachedImage -import ir.armor.tachidesk.impl.util.CachedImageResponse.getCachedImageResponse +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 diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt index a03ac30..9ffce4f 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/Page.kt @@ -9,13 +9,13 @@ package ir.armor.tachidesk.impl import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource -import ir.armor.tachidesk.impl.util.CachedImageResponse.getCachedImageResponse 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.model.database.table.ChapterTable import ir.armor.tachidesk.model.database.table.MangaTable import ir.armor.tachidesk.model.database.table.PageTable -import ir.armor.tachidesk.model.database.table.SourceTable import ir.armor.tachidesk.server.ApplicationDirs import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.select @@ -28,7 +28,7 @@ import java.io.File import java.io.InputStream object Page { -/** + /** * A page might have a imageUrl ready from the get go, or we might need to * go an extra step and call fetchImageUrl to get it. */ @@ -52,9 +52,9 @@ object Page { val pageEntry = transaction { PageTable.select { (PageTable.chapter eq chapterId) and (PageTable.index eq index) }.first() } val tachiPage = Page( - pageEntry[PageTable.index], - pageEntry[PageTable.url], - pageEntry[PageTable.imageUrl] + pageEntry[PageTable.index], + pageEntry[PageTable.url], + pageEntry[PageTable.imageUrl] ) if (pageEntry[PageTable.imageUrl] == null) { @@ -68,33 +68,28 @@ object Page { val saveDir = getChapterDir(mangaId, chapterId) File(saveDir).mkdirs() - val fileName = index.toString() + val fileName = String.format("%03d", index) // e.g. 001.jpeg return getCachedImageResponse(saveDir, fileName) { source.fetchImage(tachiPage).awaitSingle() } } -// TODO: rewrite this to match tachiyomi private val applicationDirs by DI.global.instance() - fun getChapterDir(mangaId: Int, chapterId: Int): String { + private fun getChapterDir(mangaId: Int, chapterId: Int): String { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } - val sourceId = mangaEntry[MangaTable.sourceReference] - val source = getHttpSource(sourceId) - val sourceEntry = transaction { SourceTable.select { SourceTable.id eq sourceId }.first() } + val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) val chapterEntry = transaction { ChapterTable.select { ChapterTable.id eq chapterId }.first() } - val chapterDir = when { - chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}" - else -> chapterEntry[ChapterTable.name] - } + val sourceDir = source.toString() + val mangaDir = DiskUtil.buildValidFilename(mangaEntry[MangaTable.title]) + val chapterDir = DiskUtil.buildValidFilename( + when { + chapterEntry[ChapterTable.scanlator] != null -> "${chapterEntry[ChapterTable.scanlator]}_${chapterEntry[ChapterTable.name]}" + else -> chapterEntry[ChapterTable.name] + } + ) - val mangaTitle = mangaEntry[MangaTable.title] - val sourceName = source.toString() - - val mangaDir = "${applicationDirs.mangaRoot}/$sourceName/$mangaTitle/$chapterDir" - // make sure dirs exist - File(mangaDir).mkdirs() - return mangaDir + return "${applicationDirs.mangaRoot}/$sourceDir/$mangaDir/$chapterDir" } } diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/util/MangaDexHelper.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/util/MangaDexHelper.kt deleted file mode 100644 index 5b36230..0000000 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/util/MangaDexHelper.kt +++ /dev/null @@ -1,99 +0,0 @@ -package ir.armor.tachidesk.impl.util - -/* - * 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 eu.kanade.tachiyomi.network.POST -import eu.kanade.tachiyomi.source.online.HttpSource -import okhttp3.FormBody -import okhttp3.OkHttpClient -import java.net.URLEncoder - -// TODO: finish MangaDex support -class MangaDexHelper(private val mangaDexSource: HttpSource) { - - private fun clientBuilder(): OkHttpClient = clientBuilder(0) - - private fun clientBuilder( - r18Toggle: Int, - okHttpClient: OkHttpClient = mangaDexSource.network.client - ): OkHttpClient = okHttpClient.newBuilder() - .addNetworkInterceptor { chain -> - val originalCookies = chain.request().header("Cookie") ?: "" - val newReq = chain - .request() - .newBuilder() - .header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle)}") - .build() - chain.proceed(newReq) - }.build() - - private fun cookiesHeader(r18Toggle: Int): String { - val cookies = mutableMapOf() - cookies["mangadex_h_toggle"] = r18Toggle.toString() - return buildCookies(cookies) - } - - private fun buildCookies(cookies: Map) = - cookies.entries.joinToString(separator = "; ", postfix = ";") { - "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" - } - -// fun isLogged(): Boolean { -// val httpUrl = mangaDexSource.baseUrl.toHttpUrlOrNull()!! -// return network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME } -// } - - fun login(username: String, password: String, twoFactorCode: String = ""): Boolean { - val formBody = FormBody.Builder() - .add("login_username", username) - .add("login_password", password) - .add("no_js", "1") - .add("remember_me", "1") - - twoFactorCode.let { - formBody.add("two_factor", it) - } - - val response = clientBuilder().newCall( - POST( - "${mangaDexSource.baseUrl}/ajax/actions.ajax.php?function=login", - mangaDexSource.headers, - formBody.build() - ) - ).execute() - return response.body!!.string().isEmpty() - } -// -// fun logout(): Boolean { -// return withContext(Dispatchers.IO) { -// // https://mangadex.org/ajax/actions.ajax.php?function=logout -// val httpUrl = baseUrl.toHttpUrlOrNull()!! -// val listOfDexCookies = network.cookieManager.get(httpUrl) -// val cookie = listOfDexCookies.find { it.name == REMEMBER_ME } -// val token = cookie?.value -// if (token.isNullOrEmpty()) { -// return@withContext true -// } -// val result = clientBuilder().newCall( -// POSTWithCookie( -// "$baseUrl/ajax/actions.ajax.php?function=logout", -// REMEMBER_ME, -// token, -// headers -// ) -// ).execute() -// val resultStr = result.body!!.string() -// if (resultStr.contains("success", true)) { -// network.cookieManager.remove(httpUrl) -// return@withContext true -// } -// -// false -// } -// } -} diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/util/RxCoroutineBridge.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/util/lang/RxCoroutineBridge.kt similarity index 100% rename from server/src/main/kotlin/ir/armor/tachidesk/impl/util/RxCoroutineBridge.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/util/lang/RxCoroutineBridge.kt diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/util/CachedImageResponse.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/CachedImageResponse.kt similarity index 98% rename from server/src/main/kotlin/ir/armor/tachidesk/impl/util/CachedImageResponse.kt rename to server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/CachedImageResponse.kt index d21a011..3b621f5 100644 --- a/server/src/main/kotlin/ir/armor/tachidesk/impl/util/CachedImageResponse.kt +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/CachedImageResponse.kt @@ -1,4 +1,4 @@ -package ir.armor.tachidesk.impl.util +package ir.armor.tachidesk.impl.util.storage /* * Copyright (C) Contributors to the Suwayomi project diff --git a/server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/DiskUtil.kt b/server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/DiskUtil.kt new file mode 100644 index 0000000..0262fbb --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/impl/util/storage/DiskUtil.kt @@ -0,0 +1,47 @@ +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/. */ + +// adopted from: https://github.com/tachiyomiorg/tachiyomi/blob/4cefbce7c34e724b409b6ba127f3c6c5c346ad8d/app/src/main/java/eu/kanade/tachiyomi/util/storage/DiskUtil.kt +object DiskUtil { + /** + * 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 + * with a dot), but you can manually add it later. + */ + fun buildValidFilename(origName: String): String { + val name = origName.trim('.', ' ') + if (name.isEmpty()) { + return "(invalid)" + } + val sb = StringBuilder(name.length) + name.forEach { c -> + if (isValidFatFilenameChar(c)) { + sb.append(c) + } else { + sb.append('_') + } + } + // Even though vfat allows 255 UCS-2 chars, we might eventually write to + // ext4 through a FUSE layer, so use that limit minus 15 reserved characters. + return sb.toString().take(240) + } + + /** + * Returns true if the given character is a valid filename character, false otherwise. + */ + private fun isValidFatFilenameChar(c: Char): Boolean { + if (0x00.toChar() <= c && c <= 0x1f.toChar()) { + return false + } + return when (c) { + '"', '*', '/', ':', '<', '>', '?', '\\', '|', 0x7f.toChar() -> false + else -> true + } + } +} \ No newline at end of file