Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d35e31e02d | |||
| 6c4ca36c09 | |||
| 70355dc505 | |||
| 20aeaf2a05 | |||
| 1f13e1d08b | |||
| eb9d35c123 | |||
| 45808cd530 | |||
| fd715a3f92 | |||
| e3b32367a7 | |||
| bf9554a746 | |||
| b9f8ca1488 | |||
| e8c4159678 | |||
| e57e71629e | |||
| 2bfd9d24a4 | |||
| b18b8fe22f | |||
| b154ff2f9d | |||
| e9b764b63c | |||
| 7216b97d92 | |||
| 0e9d93b194 | |||
| 2cbee62f0a | |||
| 379e9da5fe | |||
| ae7caa4901 | |||
| cd8b4c9dd7 | |||
| 60cd61dfd2 | |||
| 5a6637d9fc | |||
| dca7ed23f5 | |||
| 8cb5791f3b | |||
| 9b67f2c58f | |||
| 3815810d4f |
@@ -45,10 +45,6 @@ jobs:
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Download android.jar
|
||||
run: |
|
||||
cd master
|
||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||
|
||||
- name: Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
|
||||
@@ -47,11 +47,6 @@ jobs:
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Download android.jar
|
||||
run: |
|
||||
cd master
|
||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||
|
||||
- name: Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
env:
|
||||
@@ -64,12 +59,6 @@ jobs:
|
||||
dependencies-cache-enabled: true
|
||||
configuration-cache-enabled: true
|
||||
|
||||
# - name: Mock Build and copy webUI, Build Jar
|
||||
# run: |
|
||||
# mkdir -p master/server/build
|
||||
# cd master/server/build
|
||||
# echo "test" > Tachidesk-v0.3.8-r583.jar
|
||||
|
||||
- name: Generate Tag Name
|
||||
id: GenTagName
|
||||
run: |
|
||||
@@ -87,11 +76,6 @@ jobs:
|
||||
./unix-bundler.sh macOS-x64
|
||||
./unix-bundler.sh macOS-arm64
|
||||
|
||||
# - name: Mock make windows packages
|
||||
# run: |
|
||||
# cd master/server/build
|
||||
# echo test > Tachidesk-v0.3.8-r580-win32.zip
|
||||
|
||||
- name: Checkout preview branch
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
|
||||
@@ -46,11 +46,6 @@ jobs:
|
||||
mkdir -p ~/.gradle
|
||||
cp .github/runner-files/ci-gradle.properties ~/.gradle/gradle.properties
|
||||
|
||||
- name: Download android.jar
|
||||
run: |
|
||||
cd master
|
||||
curl https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar -o AndroidCompat/lib/android.jar
|
||||
|
||||
- name: Build and copy webUI, Build Jar
|
||||
uses: eskatos/gradle-command-action@v1
|
||||
env:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
dependencies {
|
||||
// Android stub library
|
||||
implementation(fileTree("lib/"))
|
||||
implementation("com.github.Suwayomi:android-jar:1.0.0")
|
||||
|
||||
// XML
|
||||
compileOnly("xmlpull:xmlpull:1.1.3.4a")
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
android.jar
|
||||
@@ -1,10 +1,11 @@
|
||||
# Server: v0.X.Y-rXXX + WebUI: rXXX
|
||||
# Server: v0.X.Y-next + WebUI: rXXX
|
||||
## TL;DR
|
||||
<!-- TODO: fill before release -->
|
||||
- N/A
|
||||
|
||||
## Tachidesk-Server Changelog
|
||||
- N/A
|
||||
|
||||
|
||||
## Tachidesk-WebUI Changelog
|
||||
- N/A
|
||||
|
||||
|
||||
|
||||
+32
-1
@@ -1,6 +1,37 @@
|
||||
# Server: v0.5.4 + WebUI: r820
|
||||
## TL;DR
|
||||
- Fixed ReadComicOnline, Toonily and possibly other sources not working
|
||||
- Backup and Restore now includes Updates tab data
|
||||
- Removed Anime support from WebUI, Anime support will also be removed from Tachidesk-Server in a future update
|
||||
|
||||
## Tachidesk-Server Changelog
|
||||
- (r973) convert android.jar lib to a maven repo
|
||||
- (r978) mimic Tachiyomi's behaviour more closely, fixes ReadComicOnline (EN)
|
||||
- (r980) fix export chapter ordering, include new props in backup
|
||||
- (r982) remove isNsfw annotation detection
|
||||
- (r984) use correct time conversion units when doing backups
|
||||
- (r989) Support using a CatalogueSource instead of only HttpSources ([#219](https://github.com/Suwayomi/Tachidesk-Server/pull/219) by @Syer10)
|
||||
- (r991) Use a custom task to run electron ([#220](https://github.com/Suwayomi/Tachidesk-Server/pull/220) by @Syer10)
|
||||
|
||||
## Tachidesk-WebUI Changelog
|
||||
- (r810) fix wrong strings in set Server Address dialog, fixes [#39](https://github.com/Suwayomi/Tachidesk-WebUI/issues/39)
|
||||
- (r811) fix chapterFetch loop
|
||||
- (r812) fix overlapping requests
|
||||
- (r813) fix typo
|
||||
- (r814) Better portrait support ([#41](https://github.com/Suwayomi/Tachidesk-WebUI/issues/41) by @minhe7735)
|
||||
- (r815) fixes Reader navbar colors when in light mode ([#43](https://github.com/Suwayomi/Tachidesk-WebUI/issues/43) by @abhijeetChawla)
|
||||
- (r816) default languages cleanup, force Local source enabled
|
||||
- (r817) force Local source at LangSelect
|
||||
- (r818) rename ExtensionLangSelect: generic name for generic use
|
||||
- (r819) don't show anime anymore
|
||||
- (r820) Remove Anime support
|
||||
|
||||
|
||||
|
||||
# Server: v0.5.3 + WebUI: r809
|
||||
## TL;DR
|
||||
<!-- TODO: fill before release -->
|
||||
- added support for a equivalent page to Tachiyomi's Updates tab
|
||||
- fix launchers not working on macOS M1/arm64
|
||||
|
||||
## Tachidesk-Server Changelog
|
||||
- (r956) fix macOS-arm64 bundle launchers not working
|
||||
|
||||
+6
-3
@@ -22,9 +22,6 @@ This structure is chosen to
|
||||
You need these software packages installed in order to build the project
|
||||
|
||||
- Java Development Kit and Java Runtime Environment version 8 or newer(both Oracle JDK and OpenJDK works)
|
||||
- Android stubs jar
|
||||
- **Manual download:** Download [android.jar](https://raw.githubusercontent.com/Suwayomi/Tachidesk/android-jar/android.jar) and put it under `AndroidCompat/lib`.
|
||||
- **Automated download:** Run `AndroidCompat/getAndroid.sh`(macOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows) from project's root directory to download and rebuild the jar file from Google's repository.
|
||||
|
||||
### building the full-blown jar (Tachidesk-Server + Tachidesk-WebUI bundle)
|
||||
Run `./gradlew server:downloadWebUI server:shadowJar`, the resulting built jar file will be `server/build/Tachidesk-Server-vX.Y.Z-rxxx.jar`.
|
||||
@@ -37,3 +34,9 @@ First Build the jar, then cd into the `scripts` directory and run `./windows-bun
|
||||
|
||||
## Running in development mode
|
||||
run `./gradlew :server:run --stacktrace` to run the server
|
||||
|
||||
## Building the android-jar maven repository
|
||||
Run `AndroidCompat/getAndroid.sh`(macOS/Linux) or `AndroidCompat/getAndroid.ps1`(Windows)
|
||||
from project's root directory to download and rebuild the jar file from Google's repository,
|
||||
then use `AndroidCompat/lib/android.jar` to manually create a maven repository inside the `android-jar` git branch.
|
||||
Update the dependency declaration afterwards.
|
||||
@@ -18,8 +18,8 @@ Ability to read and write Tachiyomi compatible backups and syncing is a planned
|
||||
Yes, you need a client/user interface app as a front-end for Tachidesk-Server, if you Directly Download Tachidesk-Server you'll get a bundled version of [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI) with it.
|
||||
|
||||
Here's a list of known clients/user interfaces for Tachidesk-Server:
|
||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The "official" front-end for Tachidesk-Server, A native desktop Application.
|
||||
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/electrion front-end that Tachidesk-Server is traditionally shipped with.
|
||||
- [Tachidesk-JUI](https://github.com/Suwayomi/Tachidesk-JUI): The "official" native desktop front-end for Tachidesk-Server. Currently the most advanced.
|
||||
- [Tachidesk-WebUI](https://github.com/Suwayomi/Tachidesk-WebUI): The web/ElectronJS front-end that Tachidesk-Server is traditionally shipped with. Usually gets new features faster.
|
||||
- [Tachidesk-qtui](https://github.com/Suwayomi/Tachidesk-qtui): A C++/Qt front-end for mobile devices(Android/linux), in super early stage of development.
|
||||
- [Equinox](https://github.com/Suwayomi/Equinox): A web user interface made with Vue.js, in super early stage of development.
|
||||
|
||||
@@ -32,6 +32,7 @@ Here is a list of current features:
|
||||
- Searching and browsing installed sources
|
||||
- Ability to download Manga for offline read
|
||||
- Backup and restore support powered by Tachiyomi Backups
|
||||
- Viewing latest updated chapters.
|
||||
- From Aniyomi
|
||||
- Installing and executing Aniyomi's Extensions
|
||||
- Searching and browsing installed sources.
|
||||
@@ -39,8 +40,6 @@ Here is a list of current features:
|
||||
|
||||
**Note:** These are capabilities of Tachidesk-Server, the actual working support is provided by each front-end app, checkout their respective readme for more info.
|
||||
|
||||
**Note:** Tachidesk-Server is alpha software and can break rarely and/or with each update. See [Troubleshooting](https://github.com/Suwayomi/Tachidesk-Server/wiki/Troubleshooting) if it happens.
|
||||
|
||||
# Downloading and Running the app
|
||||
## General Requirements
|
||||
In order to use the app effectively you need the following:
|
||||
|
||||
@@ -18,6 +18,7 @@ allprojects {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven("https://jitpack.io")
|
||||
maven("https://github.com/Suwayomi/Tachidesk-Server/raw/android-jar/")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ const val kotlinVersion = "1.5.30"
|
||||
const val MainClass = "suwayomi.tachidesk.MainKt"
|
||||
|
||||
// should be bumped with each stable release
|
||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.3"
|
||||
val tachideskVersion = System.getenv("ProductVersion") ?: "v0.5.4"
|
||||
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r809"
|
||||
val webUIRevisionTag = System.getenv("WebUIRevision") ?: "r820"
|
||||
|
||||
// counts commits on the master branch
|
||||
val tachideskRevision = runCatching {
|
||||
|
||||
+12
-6
@@ -74,12 +74,6 @@ dependencies {
|
||||
|
||||
application {
|
||||
mainClass.set(MainClass)
|
||||
|
||||
// uncomment for testing electron
|
||||
// applicationDefaultJvmArgs = listOf(
|
||||
// "-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
|
||||
// "-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
|
||||
// )
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
@@ -164,4 +158,16 @@ tasks {
|
||||
|
||||
overwrite(shouldOverwrite())
|
||||
}
|
||||
|
||||
register("runElectron") {
|
||||
group = "application"
|
||||
finalizedBy(run)
|
||||
doFirst {
|
||||
application.applicationDefaultJvmArgs = listOf(
|
||||
"-Dsuwayomi.tachidesk.config.server.webUIInterface=electron",
|
||||
// Change this to the installed electron application
|
||||
"-Dsuwayomi.tachidesk.config.server.electronPath=/usr/bin/electron"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
package eu.kanade.tachiyomi.annoations
|
||||
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class Nsfw
|
||||
@@ -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<SChapter> = throw Exception("Not used")
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> = 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,9 +21,9 @@ 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.getChapterDir
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaChapterDataClass
|
||||
@@ -53,7 +54,7 @@ object Chapter {
|
||||
|
||||
private suspend fun getSourceChapters(mangaId: Int): List<ChapterDataClass> {
|
||||
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 {
|
||||
|
||||
@@ -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.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
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,37 +73,36 @@ 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]
|
||||
}
|
||||
val fetchedManga = source.fetchMangaDetails(sManga).awaitSingle()
|
||||
val networkManga = source.fetchMangaDetails(sManga).awaitSingle()
|
||||
sManga.copyFrom(networkManga)
|
||||
|
||||
transaction {
|
||||
MangaTable.update({ MangaTable.id eq mangaId }) {
|
||||
|
||||
if (fetchedManga.title != mangaEntry[MangaTable.title]) {
|
||||
val canUpdateTitle = updateMangaDownloadDir(mangaId, fetchedManga.title)
|
||||
if (sManga.title != mangaEntry[MangaTable.title]) {
|
||||
val canUpdateTitle = updateMangaDownloadDir(mangaId, sManga.title)
|
||||
|
||||
if (canUpdateTitle)
|
||||
it[MangaTable.title] = fetchedManga.title
|
||||
it[MangaTable.title] = sManga.title
|
||||
}
|
||||
it[MangaTable.initialized] = true
|
||||
|
||||
it[MangaTable.artist] = fetchedManga.artist
|
||||
it[MangaTable.author] = fetchedManga.author
|
||||
it[MangaTable.description] = truncate(fetchedManga.description, 4096)
|
||||
it[MangaTable.genre] = fetchedManga.genre
|
||||
it[MangaTable.status] = fetchedManga.status
|
||||
if (fetchedManga.thumbnail_url != null && fetchedManga.thumbnail_url.orEmpty().isNotEmpty())
|
||||
it[MangaTable.thumbnail_url] = fetchedManga.thumbnail_url
|
||||
it[MangaTable.artist] = sManga.artist
|
||||
it[MangaTable.author] = sManga.author
|
||||
it[MangaTable.description] = truncate(sManga.description, 4096)
|
||||
it[MangaTable.genre] = sManga.genre
|
||||
it[MangaTable.status] = sManga.status
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,11 +120,11 @@ object Manga {
|
||||
|
||||
true,
|
||||
|
||||
fetchedManga.artist,
|
||||
fetchedManga.author,
|
||||
fetchedManga.description,
|
||||
fetchedManga.genre.toGenreList(),
|
||||
MangaStatus.valueOf(fetchedManga.status).name,
|
||||
sManga.artist,
|
||||
sManga.author,
|
||||
sManga.description,
|
||||
sManga.genre.toGenreList(),
|
||||
MangaStatus.valueOf(sManga.status).name,
|
||||
mangaEntry[MangaTable.inLibrary],
|
||||
mangaEntry[MangaTable.inLibraryAt],
|
||||
getSource(mangaEntry[MangaTable.sourceReference]),
|
||||
@@ -163,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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,8 +13,8 @@ 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.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.model.dataclass.MangaDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.PagedMangaListDataClass
|
||||
import suwayomi.tachidesk.manga.model.dataclass.toGenreList
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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.getChapterDir
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
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<InputStream, String> {
|
||||
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 {
|
||||
|
||||
@@ -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.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
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]!!
|
||||
}
|
||||
|
||||
@@ -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,8 +20,9 @@ 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.invalidateSourceCache
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSourceOrStub
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.invalidateSourceCache
|
||||
import suwayomi.tachidesk.manga.model.dataclass.SourceDataClass
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
@@ -36,7 +36,7 @@ object Source {
|
||||
fun getSourceList(): List<SourceDataClass> {
|
||||
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<PreferenceObject> {
|
||||
val source = getHttpSource(sourceId)
|
||||
val source = getCatalogueSourceOrStub(sourceId)
|
||||
|
||||
if (source is ConfigurableSource) {
|
||||
val sourceShardPreferences =
|
||||
|
||||
+4
-3
@@ -32,6 +32,7 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import suwayomi.tachidesk.manga.model.table.toDataClass
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object ProtoBackupExport : ProtoBackupBase() {
|
||||
suspend fun createBackup(flags: BackupFlags): InputStream {
|
||||
@@ -68,7 +69,7 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
|
||||
MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
|
||||
mangaRow[MangaTable.thumbnail_url],
|
||||
0, // not supported in Tachidesk
|
||||
TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
|
||||
0, // not supported in Tachidesk
|
||||
)
|
||||
|
||||
@@ -84,10 +85,10 @@ object ProtoBackupExport : ProtoBackupBase() {
|
||||
it.read,
|
||||
it.bookmarked,
|
||||
it.lastPageRead,
|
||||
0, // not supported in Tachidesk
|
||||
TimeUnit.SECONDS.toMillis(it.fetchedAt),
|
||||
it.uploadDate,
|
||||
it.chapterNumber,
|
||||
it.index,
|
||||
chapters.size - it.index,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
import java.io.InputStream
|
||||
import java.lang.Integer.max
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
object ProtoBackupImport : ProtoBackupBase() {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
@@ -148,6 +149,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
it[initialized] = manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}.value
|
||||
|
||||
// insert chapter data
|
||||
@@ -166,6 +169,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
it[isRead] = chapter.read
|
||||
it[lastPageRead] = chapter.last_page_read
|
||||
it[isBookmarked] = chapter.bookmark
|
||||
|
||||
it[fetchedAt] = TimeUnit.MILLISECONDS.toSeconds(chapter.date_fetch)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,6 +195,8 @@ object ProtoBackupImport : ProtoBackupBase() {
|
||||
it[initialized] = dbManga[initialized] || manga.description != null
|
||||
|
||||
it[inLibrary] = manga.favorite || dbManga[inLibrary]
|
||||
|
||||
it[inLibraryAt] = TimeUnit.MILLISECONDS.toSeconds(manga.date_added)
|
||||
}
|
||||
|
||||
// merge chapter data
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.extensionTableAsDataClass
|
||||
import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.EXTENSION_FEATURE
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.LIB_VERSION_MAX
|
||||
@@ -39,6 +38,7 @@ import suwayomi.tachidesk.manga.impl.util.PackageTools.dex2jar
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.getPackageInfo
|
||||
import suwayomi.tachidesk.manga.impl.util.PackageTools.loadExtensionSources
|
||||
import suwayomi.tachidesk.manga.impl.util.network.await
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.ImageResponse.getImageResponse
|
||||
import suwayomi.tachidesk.manga.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
@@ -51,9 +51,6 @@ object Extension {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
private fun Any.isNsfw(): Boolean =
|
||||
this::class.annotations.any { it.toString() == "@eu.kanade.tachiyomi.annotations.Nsfw()" }
|
||||
|
||||
suspend fun installExtension(pkgName: String): Int {
|
||||
logger.debug("Installing $pkgName")
|
||||
val extensionRecord = extensionTableAsDataClass().first { it.pkgName == pkgName }
|
||||
@@ -189,7 +186,7 @@ object Extension {
|
||||
it[name] = httpSource.name
|
||||
it[lang] = httpSource.lang
|
||||
it[extension] = extensionId
|
||||
it[SourceTable.isNsfw] = isNsfw || extensionMainClassInstance.isNsfw()
|
||||
it[SourceTable.isNsfw] = isNsfw
|
||||
}
|
||||
logger.debug { "Installed source ${httpSource.name} (${httpSource.lang}) with id:${httpSource.id}" }
|
||||
}
|
||||
@@ -243,7 +240,7 @@ object Extension {
|
||||
PackageTools.jarLoaderMap.remove(jarPath)?.close()
|
||||
|
||||
// clear all loaded sources
|
||||
sources.forEach { GetHttpSource.invalidateSourceCache(it) }
|
||||
sources.forEach { GetCatalogueSource.invalidateSourceCache(it) }
|
||||
|
||||
File(jarPath).delete()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource
|
||||
import suwayomi.tachidesk.manga.impl.util.storage.SafePath
|
||||
import suwayomi.tachidesk.manga.model.table.ChapterTable
|
||||
import suwayomi.tachidesk.manga.model.table.MangaTable
|
||||
@@ -22,7 +23,7 @@ private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
fun getMangaDir(mangaId: Int): String {
|
||||
val mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
|
||||
val source = GetHttpSource.getHttpSource(mangaEntry[MangaTable.sourceReference])
|
||||
val source = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val sourceDir = source.toString()
|
||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
||||
@@ -46,7 +47,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 = GetCatalogueSource.getCatalogueSourceOrStub(mangaEntry[MangaTable.sourceReference])
|
||||
|
||||
val sourceDir = source.toString()
|
||||
val mangaDir = SafePath.buildValidFilename(mangaEntry[MangaTable.title])
|
||||
|
||||
+14
-11
@@ -1,4 +1,4 @@
|
||||
package suwayomi.tachidesk.manga.impl.util
|
||||
package suwayomi.tachidesk.manga.impl.util.source
|
||||
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
@@ -7,6 +7,7 @@ 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
|
||||
@@ -22,23 +23,21 @@ import suwayomi.tachidesk.manga.model.table.SourceTable
|
||||
import suwayomi.tachidesk.server.ApplicationDirs
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
object GetHttpSource {
|
||||
private val sourceCache = ConcurrentHashMap<Long, HttpSource>()
|
||||
object GetCatalogueSource {
|
||||
private val sourceCache = ConcurrentHashMap<Long, CatalogueSource>(
|
||||
mapOf(LocalSource.ID to LocalSource())
|
||||
)
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
fun getHttpSource(sourceId: Long): HttpSource {
|
||||
val cachedResult: HttpSource? = sourceCache[sourceId]
|
||||
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 +59,10 @@ object GetHttpSource {
|
||||
return sourceCache[sourceId]!!
|
||||
}
|
||||
|
||||
fun getCatalogueSourceOrStub(sourceId: Long): CatalogueSource {
|
||||
return getCatalogueSource(sourceId) ?: StubSource(sourceId)
|
||||
}
|
||||
|
||||
fun invalidateSourceCache(sourceId: Long) {
|
||||
sourceCache.remove(sourceId)
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package suwayomi.tachidesk.manga.impl.util.source
|
||||
|
||||
/*
|
||||
* 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.source.CatalogueSource
|
||||
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 rx.Observable
|
||||
|
||||
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<MangasPage> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun getFilterList(): FilterList {
|
||||
return FilterList()
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return Observable.error(getSourceNotInstalledException())
|
||||
}
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
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")
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import suwayomi.tachidesk.manga.impl.extension.Extension.installExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.uninstallExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.Extension.updateExtension
|
||||
import suwayomi.tachidesk.manga.impl.extension.ExtensionsList.getExtensionList
|
||||
import suwayomi.tachidesk.manga.impl.util.GetHttpSource.getHttpSource
|
||||
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
|
||||
import suwayomi.tachidesk.manga.impl.util.source.GetCatalogueSource.getCatalogueSource
|
||||
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
|
||||
import suwayomi.tachidesk.server.applicationSetup
|
||||
import java.io.File
|
||||
@@ -69,7 +69,7 @@ class TestExtensions {
|
||||
}
|
||||
}
|
||||
}
|
||||
sources = getSourceList().map { getHttpSource(it.id.toLong()) }
|
||||
sources = getSourceList().map { getCatalogueSource(it.id.toLong())!! as HttpSource }
|
||||
}
|
||||
setLoggingEnabled(true)
|
||||
File("tmp/TestDesk/sources.txt").writeText(sources.joinToString("\n") { "${it.name} - ${it.lang.uppercase()} - ${it.id}" })
|
||||
|
||||
Reference in New Issue
Block a user