From 45808cd5309b98cc41643d82bd64a862395e86b8 Mon Sep 17 00:00:00 2001 From: Mitchell Syer Date: Sun, 10 Oct 2021 16:31:04 -0400 Subject: [PATCH] Support using a CatalogueSource instead of only HttpSources (#219) --- .../tachiyomi/source/local/LocalSource.kt | 90 ++----------------- .../suwayomi/tachidesk/manga/impl/Chapter.kt | 9 +- .../suwayomi/tachidesk/manga/impl/Manga.kt | 65 +++++++++----- .../tachidesk/manga/impl/MangaList.kt | 9 +- .../suwayomi/tachidesk/manga/impl/Page.kt | 18 ++-- .../suwayomi/tachidesk/manga/impl/Search.kt | 6 +- .../suwayomi/tachidesk/manga/impl/Source.kt | 27 +++--- .../tachidesk/manga/impl/util/DirName.kt | 4 +- .../manga/impl/util/GetHttpSource.kt | 63 +++++++++++-- 9 files changed, 136 insertions(+), 155 deletions(-) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt index eca34499..21e420a1 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/local/LocalSource.kt @@ -1,7 +1,7 @@ package eu.kanade.tachiyomi.source.local import com.github.junrar.Archive -import eu.kanade.tachiyomi.source.local.FileSystemInterceptor.fakeUrlFrom +import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.local.LocalSource.Format.Directory import eu.kanade.tachiyomi.source.local.LocalSource.Format.Epub import eu.kanade.tachiyomi.source.local.LocalSource.Format.Rar @@ -15,7 +15,6 @@ import eu.kanade.tachiyomi.source.model.MangasPage 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 eu.kanade.tachiyomi.util.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.lang.compareToCaseInsensitiveNaturalOrder import eu.kanade.tachiyomi.util.storage.EpubFile @@ -27,15 +26,6 @@ import kotlinx.serialization.json.intOrNull import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonPrimitive import mu.KotlinLogging -import okhttp3.Interceptor -import okhttp3.OkHttpClient -import okhttp3.Protocol -import okhttp3.Request -import okhttp3.Response -import okhttp3.ResponseBody.Companion.asResponseBody -import okhttp3.ResponseBody.Companion.toResponseBody -import okio.buffer -import okio.source import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.select @@ -51,14 +41,12 @@ import suwayomi.tachidesk.server.ApplicationDirs import uy.kohesive.injekt.injectLazy import java.io.File import java.io.FileInputStream -import java.io.FileNotFoundException import java.io.InputStream -import java.net.URLDecoder import java.util.Locale import java.util.concurrent.TimeUnit import java.util.zip.ZipFile -class LocalSource : HttpSource() { +class LocalSource : CatalogueSource { companion object { const val ID = 0L const val LANG = "localsourcelang" @@ -133,13 +121,8 @@ class LocalSource : HttpSource() { override val id = ID override val name = NAME override val lang = LANG - override val baseUrl: String = "" override val supportsLatest = true - override val client: OkHttpClient = super.client.newBuilder() - .addInterceptor(FileSystemInterceptor) - .build() - private val json: Json by injectLazy() override fun toString() = name @@ -181,7 +164,7 @@ class LocalSource : HttpSource() { // Try to find the cover val cover = getCoverFile(File("${applicationDirs.localMangaRoot}/$url")) if (cover != null && cover.exists()) { - thumbnail_url = fakeUrlFrom(cover.absolutePath) + thumbnail_url = cover.absolutePath } val chapters = fetchChapterList(this).toBlocking().first() @@ -197,8 +180,7 @@ class LocalSource : HttpSource() { // Copy the cover from the first chapter found. if (thumbnail_url == null) { try { - val dest = updateCover(chapter, this) - thumbnail_url = dest?.absolutePath?.let { fakeUrlFrom(it) } + thumbnail_url = updateCover(chapter, this)?.absolutePath } catch (e: Exception) { logger.error { e } } @@ -311,7 +293,7 @@ class LocalSource : HttpSource() { chapterFile.listFiles().orEmpty().sortedBy { it.name }.mapIndexed { index, page -> Page( index, - imageUrl = fakeUrlFrom(applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name) + imageUrl = applicationDirs.localMangaRoot + "/" + chapter.url + "/" + page.name ) } ) @@ -412,66 +394,4 @@ class LocalSource : HttpSource() { data class Rar(val file: File) : Format() data class Epub(val file: File) : Format() } - - // ///////////////////// Not used ///////////////////// // - - override fun mangaDetailsParse(response: Response): SManga = throw Exception("Not used") - - override fun chapterListParse(response: Response): List = throw Exception("Not used") - - override fun pageListParse(response: Response): List = throw Exception("Not used") - - override fun imageUrlParse(response: Response): String = throw Exception("Not used") - - override fun popularMangaRequest(page: Int): Request = throw Exception("Not used") - - override fun popularMangaParse(response: Response): MangasPage = throw Exception("Not used") - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = - throw Exception("Not used") - - override fun searchMangaParse(response: Response): MangasPage = throw Exception("Not used") - - override fun latestUpdatesRequest(page: Int): Request = throw Exception("Not used") - - override fun latestUpdatesParse(response: Response): MangasPage = throw Exception("Not used") -} - -private object FileSystemInterceptor : Interceptor { - fun fakeUrlFrom(path: String): String = "http://$path" - - private fun restoreFilePath(url: String): String { - val path = URLDecoder.decode(url.replaceFirst("http://", ""), "UTF-8") - - // Windows - if (System.getProperty("os.name").lowercase().startsWith("win")) { - // convert paths like "c/Users/..." to "c:/Users/..." - return StringBuilder(path).insert(1, ":").toString() - } - - return "/$path" - } - - override fun intercept(chain: Interceptor.Chain): Response { - val request = chain.request() - val url = request.url - val filePath = restoreFilePath(url.toString()) - return try { - Response.Builder() - .body(File(filePath).source().buffer().asResponseBody()) - .code(200) - .message("Some file") - .protocol(Protocol.HTTP_1_0) - .request(request) - .build() - } catch (e: FileNotFoundException) { - Response.Builder() - .body("".toResponseBody()) - .code(404) - .message(e.message ?: "File not found ($filePath)") - .protocol(Protocol.HTTP_1_0) - .request(request) - .build() - } - } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt index fb145b7c..f7255604 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -9,6 +9,7 @@ package suwayomi.tachidesk.manga.impl import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SortOrder @@ -20,7 +21,7 @@ import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.update import suwayomi.tachidesk.manga.impl.Manga.getManga import suwayomi.tachidesk.manga.impl.Page.getPageName -import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.getChapterDir import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse @@ -53,7 +54,7 @@ object Chapter { private suspend fun getSourceChapters(mangaId: Int): List { val manga = getManga(mangaId) - val source = getHttpSource(manga.sourceId.toLong()) + val source = getCatalogueSourceOrStub(manga.sourceId.toLong()) val sManga = SManga.create().apply { title = manga.title @@ -64,7 +65,7 @@ object Chapter { // Recognize number for new chapters. chapterList.forEach { - source.prepareNewChapter(it, sManga) + (source as? HttpSource)?.prepareNewChapter(it, sManga) ChapterRecognition.parseChapterNumber(it, sManga) } @@ -169,7 +170,7 @@ object Chapter { } val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } - val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) + val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val pageList = source.fetchPageList( SChapter.create().apply { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt index f0345607..8828e796 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -8,7 +8,9 @@ package suwayomi.tachidesk.manga.impl * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.online.HttpSource import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.select @@ -19,11 +21,12 @@ import org.kodein.di.conf.global import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.MangaList.proxyThumbnailUrl import suwayomi.tachidesk.manga.impl.Source.getSource -import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.network.await import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.clearCachedImage import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse +import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil import suwayomi.tachidesk.manga.impl.util.updateMangaDownloadDir import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass import suwayomi.tachidesk.manga.model.dataclass.toGenreList @@ -31,6 +34,8 @@ import suwayomi.tachidesk.manga.model.table.MangaMetaTable import suwayomi.tachidesk.manga.model.table.MangaStatus import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.server.ApplicationDirs +import java.io.File +import java.io.IOException import java.io.InputStream object Manga { @@ -68,7 +73,7 @@ object Manga { false ) } else { // initialize manga - val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) + val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val sManga = SManga.create().apply { url = mangaEntry[MangaTable.url] title = mangaEntry[MangaTable.title] @@ -95,11 +100,9 @@ object Manga { if (sManga.thumbnail_url != null && sManga.thumbnail_url.orEmpty().isNotEmpty()) it[MangaTable.thumbnail_url] = sManga.thumbnail_url - it[MangaTable.realUrl] = try { - source.mangaDetailsRequest(sManga).url.toString() - } catch (e: Exception) { - null - } + it[MangaTable.realUrl] = runCatching { + (source as? HttpSource)?.mangaDetailsRequest(sManga)?.url?.toString() + }.getOrNull() } } @@ -164,25 +167,39 @@ object Manga { val saveDir = applicationDirs.mangaThumbnailsRoot val fileName = mangaId.toString() - return getImageResponse(saveDir, fileName, useCache) { - val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } + val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } + val sourceId = mangaEntry[MangaTable.sourceReference] - val sourceId = mangaEntry[MangaTable.sourceReference] - val source = getHttpSource(sourceId) + return when (val source = getCatalogueSourceOrStub(sourceId)) { + is HttpSource -> getImageResponse(saveDir, fileName, useCache) { + val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url] + ?: if (!mangaEntry[MangaTable.initialized]) { + // initialize then try again + getManga(mangaId) + transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }[MangaTable.thumbnail_url]!! + } else { + // source provides no thumbnail url for this manga + throw NullPointerException() + } - val thumbnailUrl = mangaEntry[MangaTable.thumbnail_url] - ?: if (!mangaEntry[MangaTable.initialized]) { - // initialize then try again - getManga(mangaId) - transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }[MangaTable.thumbnail_url]!! - } else { - // source provides no thumbnail url for this manga - throw NullPointerException() - } - - source.client.newCall( - GET(thumbnailUrl, source.headers) - ).await() + source.client.newCall( + GET(thumbnailUrl, source.headers) + ).await() + } + is LocalSource -> { + val imageFile = mangaEntry[MangaTable.thumbnail_url]?.let { + val file = File(it) + if (file.exists()) { + file + } else { + null + } + } ?: throw IOException("Thumbnail does not exist") + val contentType = ImageUtil.findImageType { imageFile.inputStream() }?.mime + ?: "image/jpeg" + imageFile.inputStream() to contentType + } + else -> throw IllegalArgumentException("Unknown source") } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt index e9f2b93d..2ff304d1 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt @@ -13,7 +13,7 @@ import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap -import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass @@ -27,14 +27,15 @@ object MangaList { } suspend fun getMangaList(sourceId: Long, pageNum: Int = 1, popular: Boolean): PagedMangaListDataClass { - val source = getHttpSource(sourceId) + val source = getCatalogueSourceOrStub(sourceId) val mangasPage = if (popular) { source.fetchPopularManga(pageNum).awaitSingle() } else { - if (source.supportsLatest) + if (source.supportsLatest) { source.fetchLatestUpdates(pageNum).awaitSingle() - else + } else { throw Exception("Source $source doesn't support latest") + } } return mangasPage.processEntries(sourceId) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt index 15385a38..40820a30 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Page.kt @@ -17,11 +17,12 @@ import org.jetbrains.exposed.sql.update import org.kodein.di.DI import org.kodein.di.conf.global import org.kodein.di.instance -import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.getChapterDir import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse +import suwayomi.tachidesk.manga.impl.util.storage.ImageUtil import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.PageTable @@ -43,7 +44,7 @@ object Page { suspend fun getPageImage(mangaId: Int, chapterIndex: Int, index: Int, useCache: Boolean = true): Pair { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } - val source = getHttpSource(mangaEntry[MangaTable.sourceReference]) + val source = getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val chapterEntry = transaction { ChapterTable.select { (ChapterTable.sourceOrder eq chapterIndex) and (ChapterTable.manga eq mangaId) @@ -61,19 +62,20 @@ object Page { ) // we treat Local source differently - if (mangaEntry[MangaTable.sourceReference] == LocalSource.ID) { + if (source.id == LocalSource.ID) { // is of archive format if (LocalSource.pageCache.containsKey(chapterEntry[ChapterTable.url])) { - val pageStream = LocalSource.pageCache[chapterEntry[ChapterTable.url]]!![index]() - return pageStream to "image/jpeg" + val pageStream = LocalSource.pageCache[chapterEntry[ChapterTable.url]]!![index] + return pageStream() to (ImageUtil.findImageType { pageStream() }?.mime ?: "image/jpeg") } // is of directory format - return ImageResponse.getNoCacheImageResponse { - source.fetchImage(tachiyomiPage).awaitSingle() - } + val imageFile = File(tachiyomiPage.imageUrl!!) + return imageFile.inputStream() to (ImageUtil.findImageType { imageFile.inputStream() }?.mime ?: "image/jpeg") } + source as HttpSource + if (pageEntry[PageTable.imageUrl] == null) { val trueImageUrl = getTrueImageUrl(tachiyomiPage, source) transaction { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt index 0c16a3a9..661337e4 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Search.kt @@ -10,13 +10,13 @@ package suwayomi.tachidesk.manga.impl import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import suwayomi.tachidesk.manga.impl.MangaList.processEntries -import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass object Search { suspend fun sourceSearch(sourceId: Long, searchTerm: String, pageNum: Int): PagedMangaListDataClass { - val source = getHttpSource(sourceId) + val source = getCatalogueSourceOrStub(sourceId) val searchManga = source.fetchSearchManga(pageNum, searchTerm, getFilterListOf(sourceId)).awaitSingle() return searchManga.processEntries(sourceId) } @@ -25,7 +25,7 @@ object Search { private fun getFilterListOf(sourceId: Long, reset: Boolean = false): FilterList { if (reset || !filterListCache.containsKey(sourceId)) { - filterListCache[sourceId] = getHttpSource(sourceId).getFilterList() + filterListCache[sourceId] = getCatalogueSourceOrStub(sourceId).getFilterList() } return filterListCache[sourceId]!! } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt index 8a6c5841..f91e942d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Source.kt @@ -12,7 +12,6 @@ import android.content.Context import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.getPreferenceKey -import eu.kanade.tachiyomi.source.local.LocalSource import mu.KotlinLogging import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.selectAll @@ -21,7 +20,8 @@ import org.kodein.di.DI import org.kodein.di.conf.global import org.kodein.di.instance import suwayomi.tachidesk.manga.impl.extension.Extension.getExtensionIconUrl -import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSource +import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getCatalogueSourceOrStub import suwayomi.tachidesk.manga.impl.util.GetHttpSource.invalidateSourceCache import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass import suwayomi.tachidesk.manga.model.table.ExtensionTable @@ -36,7 +36,7 @@ object Source { fun getSourceList(): List { return transaction { SourceTable.selectAll().map { - val httpSource = getHttpSource(it[SourceTable.id].value) + val catalogueSource = getCatalogueSourceOrStub(it[SourceTable.id].value) val sourceExtension = ExtensionTable.select { ExtensionTable.id eq it[SourceTable.extension] }.first() SourceDataClass( @@ -44,10 +44,10 @@ object Source { it[SourceTable.name], it[SourceTable.lang], getExtensionIconUrl(sourceExtension[ExtensionTable.apkName]), - httpSource.supportsLatest, - httpSource is ConfigurableSource, + catalogueSource.supportsLatest, + catalogueSource is ConfigurableSource, it[SourceTable.isNsfw], - httpSource.toString(), + catalogueSource.toString(), ) } } @@ -55,13 +55,8 @@ object Source { fun getSource(sourceId: Long): SourceDataClass { // all the data extracted fresh form the source instance return transaction { - if (sourceId == LocalSource.ID) { - // initialize local source - getHttpSource(sourceId) - } - val source = SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() - val httpSource = source?.let { getHttpSource(sourceId) } + val catalogueSource = source?.let { getCatalogueSource(sourceId) } val extension = source?.let { ExtensionTable.select { ExtensionTable.id eq source[SourceTable.extension] }.first() } @@ -75,10 +70,10 @@ object Source { extension!![ExtensionTable.apkName] ) }, - httpSource?.supportsLatest, - httpSource?.let { it is ConfigurableSource }, + catalogueSource?.supportsLatest, + catalogueSource?.let { it is ConfigurableSource }, source?.get(SourceTable.isNsfw), - httpSource?.toString() + catalogueSource?.toString() ) } } @@ -103,7 +98,7 @@ object Source { * Gets a source's PreferenceScreen, puts the result into [preferenceScreenMap] */ fun getSourcePreferences(sourceId: Long): List { - val source = getHttpSource(sourceId) + val source = getCatalogueSourceOrStub(sourceId) if (source is ConfigurableSource) { val sourceShardPreferences = diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt index 97becc4e..4548837e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/DirName.kt @@ -22,7 +22,7 @@ private val applicationDirs by DI.global.instance() fun getMangaDir(mangaId: Int): String { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } - val source = GetHttpSource.getHttpSource(mangaEntry[MangaTable.sourceReference]) + val source = GetHttpSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val sourceDir = source.toString() val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title]) @@ -46,7 +46,7 @@ fun getChapterDir(mangaId: Int, chapterId: Int): String { /** return value says if rename/move was successful */ fun updateMangaDownloadDir(mangaId: Int, newTitle: String): Boolean { val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } - val source = GetHttpSource.getHttpSource(mangaEntry[MangaTable.sourceReference]) + val source = GetHttpSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference]) val sourceDir = source.toString() val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title]) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt index 1ef99262..be07223d 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/GetHttpSource.kt @@ -7,15 +7,22 @@ package suwayomi.tachidesk.manga.impl.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.CatalogueSource import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.local.LocalSource +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +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 org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import org.kodein.di.DI import org.kodein.di.conf.global import org.kodein.di.instance +import rx.Observable import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources import suwayomi.tachidesk.manga.model.table.ExtensionTable import suwayomi.tachidesk.manga.model.table.SourceTable @@ -23,22 +30,56 @@ import suwayomi.tachidesk.server.ApplicationDirs import java.util.concurrent.ConcurrentHashMap object GetHttpSource { - private val sourceCache = ConcurrentHashMap() + private val sourceCache = ConcurrentHashMap( + mapOf(LocalSource.ID to LocalSource()) + ) private val applicationDirs by DI.global.instance() - fun getHttpSource(sourceId: Long): HttpSource { - val cachedResult: HttpSource? = sourceCache[sourceId] + class StubSource(override val id: Long) : CatalogueSource { + override val lang: String = "other" + override val supportsLatest: Boolean = false + override val name: String + get() = id.toString() + override fun fetchPopularManga(page: Int): Observable { + return Observable.error(getSourceNotInstalledException()) + } + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return Observable.error(getSourceNotInstalledException()) + } + override fun fetchLatestUpdates(page: Int): Observable { + return Observable.error(getSourceNotInstalledException()) + } + override fun getFilterList(): FilterList { + return FilterList() + } + override fun fetchMangaDetails(manga: SManga): Observable { + return Observable.error(getSourceNotInstalledException()) + } + override fun fetchChapterList(manga: SManga): Observable> { + return Observable.error(getSourceNotInstalledException()) + } + override fun fetchPageList(chapter: SChapter): Observable> { + return Observable.error(getSourceNotInstalledException()) + } + override fun toString(): String { + return name + } + private fun getSourceNotInstalledException(): SourceNotInstalledException { + return SourceNotInstalledException(id) + } + inner class SourceNotInstalledException(val id: Long) : + Exception("Source not installed: $id") + } + + fun getCatalogueSource(sourceId: Long): CatalogueSource? { + val cachedResult: CatalogueSource? = sourceCache[sourceId] if (cachedResult != null) { return cachedResult } val sourceRecord = transaction { - SourceTable.select { SourceTable.id eq sourceId }.firstOrNull()!! - } - - if (sourceId == LocalSource.ID) { - return LocalSource() - } + SourceTable.select { SourceTable.id eq sourceId }.firstOrNull() + } ?: return null val extensionId = sourceRecord[SourceTable.extension] val extensionRecord = transaction { @@ -60,6 +101,10 @@ object GetHttpSource { return sourceCache[sourceId]!! } + fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource { + return getCatalogueSource(sourceId) ?: StubSource(sourceId) + } + fun invalidateSourceCache(sourceId: Long) { sourceCache.remove(sourceId) }