add support for Extensions Lib 1.4 (#496)

* Support extensions lib 1.4

* Fix build

* Support UpdateStrategy

* Update extension lib min/max to match Tachiyomi

* Use HttpSource.getMangaUrl and add Chapter.realUrl
This commit is contained in:
Mitchell Syer
2023-02-11 21:19:32 -05:00
committed by GitHub
parent 406cb46170
commit 926a53a4b0
24 changed files with 303 additions and 146 deletions

View File

@@ -1,8 +1,8 @@
[versions] [versions]
kotlin = "1.7.20" kotlin = "1.8.0"
coroutines = "1.6.4" coroutines = "1.6.4"
serialization = "1.4.1" serialization = "1.4.1"
okhttp = "4.10.0" # Major version is locked by Tachiyomi extensions okhttp = "5.0.0-alpha.11" # Major version is locked by Tachiyomi extensions
javalin = "4.6.6" # Javalin 5.0.0+ requires Java 11 javalin = "4.6.6" # Javalin 5.0.0+ requires Java 11
jackson = "2.13.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency` jackson = "2.13.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency`
exposed = "0.40.1" exposed = "0.40.1"
@@ -36,7 +36,7 @@ kotlinlogging = "io.github.microutils:kotlin-logging:3.0.5"
okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-core = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" } okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
okio = "com.squareup.okio:okio:3.2.0" okio = "com.squareup.okio:okio:3.3.0"
# Javalin api # Javalin api
javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" } javalin-core = { module = "io.javalin:javalin", version.ref = "javalin" }

View File

@@ -16,6 +16,7 @@ package eu.kanade.tachiyomi
// import eu.kanade.tachiyomi.data.track.TrackManager // import eu.kanade.tachiyomi.data.track.TrackManager
// import eu.kanade.tachiyomi.extension.ExtensionManager // import eu.kanade.tachiyomi.extension.ExtensionManager
import android.app.Application import android.app.Application
import eu.kanade.tachiyomi.network.JavaScriptEngine
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import rx.Observable import rx.Observable
@@ -41,6 +42,8 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { NetworkHelper(app) } addSingletonFactory { NetworkHelper(app) }
addSingletonFactory { JavaScriptEngine(app) }
// addSingletonFactory { SourceManager(app).also { get<ExtensionManager>().init(it) } } // addSingletonFactory { SourceManager(app).also { get<ExtensionManager>().init(it) } }
// //
// addSingletonFactory { ExtensionManager(app) } // addSingletonFactory { ExtensionManager(app) }

View File

@@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.network
import android.content.Context
import app.cash.quickjs.QuickJs
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
/**
* Util for evaluating JavaScript in sources.
*/
class JavaScriptEngine(context: Context) {
/**
* Evaluate arbitrary JavaScript code and get the result as a primitive type
* (e.g., String, Int).
*
* @since extensions-lib 1.4
* @param script JavaScript to execute.
* @return Result of JavaScript code as a primitive type.
*/
@Suppress("UNUSED", "UNCHECKED_CAST")
suspend fun <T> evaluate(script: String): T = withContext(Dispatchers.IO) {
QuickJs.create().use {
it.evaluate(script) as T
}
}
}

View File

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network
import okhttp3.CacheControl import okhttp3.CacheControl
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.MINUTES
@@ -23,6 +24,21 @@ fun GET(
.build() .build()
} }
/**
* @since extensions-lib 1.4
*/
fun GET(
url: HttpUrl,
headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL
): Request {
return Request.Builder()
.url(url)
.headers(headers)
.cacheControl(cache)
.build()
}
fun POST( fun POST(
url: String, url: String,
headers: Headers = DEFAULT_HEADERS, headers: Headers = DEFAULT_HEADERS,

View File

@@ -20,6 +20,8 @@ interface SManga : Serializable {
var thumbnail_url: String? var thumbnail_url: String?
var update_strategy: UpdateStrategy
var initialized: Boolean var initialized: Boolean
fun copyFrom(other: SManga) { fun copyFrom(other: SManga) {

View File

@@ -18,5 +18,7 @@ class SMangaImpl : SManga {
override var thumbnail_url: String? = null override var thumbnail_url: String? = null
override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE
override var initialized: Boolean = false override var initialized: Boolean = false
} }

View File

@@ -0,0 +1,6 @@
package eu.kanade.tachiyomi.source.model
enum class UpdateStrategy {
ALWAYS_UPDATE,
ONLY_FETCH_ONCE
}

View File

@@ -357,6 +357,28 @@ abstract class HttpSource : CatalogueSource {
} }
} }
/**
* Returns the url of the provided manga
*
* @since extensions-lib 1.4
* @param manga the manga
* @return url of the manga
*/
open fun getMangaUrl(manga: SManga): String {
return mangaDetailsRequest(manga).url.toString()
}
/**
* Returns the url of the provided chapter
*
* @since extensions-lib 1.4
* @param chapter the chapter
* @return url of the chapter
*/
open fun getChapterUrl(chapter: SChapter): String {
return pageListRequest(chapter).url.toString()
}
/** /**
* Called before inserting a new chapter into database. Use it if you need to override chapter * Called before inserting a new chapter into database. Use it if you need to override chapter
* fields, like the title or the chapter number. Do not change anything to [manga]. * fields, like the title or the chapter number. Do not change anything to [manga].
@@ -364,8 +386,7 @@ abstract class HttpSource : CatalogueSource {
* @param chapter the chapter to be added. * @param chapter the chapter to be added.
* @param manga the manga of the chapter. * @param manga the manga of the chapter.
*/ */
open fun prepareNewChapter(chapter: SChapter, manga: SManga) { open fun prepareNewChapter(chapter: SChapter, manga: SManga) {}
}
/** /**
* Returns the list of filters for the source. * Returns the list of filters for the source.

View File

@@ -1,5 +1,6 @@
package suwayomi.tachidesk.manga.controller package suwayomi.tachidesk.manga.controller
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import io.javalin.http.HttpCode import io.javalin.http.HttpCode
import io.javalin.websocket.WsConfig import io.javalin.websocket.WsConfig
import mu.KotlinLogging import mu.KotlinLogging
@@ -96,6 +97,7 @@ object UpdateController {
.flatMap { CategoryManga.getCategoryMangaList(it.id) } .flatMap { CategoryManga.getCategoryMangaList(it.id) }
.distinctBy { it.id } .distinctBy { it.id }
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, MangaDataClass::title)) .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, MangaDataClass::title))
.filter { it.updateStrategy == UpdateStrategy.ALWAYS_UPDATE }
.forEach { manga -> .forEach { manga ->
updater.addMangaToQueue(manga) updater.addMangaToQueue(manga)
} }

View File

@@ -12,11 +12,16 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import eu.kanade.tachiyomi.util.chapter.ChapterRecognition
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.sql.* import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.SortOrder
import org.jetbrains.exposed.sql.SortOrder.ASC import org.jetbrains.exposed.sql.SortOrder.ASC
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.SqlExpressionBuilder.inList import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.deleteWhere
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction 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.Manga.getManga
import suwayomi.tachidesk.manga.impl.util.getChapterDir import suwayomi.tachidesk.manga.impl.util.getChapterDir
import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle import suwayomi.tachidesk.manga.impl.util.lang.awaitSingle
@@ -27,6 +32,7 @@ import suwayomi.tachidesk.manga.model.dataclass.PaginatedList
import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom import suwayomi.tachidesk.manga.model.dataclass.paginatedFrom
import suwayomi.tachidesk.manga.model.table.ChapterMetaTable import suwayomi.tachidesk.manga.model.table.ChapterMetaTable
import suwayomi.tachidesk.manga.model.table.ChapterTable import suwayomi.tachidesk.manga.model.table.ChapterTable
import suwayomi.tachidesk.manga.model.table.ChapterTable.scanlator
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
import suwayomi.tachidesk.manga.model.table.PageTable import suwayomi.tachidesk.manga.model.table.PageTable
import suwayomi.tachidesk.manga.model.table.toDataClass import suwayomi.tachidesk.manga.model.table.toDataClass
@@ -85,6 +91,10 @@ object Chapter {
it[sourceOrder] = index + 1 it[sourceOrder] = index + 1
it[fetchedAt] = now++ it[fetchedAt] = now++
it[ChapterTable.manga] = mangaId it[ChapterTable.manga] = mangaId
it[realUrl] = runCatching {
(source as? HttpSource)?.getChapterUrl(fetchedChapter)
}.getOrNull()
} }
} else { } else {
ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) { ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) {
@@ -95,6 +105,10 @@ object Chapter {
it[sourceOrder] = index + 1 it[sourceOrder] = index + 1
it[ChapterTable.manga] = mangaId it[ChapterTable.manga] = mangaId
it[realUrl] = runCatching {
(source as? HttpSource)?.getChapterUrl(fetchedChapter)
}.getOrNull()
} }
} }
} }
@@ -138,26 +152,27 @@ object Chapter {
val dbChapter = dbChapterMap.getValue(it.url) val dbChapter = dbChapterMap.getValue(it.url)
ChapterDataClass( ChapterDataClass(
dbChapter[ChapterTable.id].value, id = dbChapter[ChapterTable.id].value,
it.url, url = it.url,
it.name, name = it.name,
it.date_upload, uploadDate = it.date_upload,
it.chapter_number, chapterNumber = it.chapter_number,
it.scanlator, scanlator = it.scanlator,
mangaId, mangaId = mangaId,
dbChapter[ChapterTable.isRead], read = dbChapter[ChapterTable.isRead],
dbChapter[ChapterTable.isBookmarked], bookmarked = dbChapter[ChapterTable.isBookmarked],
dbChapter[ChapterTable.lastPageRead], lastPageRead = dbChapter[ChapterTable.lastPageRead],
dbChapter[ChapterTable.lastReadAt], lastReadAt = dbChapter[ChapterTable.lastReadAt],
chapterCount - index, index = chapterCount - index,
dbChapter[ChapterTable.fetchedAt], fetchedAt = dbChapter[ChapterTable.fetchedAt],
dbChapter[ChapterTable.isDownloaded], realUrl = dbChapter[ChapterTable.realUrl],
downloaded = dbChapter[ChapterTable.isDownloaded],
dbChapter[ChapterTable.pageCount], pageCount = dbChapter[ChapterTable.pageCount],
chapterList.size, chapterCount = chapterList.size,
meta = chapterMetas.getValue(dbChapter[ChapterTable.id]) meta = chapterMetas.getValue(dbChapter[ChapterTable.id])
) )
} }

View File

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.local.LocalSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.SortOrder
@@ -94,39 +95,42 @@ object Manga {
} }
it[MangaTable.realUrl] = runCatching { it[MangaTable.realUrl] = runCatching {
(source as? HttpSource)?.mangaDetailsRequest(sManga)?.url?.toString() (source as? HttpSource)?.getMangaUrl(sManga)
}.getOrNull() }.getOrNull()
it[MangaTable.lastFetchedAt] = Instant.now().epochSecond it[MangaTable.lastFetchedAt] = Instant.now().epochSecond
it[MangaTable.updateStrategy] = sManga.update_strategy.name
} }
} }
mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() }
MangaDataClass( MangaDataClass(
mangaId, id = mangaId,
mangaEntry[MangaTable.sourceReference].toString(), sourceId = mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[MangaTable.url], url = mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title], title = mangaEntry[MangaTable.title],
proxyThumbnailUrl(mangaId), thumbnailUrl = proxyThumbnailUrl(mangaId),
mangaEntry[MangaTable.thumbnailUrlLastFetched], thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
true, initialized = true,
sManga.artist, artist = sManga.artist,
sManga.author, author = sManga.author,
sManga.description, description = sManga.description,
sManga.genre.toGenreList(), genre = sManga.genre.toGenreList(),
MangaStatus.valueOf(sManga.status).name, status = MangaStatus.valueOf(sManga.status).name,
mangaEntry[MangaTable.inLibrary], inLibrary = mangaEntry[MangaTable.inLibrary],
mangaEntry[MangaTable.inLibraryAt], inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
getSource(mangaEntry[MangaTable.sourceReference]), source = getSource(mangaEntry[MangaTable.sourceReference]),
getMangaMetaMap(mangaId), meta = getMangaMetaMap(mangaId),
mangaEntry[MangaTable.realUrl], realUrl = mangaEntry[MangaTable.realUrl],
mangaEntry[MangaTable.lastFetchedAt], lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
mangaEntry[MangaTable.chaptersLastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
true updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
freshData = true
) )
} }
} }
@@ -166,29 +170,30 @@ object Manga {
} }
private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass( private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass(
mangaId, id = mangaId,
mangaEntry[MangaTable.sourceReference].toString(), sourceId = mangaEntry[MangaTable.sourceReference].toString(),
mangaEntry[MangaTable.url], url = mangaEntry[MangaTable.url],
mangaEntry[MangaTable.title], title = mangaEntry[MangaTable.title],
proxyThumbnailUrl(mangaId), thumbnailUrl = proxyThumbnailUrl(mangaId),
mangaEntry[MangaTable.thumbnailUrlLastFetched], thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
true, initialized = true,
mangaEntry[MangaTable.artist], artist = mangaEntry[MangaTable.artist],
mangaEntry[MangaTable.author], author = mangaEntry[MangaTable.author],
mangaEntry[MangaTable.description], description = mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre].toGenreList(), genre = mangaEntry[MangaTable.genre].toGenreList(),
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary], inLibrary = mangaEntry[MangaTable.inLibrary],
mangaEntry[MangaTable.inLibraryAt], inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
getSource(mangaEntry[MangaTable.sourceReference]), source = getSource(mangaEntry[MangaTable.sourceReference]),
getMangaMetaMap(mangaId), meta = getMangaMetaMap(mangaId),
mangaEntry[MangaTable.realUrl], realUrl = mangaEntry[MangaTable.realUrl],
mangaEntry[MangaTable.lastFetchedAt], lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
mangaEntry[MangaTable.chaptersLastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
false updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
freshData = false
) )
fun getMangaMetaMap(mangaId: Int): Map<String, String> { fun getMangaMetaMap(mangaId: Int): Map<String, String> {

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.impl
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.insertAndGetId
import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.select
@@ -61,6 +62,7 @@ object MangaList {
it[genre] = manga.genre it[genre] = manga.genre
it[status] = manga.status it[status] = manga.status
it[thumbnail_url] = manga.thumbnail_url it[thumbnail_url] = manga.thumbnail_url
it[updateStrategy] = manga.update_strategy.name
it[sourceReference] = sourceId it[sourceReference] = sourceId
}.value }.value
@@ -70,53 +72,55 @@ object MangaList {
}.first() }.first()
MangaDataClass( MangaDataClass(
mangaId, id = mangaId,
sourceId.toString(), sourceId = sourceId.toString(),
manga.url, url = manga.url,
manga.title, title = manga.title,
proxyThumbnailUrl(mangaId), thumbnailUrl = proxyThumbnailUrl(mangaId),
mangaEntry[MangaTable.thumbnailUrlLastFetched], thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
manga.initialized, initialized = manga.initialized,
manga.artist, artist = manga.artist,
manga.author, author = manga.author,
manga.description, description = manga.description,
manga.genre.toGenreList(), genre = manga.genre.toGenreList(),
MangaStatus.valueOf(manga.status).name, status = MangaStatus.valueOf(manga.status).name,
false, // It's a new manga entry inLibrary = false, // It's a new manga entry
0, inLibraryAt = 0,
meta = getMangaMetaMap(mangaId), meta = getMangaMetaMap(mangaId),
realUrl = mangaEntry[MangaTable.realUrl], realUrl = mangaEntry[MangaTable.realUrl],
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
freshData = true freshData = true
) )
} else { } else {
val mangaId = mangaEntry[MangaTable.id].value val mangaId = mangaEntry[MangaTable.id].value
MangaDataClass( MangaDataClass(
mangaId, id = mangaId,
sourceId.toString(), sourceId = sourceId.toString(),
manga.url, url = manga.url,
manga.title, title = manga.title,
proxyThumbnailUrl(mangaId), thumbnailUrl = proxyThumbnailUrl(mangaId),
mangaEntry[MangaTable.thumbnailUrlLastFetched], thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched],
true, initialized = true,
mangaEntry[MangaTable.artist], artist = mangaEntry[MangaTable.artist],
mangaEntry[MangaTable.author], author = mangaEntry[MangaTable.author],
mangaEntry[MangaTable.description], description = mangaEntry[MangaTable.description],
mangaEntry[MangaTable.genre].toGenreList(), genre = mangaEntry[MangaTable.genre].toGenreList(),
MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name,
mangaEntry[MangaTable.inLibrary], inLibrary = mangaEntry[MangaTable.inLibrary],
mangaEntry[MangaTable.inLibraryAt], inLibraryAt = mangaEntry[MangaTable.inLibraryAt],
meta = getMangaMetaMap(mangaId), meta = getMangaMetaMap(mangaId),
realUrl = mangaEntry[MangaTable.realUrl], realUrl = mangaEntry[MangaTable.realUrl],
lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt],
updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]),
freshData = false freshData = false
) )
} }

View File

@@ -1,8 +0,0 @@
package suwayomi.tachidesk.manga.impl.backup.models
class LibraryManga : MangaImpl() {
var unread: Int = 0
var category: Int = 0
}

View File

@@ -1,5 +1,6 @@
package suwayomi.tachidesk.manga.impl.backup.models package suwayomi.tachidesk.manga.impl.backup.models
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import suwayomi.tachidesk.manga.model.table.MangaTable import suwayomi.tachidesk.manga.model.table.MangaTable
@@ -25,6 +26,8 @@ open class MangaImpl : Manga {
override var thumbnail_url: String? = null override var thumbnail_url: String? = null
override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE
override var favorite: Boolean = false override var favorite: Boolean = false
override var last_update: Long = 0 override var last_update: Long = 0

View File

@@ -7,6 +7,7 @@ package suwayomi.tachidesk.manga.impl.backup.proto
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import okio.buffer import okio.buffer
import okio.gzip import okio.gzip
import okio.sink import okio.sink
@@ -59,17 +60,18 @@ object ProtoBackupExport : ProtoBackupBase() {
private fun backupManga(databaseManga: Query, flags: BackupFlags): List<BackupManga> { private fun backupManga(databaseManga: Query, flags: BackupFlags): List<BackupManga> {
return databaseManga.map { mangaRow -> return databaseManga.map { mangaRow ->
val backupManga = BackupManga( val backupManga = BackupManga(
mangaRow[MangaTable.sourceReference], source = mangaRow[MangaTable.sourceReference],
mangaRow[MangaTable.url], url = mangaRow[MangaTable.url],
mangaRow[MangaTable.title], title = mangaRow[MangaTable.title],
mangaRow[MangaTable.artist], artist = mangaRow[MangaTable.artist],
mangaRow[MangaTable.author], author = mangaRow[MangaTable.author],
mangaRow[MangaTable.description], description = mangaRow[MangaTable.description],
mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(), genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(),
MangaStatus.valueOf(mangaRow[MangaTable.status]).value, status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value,
mangaRow[MangaTable.thumbnail_url], thumbnailUrl = mangaRow[MangaTable.thumbnail_url],
TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]), dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]),
0 // not supported in Tachidesk viewer = 0, // not supported in Tachidesk
updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy])
) )
val mangaId = mangaRow[MangaTable.id].value val mangaId = mangaRow[MangaTable.id].value

View File

@@ -145,6 +145,7 @@ object ProtoBackupImport : ProtoBackupBase() {
it[genre] = manga.genre it[genre] = manga.genre
it[status] = manga.status it[status] = manga.status
it[thumbnail_url] = manga.thumbnail_url it[thumbnail_url] = manga.thumbnail_url
it[updateStrategy] = manga.update_strategy.name
it[sourceReference] = manga.source it[sourceReference] = manga.source
@@ -193,6 +194,7 @@ object ProtoBackupImport : ProtoBackupBase() {
it[genre] = manga.genre ?: dbManga[genre] it[genre] = manga.genre ?: dbManga[genre]
it[status] = manga.status it[status] = manga.status
it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url] it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url]
it[updateStrategy] = manga.update_strategy.name
it[initialized] = dbManga[initialized] || manga.description != null it[initialized] = dbManga[initialized] || manga.description != null

View File

@@ -1,5 +1,6 @@
package suwayomi.tachidesk.manga.impl.backup.proto.models package suwayomi.tachidesk.manga.impl.backup.proto.models
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.protobuf.ProtoNumber import kotlinx.serialization.protobuf.ProtoNumber
import suwayomi.tachidesk.manga.impl.backup.models.ChapterImpl import suwayomi.tachidesk.manga.impl.backup.models.ChapterImpl
@@ -35,7 +36,8 @@ data class BackupManga(
@ProtoNumber(101) var chapterFlags: Int = 0, @ProtoNumber(101) var chapterFlags: Int = 0,
@ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(), @ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(),
@ProtoNumber(103) var viewer_flags: Int? = null, @ProtoNumber(103) var viewer_flags: Int? = null,
@ProtoNumber(104) var history: List<BackupHistory> = emptyList() @ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
@ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE
) { ) {
fun getMangaImpl(): MangaImpl { fun getMangaImpl(): MangaImpl {
return MangaImpl().apply { return MangaImpl().apply {
@@ -52,6 +54,7 @@ data class BackupManga(
date_added = this@BackupManga.dateAdded date_added = this@BackupManga.dateAdded
viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer
chapter_flags = this@BackupManga.chapterFlags chapter_flags = this@BackupManga.chapterFlags
update_strategy = this@BackupManga.updateStrategy
} }
} }

View File

@@ -40,8 +40,8 @@ object PackageTools {
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
const val METADATA_NSFW = "tachiyomi.extension.nsfw" const val METADATA_NSFW = "tachiyomi.extension.nsfw"
const val LIB_VERSION_MIN = 1.2 const val LIB_VERSION_MIN = 1.3
const val LIB_VERSION_MAX = 1.3 const val LIB_VERSION_MAX = 1.4
private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key
private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key

View File

@@ -35,6 +35,9 @@ data class ChapterDataClass(
/** the date we fist saw this chapter*/ /** the date we fist saw this chapter*/
val fetchedAt: Long, val fetchedAt: Long,
/** the website url of this chapter*/
val realUrl: String? = null,
/** is chapter downloaded */ /** is chapter downloaded */
val downloaded: Boolean, val downloaded: Boolean,

View File

@@ -7,6 +7,7 @@ package suwayomi.tachidesk.manga.model.dataclass
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import suwayomi.tachidesk.manga.impl.util.lang.trimAll import suwayomi.tachidesk.manga.impl.util.lang.trimAll
import suwayomi.tachidesk.manga.model.table.MangaStatus import suwayomi.tachidesk.manga.model.table.MangaStatus
import java.time.Instant import java.time.Instant
@@ -38,6 +39,8 @@ data class MangaDataClass(
var lastFetchedAt: Long? = 0, var lastFetchedAt: Long? = 0,
var chaptersLastFetchedAt: Long? = 0, var chaptersLastFetchedAt: Long? = 0,
var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
val freshData: Boolean = false, val freshData: Boolean = false,
var unreadCount: Long? = null, var unreadCount: Long? = null,
var downloadCount: Long? = null, var downloadCount: Long? = null,

View File

@@ -13,6 +13,7 @@ import org.jetbrains.exposed.sql.select
import org.jetbrains.exposed.sql.transactions.transaction import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.manga.impl.Chapter.getChapterMetaMap import suwayomi.tachidesk.manga.impl.Chapter.getChapterMetaMap
import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass
import suwayomi.tachidesk.manga.model.table.MangaTable.nullable
object ChapterTable : IntIdTable() { object ChapterTable : IntIdTable() {
val url = varchar("url", 2048) val url = varchar("url", 2048)
@@ -29,6 +30,9 @@ object ChapterTable : IntIdTable() {
val sourceOrder = integer("source_order") val sourceOrder = integer("source_order")
/** the real url of a chapter used for the "open in WebView" feature */
val realUrl = varchar("real_url", 2048).nullable()
val isDownloaded = bool("is_downloaded").default(false) val isDownloaded = bool("is_downloaded").default(false)
val pageCount = integer("page_count").default(-1) val pageCount = integer("page_count").default(-1)
@@ -38,21 +42,22 @@ object ChapterTable : IntIdTable() {
fun ChapterTable.toDataClass(chapterEntry: ResultRow) = fun ChapterTable.toDataClass(chapterEntry: ResultRow) =
ChapterDataClass( ChapterDataClass(
chapterEntry[id].value, id = chapterEntry[id].value,
chapterEntry[url], url = chapterEntry[url],
chapterEntry[name], name = chapterEntry[name],
chapterEntry[date_upload], uploadDate = chapterEntry[date_upload],
chapterEntry[chapter_number], chapterNumber = chapterEntry[chapter_number],
chapterEntry[scanlator], scanlator = chapterEntry[scanlator],
chapterEntry[manga].value, mangaId = chapterEntry[manga].value,
chapterEntry[isRead], read = chapterEntry[isRead],
chapterEntry[isBookmarked], bookmarked = chapterEntry[isBookmarked],
chapterEntry[lastPageRead], lastPageRead = chapterEntry[lastPageRead],
chapterEntry[lastReadAt], lastReadAt = chapterEntry[lastReadAt],
chapterEntry[sourceOrder], index = chapterEntry[sourceOrder],
chapterEntry[fetchedAt], fetchedAt = chapterEntry[fetchedAt],
chapterEntry[isDownloaded], realUrl = chapterEntry[realUrl],
chapterEntry[pageCount], downloaded = chapterEntry[isDownloaded],
transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() }, pageCount = chapterEntry[pageCount],
getChapterMetaMap(chapterEntry[id]) chapterCount = transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() },
meta = getChapterMetaMap(chapterEntry[id])
) )

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.model.table
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.ResultRow
import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap
@@ -42,31 +43,34 @@ object MangaTable : IntIdTable() {
val lastFetchedAt = long("last_fetched_at").default(0) val lastFetchedAt = long("last_fetched_at").default(0)
val chaptersLastFetchedAt = long("chapters_last_fetched_at").default(0) val chaptersLastFetchedAt = long("chapters_last_fetched_at").default(0)
val updateStrategy = varchar("update_strategy", 256).default(UpdateStrategy.ALWAYS_UPDATE.name)
} }
fun MangaTable.toDataClass(mangaEntry: ResultRow) = fun MangaTable.toDataClass(mangaEntry: ResultRow) =
MangaDataClass( MangaDataClass(
mangaEntry[this.id].value, id = mangaEntry[this.id].value,
mangaEntry[sourceReference].toString(), sourceId = mangaEntry[sourceReference].toString(),
mangaEntry[url], url = mangaEntry[url],
mangaEntry[title], title = mangaEntry[title],
proxyThumbnailUrl(mangaEntry[this.id].value), thumbnailUrl = proxyThumbnailUrl(mangaEntry[this.id].value),
mangaEntry[MangaTable.thumbnailUrlLastFetched], thumbnailUrlLastFetched = mangaEntry[thumbnailUrlLastFetched],
mangaEntry[initialized], initialized = mangaEntry[initialized],
mangaEntry[artist], artist = mangaEntry[artist],
mangaEntry[author], author = mangaEntry[author],
mangaEntry[description], description = mangaEntry[description],
mangaEntry[genre].toGenreList(), genre = mangaEntry[genre].toGenreList(),
Companion.valueOf(mangaEntry[status]).name, status = Companion.valueOf(mangaEntry[status]).name,
mangaEntry[inLibrary], inLibrary = mangaEntry[inLibrary],
mangaEntry[inLibraryAt], inLibraryAt = mangaEntry[inLibraryAt],
meta = getMangaMetaMap(mangaEntry[id].value), meta = getMangaMetaMap(mangaEntry[id].value),
realUrl = mangaEntry[realUrl], realUrl = mangaEntry[realUrl],
lastFetchedAt = mangaEntry[lastFetchedAt], lastFetchedAt = mangaEntry[lastFetchedAt],
chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt] chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt],
updateStrategy = UpdateStrategy.valueOf(mangaEntry[updateStrategy])
) )
enum class MangaStatus(val value: Int) { enum class MangaStatus(val value: Int) {

View File

@@ -0,0 +1,19 @@
package suwayomi.tachidesk.server.database.migration
/*
* 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 de.neonew.exposed.migrations.helpers.AddColumnMigration
import eu.kanade.tachiyomi.source.model.UpdateStrategy
@Suppress("ClassName", "unused")
class M0024_MangaUpdateStrategy : AddColumnMigration(
"Manga",
"update_strategy",
"VARCHAR(256)",
"'${UpdateStrategy.ALWAYS_UPDATE.name}'"
)

View File

@@ -0,0 +1,18 @@
package suwayomi.tachidesk.server.database.migration
/*
* 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 de.neonew.exposed.migrations.helpers.AddColumnMigration
@Suppress("ClassName", "unused")
class M0025_ChapterRealUrl : AddColumnMigration(
"Chapter",
"real_url",
"VARCHAR(2048)",
"NULL"
)