diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 425b075fc..4b2974d22 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,8 +1,8 @@ [versions] -kotlin = "1.7.20" +kotlin = "1.8.0" coroutines = "1.6.4" 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 jackson = "2.13.3" # jackson version locked by javalin, ref: `io.javalin.core.util.OptionalDependency` 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-logging = { module = "com.squareup.okhttp3:logging-interceptor", 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-core = { module = "io.javalin:javalin", version.ref = "javalin" } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt index c46b8d1c8..b8620cde5 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/AppModule.kt @@ -16,6 +16,7 @@ package eu.kanade.tachiyomi // import eu.kanade.tachiyomi.data.track.TrackManager // import eu.kanade.tachiyomi.extension.ExtensionManager import android.app.Application +import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import kotlinx.serialization.json.Json import rx.Observable @@ -41,6 +42,8 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { NetworkHelper(app) } + addSingletonFactory { JavaScriptEngine(app) } + // addSingletonFactory { SourceManager(app).also { get().init(it) } } // // addSingletonFactory { ExtensionManager(app) } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt new file mode 100644 index 000000000..d63d24968 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/JavaScriptEngine.kt @@ -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 evaluate(script: String): T = withContext(Dispatchers.IO) { + QuickJs.create().use { + it.evaluate(script) as T + } + } +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt index 3d3b88b54..b8bfa73e5 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/Requests.kt @@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.network import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.Headers +import okhttp3.HttpUrl import okhttp3.Request import okhttp3.RequestBody import java.util.concurrent.TimeUnit.MINUTES @@ -23,6 +24,21 @@ fun GET( .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( url: String, headers: Headers = DEFAULT_HEADERS, diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt index 28fd48a8c..22e9962f8 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -20,6 +20,8 @@ interface SManga : Serializable { var thumbnail_url: String? + var update_strategy: UpdateStrategy + var initialized: Boolean fun copyFrom(other: SManga) { diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt index c944474a1..91a7711cc 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -18,5 +18,7 @@ class SMangaImpl : SManga { override var thumbnail_url: String? = null + override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE + override var initialized: Boolean = false } diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt new file mode 100644 index 000000000..aa1d70181 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt @@ -0,0 +1,6 @@ +package eu.kanade.tachiyomi.source.model + +enum class UpdateStrategy { + ALWAYS_UPDATE, + ONLY_FETCH_ONCE +} diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index 7ad2ec8b5..69d88d864 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -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 * 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 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. diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt index c6f09bb73..a86da540c 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/controller/UpdateController.kt @@ -1,5 +1,6 @@ package suwayomi.tachidesk.manga.controller +import eu.kanade.tachiyomi.source.model.UpdateStrategy import io.javalin.http.HttpCode import io.javalin.websocket.WsConfig import mu.KotlinLogging @@ -96,6 +97,7 @@ object UpdateController { .flatMap { CategoryManga.getCategoryMangaList(it.id) } .distinctBy { it.id } .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, MangaDataClass::title)) + .filter { it.updateStrategy == UpdateStrategy.ALWAYS_UPDATE } .forEach { manga -> updater.addMangaToQueue(manga) } 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 fa8739293..124170746 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Chapter.kt @@ -12,11 +12,16 @@ import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.util.chapter.ChapterRecognition import kotlinx.serialization.Serializable 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.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.update import suwayomi.tachidesk.manga.impl.Manga.getManga import suwayomi.tachidesk.manga.impl.util.getChapterDir 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.table.ChapterMetaTable 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.PageTable import suwayomi.tachidesk.manga.model.table.toDataClass @@ -85,6 +91,10 @@ object Chapter { it[sourceOrder] = index + 1 it[fetchedAt] = now++ it[ChapterTable.manga] = mangaId + + it[realUrl] = runCatching { + (source as? HttpSource)?.getChapterUrl(fetchedChapter) + }.getOrNull() } } else { ChapterTable.update({ ChapterTable.url eq fetchedChapter.url }) { @@ -95,6 +105,10 @@ object Chapter { it[sourceOrder] = index + 1 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) ChapterDataClass( - dbChapter[ChapterTable.id].value, - it.url, - it.name, - it.date_upload, - it.chapter_number, - it.scanlator, - mangaId, - - dbChapter[ChapterTable.isRead], - dbChapter[ChapterTable.isBookmarked], - dbChapter[ChapterTable.lastPageRead], - dbChapter[ChapterTable.lastReadAt], - - chapterCount - index, - dbChapter[ChapterTable.fetchedAt], - dbChapter[ChapterTable.isDownloaded], - - dbChapter[ChapterTable.pageCount], - - chapterList.size, + id = dbChapter[ChapterTable.id].value, + url = it.url, + name = it.name, + uploadDate = it.date_upload, + chapterNumber = it.chapter_number, + scanlator = it.scanlator, + mangaId = mangaId, + + read = dbChapter[ChapterTable.isRead], + bookmarked = dbChapter[ChapterTable.isBookmarked], + lastPageRead = dbChapter[ChapterTable.lastPageRead], + lastReadAt = dbChapter[ChapterTable.lastReadAt], + + index = chapterCount - index, + fetchedAt = dbChapter[ChapterTable.fetchedAt], + realUrl = dbChapter[ChapterTable.realUrl], + downloaded = dbChapter[ChapterTable.isDownloaded], + + pageCount = dbChapter[ChapterTable.pageCount], + + chapterCount = chapterList.size, meta = chapterMetas.getValue(dbChapter[ChapterTable.id]) ) } 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 fd8b2f345..2f7e4f242 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/Manga.kt @@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.local.LocalSource import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy import eu.kanade.tachiyomi.source.online.HttpSource import org.jetbrains.exposed.sql.ResultRow import org.jetbrains.exposed.sql.SortOrder @@ -94,39 +95,42 @@ object Manga { } it[MangaTable.realUrl] = runCatching { - (source as? HttpSource)?.mangaDetailsRequest(sManga)?.url?.toString() + (source as? HttpSource)?.getMangaUrl(sManga) }.getOrNull() it[MangaTable.lastFetchedAt] = Instant.now().epochSecond + + it[MangaTable.updateStrategy] = sManga.update_strategy.name } } mangaEntry = transaction { MangaTable.select { MangaTable.id eq mangaId }.first() } MangaDataClass( - mangaId, - mangaEntry[MangaTable.sourceReference].toString(), - - mangaEntry[MangaTable.url], - mangaEntry[MangaTable.title], - proxyThumbnailUrl(mangaId), - mangaEntry[MangaTable.thumbnailUrlLastFetched], - - true, - - sManga.artist, - sManga.author, - sManga.description, - sManga.genre.toGenreList(), - MangaStatus.valueOf(sManga.status).name, - mangaEntry[MangaTable.inLibrary], - mangaEntry[MangaTable.inLibraryAt], - getSource(mangaEntry[MangaTable.sourceReference]), - getMangaMetaMap(mangaId), - mangaEntry[MangaTable.realUrl], - mangaEntry[MangaTable.lastFetchedAt], - mangaEntry[MangaTable.chaptersLastFetchedAt], - true + id = mangaId, + sourceId = mangaEntry[MangaTable.sourceReference].toString(), + + url = mangaEntry[MangaTable.url], + title = mangaEntry[MangaTable.title], + thumbnailUrl = proxyThumbnailUrl(mangaId), + thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], + + initialized = true, + + artist = sManga.artist, + author = sManga.author, + description = sManga.description, + genre = sManga.genre.toGenreList(), + status = MangaStatus.valueOf(sManga.status).name, + inLibrary = mangaEntry[MangaTable.inLibrary], + inLibraryAt = mangaEntry[MangaTable.inLibraryAt], + source = getSource(mangaEntry[MangaTable.sourceReference]), + meta = getMangaMetaMap(mangaId), + realUrl = mangaEntry[MangaTable.realUrl], + lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], + chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), + freshData = true ) } } @@ -166,29 +170,30 @@ object Manga { } private fun getMangaDataClass(mangaId: Int, mangaEntry: ResultRow) = MangaDataClass( - mangaId, - mangaEntry[MangaTable.sourceReference].toString(), - - mangaEntry[MangaTable.url], - mangaEntry[MangaTable.title], - proxyThumbnailUrl(mangaId), - mangaEntry[MangaTable.thumbnailUrlLastFetched], - - true, - - mangaEntry[MangaTable.artist], - mangaEntry[MangaTable.author], - mangaEntry[MangaTable.description], - mangaEntry[MangaTable.genre].toGenreList(), - MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary], - mangaEntry[MangaTable.inLibraryAt], - getSource(mangaEntry[MangaTable.sourceReference]), - getMangaMetaMap(mangaId), - mangaEntry[MangaTable.realUrl], - mangaEntry[MangaTable.lastFetchedAt], - mangaEntry[MangaTable.chaptersLastFetchedAt], - false + id = mangaId, + sourceId = mangaEntry[MangaTable.sourceReference].toString(), + + url = mangaEntry[MangaTable.url], + title = mangaEntry[MangaTable.title], + thumbnailUrl = proxyThumbnailUrl(mangaId), + thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], + + initialized = true, + + artist = mangaEntry[MangaTable.artist], + author = mangaEntry[MangaTable.author], + description = mangaEntry[MangaTable.description], + genre = mangaEntry[MangaTable.genre].toGenreList(), + status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + inLibrary = mangaEntry[MangaTable.inLibrary], + inLibraryAt = mangaEntry[MangaTable.inLibraryAt], + source = getSource(mangaEntry[MangaTable.sourceReference]), + meta = getMangaMetaMap(mangaId), + realUrl = mangaEntry[MangaTable.realUrl], + lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], + chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), + freshData = false ) fun getMangaMetaMap(mangaId: Int): Map { 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 895301527..3a7207252 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/MangaList.kt @@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.impl * 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.UpdateStrategy import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.insertAndGetId import org.jetbrains.exposed.sql.select @@ -61,6 +62,7 @@ object MangaList { it[genre] = manga.genre it[status] = manga.status it[thumbnail_url] = manga.thumbnail_url + it[updateStrategy] = manga.update_strategy.name it[sourceReference] = sourceId }.value @@ -70,53 +72,55 @@ object MangaList { }.first() MangaDataClass( - mangaId, - sourceId.toString(), + id = mangaId, + sourceId = sourceId.toString(), - manga.url, - manga.title, - proxyThumbnailUrl(mangaId), - mangaEntry[MangaTable.thumbnailUrlLastFetched], + url = manga.url, + title = manga.title, + thumbnailUrl = proxyThumbnailUrl(mangaId), + thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], - manga.initialized, + initialized = manga.initialized, - manga.artist, - manga.author, - manga.description, - manga.genre.toGenreList(), - MangaStatus.valueOf(manga.status).name, - false, // It's a new manga entry - 0, + artist = manga.artist, + author = manga.author, + description = manga.description, + genre = manga.genre.toGenreList(), + status = MangaStatus.valueOf(manga.status).name, + inLibrary = false, // It's a new manga entry + inLibraryAt = 0, meta = getMangaMetaMap(mangaId), realUrl = mangaEntry[MangaTable.realUrl], lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), freshData = true ) } else { val mangaId = mangaEntry[MangaTable.id].value MangaDataClass( - mangaId, - sourceId.toString(), + id = mangaId, + sourceId = sourceId.toString(), - manga.url, - manga.title, - proxyThumbnailUrl(mangaId), - mangaEntry[MangaTable.thumbnailUrlLastFetched], + url = manga.url, + title = manga.title, + thumbnailUrl = proxyThumbnailUrl(mangaId), + thumbnailUrlLastFetched = mangaEntry[MangaTable.thumbnailUrlLastFetched], - true, + initialized = true, - mangaEntry[MangaTable.artist], - mangaEntry[MangaTable.author], - mangaEntry[MangaTable.description], - mangaEntry[MangaTable.genre].toGenreList(), - MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, - mangaEntry[MangaTable.inLibrary], - mangaEntry[MangaTable.inLibraryAt], + artist = mangaEntry[MangaTable.artist], + author = mangaEntry[MangaTable.author], + description = mangaEntry[MangaTable.description], + genre = mangaEntry[MangaTable.genre].toGenreList(), + status = MangaStatus.valueOf(mangaEntry[MangaTable.status]).name, + inLibrary = mangaEntry[MangaTable.inLibrary], + inLibraryAt = mangaEntry[MangaTable.inLibraryAt], meta = getMangaMetaMap(mangaId), realUrl = mangaEntry[MangaTable.realUrl], lastFetchedAt = mangaEntry[MangaTable.lastFetchedAt], chaptersLastFetchedAt = mangaEntry[MangaTable.chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[MangaTable.updateStrategy]), freshData = false ) } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/LibraryManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/LibraryManga.kt deleted file mode 100644 index c3ee8630c..000000000 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/LibraryManga.kt +++ /dev/null @@ -1,8 +0,0 @@ -package suwayomi.tachidesk.manga.impl.backup.models - -class LibraryManga : MangaImpl() { - - var unread: Int = 0 - - var category: Int = 0 -} diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt index 123d78e68..c1b7ac535 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/models/MangaImpl.kt @@ -1,5 +1,6 @@ package suwayomi.tachidesk.manga.impl.backup.models +import eu.kanade.tachiyomi.source.model.UpdateStrategy import org.jetbrains.exposed.sql.ResultRow import suwayomi.tachidesk.manga.model.table.MangaTable @@ -25,6 +26,8 @@ open class MangaImpl : Manga { override var thumbnail_url: String? = null + override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE + override var favorite: Boolean = false override var last_update: Long = 0 diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt index ce9a37ce0..55a1c407e 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupExport.kt @@ -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 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +import eu.kanade.tachiyomi.source.model.UpdateStrategy import okio.buffer import okio.gzip import okio.sink @@ -59,17 +60,18 @@ object ProtoBackupExport : ProtoBackupBase() { private fun backupManga(databaseManga: Query, flags: BackupFlags): List { return databaseManga.map { mangaRow -> val backupManga = BackupManga( - mangaRow[MangaTable.sourceReference], - mangaRow[MangaTable.url], - mangaRow[MangaTable.title], - mangaRow[MangaTable.artist], - mangaRow[MangaTable.author], - mangaRow[MangaTable.description], - mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(), - MangaStatus.valueOf(mangaRow[MangaTable.status]).value, - mangaRow[MangaTable.thumbnail_url], - TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]), - 0 // not supported in Tachidesk + source = mangaRow[MangaTable.sourceReference], + url = mangaRow[MangaTable.url], + title = mangaRow[MangaTable.title], + artist = mangaRow[MangaTable.artist], + author = mangaRow[MangaTable.author], + description = mangaRow[MangaTable.description], + genre = mangaRow[MangaTable.genre]?.split(", ") ?: emptyList(), + status = MangaStatus.valueOf(mangaRow[MangaTable.status]).value, + thumbnailUrl = mangaRow[MangaTable.thumbnail_url], + dateAdded = TimeUnit.SECONDS.toMillis(mangaRow[MangaTable.inLibraryAt]), + viewer = 0, // not supported in Tachidesk + updateStrategy = UpdateStrategy.valueOf(mangaRow[MangaTable.updateStrategy]) ) val mangaId = mangaRow[MangaTable.id].value diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt index 8123b87db..dbe9f2642 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/ProtoBackupImport.kt @@ -145,6 +145,7 @@ object ProtoBackupImport : ProtoBackupBase() { it[genre] = manga.genre it[status] = manga.status it[thumbnail_url] = manga.thumbnail_url + it[updateStrategy] = manga.update_strategy.name it[sourceReference] = manga.source @@ -193,6 +194,7 @@ object ProtoBackupImport : ProtoBackupBase() { it[genre] = manga.genre ?: dbManga[genre] it[status] = manga.status it[thumbnail_url] = manga.thumbnail_url ?: dbManga[thumbnail_url] + it[updateStrategy] = manga.update_strategy.name it[initialized] = dbManga[initialized] || manga.description != null diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt index aff20da4a..31b2e849f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/backup/proto/models/BackupManga.kt @@ -1,5 +1,6 @@ package suwayomi.tachidesk.manga.impl.backup.proto.models +import eu.kanade.tachiyomi.source.model.UpdateStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber import suwayomi.tachidesk.manga.impl.backup.models.ChapterImpl @@ -35,7 +36,8 @@ data class BackupManga( @ProtoNumber(101) var chapterFlags: Int = 0, @ProtoNumber(102) var brokenHistory: List = emptyList(), @ProtoNumber(103) var viewer_flags: Int? = null, - @ProtoNumber(104) var history: List = emptyList() + @ProtoNumber(104) var history: List = emptyList(), + @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE ) { fun getMangaImpl(): MangaImpl { return MangaImpl().apply { @@ -52,6 +54,7 @@ data class BackupManga( date_added = this@BackupManga.dateAdded viewer_flags = this@BackupManga.viewer_flags ?: this@BackupManga.viewer chapter_flags = this@BackupManga.chapterFlags + update_strategy = this@BackupManga.updateStrategy } } diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt index a2ba85ce0..7b345bd5f 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/impl/util/PackageTools.kt @@ -40,8 +40,8 @@ object PackageTools { const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory" const val METADATA_NSFW = "tachiyomi.extension.nsfw" - const val LIB_VERSION_MIN = 1.2 - const val LIB_VERSION_MAX = 1.3 + const val LIB_VERSION_MIN = 1.3 + const val LIB_VERSION_MAX = 1.4 private const val officialSignature = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key private const val unofficialSignature = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt index 68930517d..ed3049a45 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/ChapterDataClass.kt @@ -35,6 +35,9 @@ data class ChapterDataClass( /** the date we fist saw this chapter*/ val fetchedAt: Long, + /** the website url of this chapter*/ + val realUrl: String? = null, + /** is chapter downloaded */ val downloaded: Boolean, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt index 0eb1948a3..84a893397 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/dataclass/MangaDataClass.kt @@ -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 * 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.model.table.MangaStatus import java.time.Instant @@ -38,6 +39,8 @@ data class MangaDataClass( var lastFetchedAt: Long? = 0, var chaptersLastFetchedAt: Long? = 0, + var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, + val freshData: Boolean = false, var unreadCount: Long? = null, var downloadCount: Long? = null, diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt index 6a9aabf73..d3b24dbad 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/ChapterTable.kt @@ -13,6 +13,7 @@ import org.jetbrains.exposed.sql.select import org.jetbrains.exposed.sql.transactions.transaction import suwayomi.tachidesk.manga.impl.Chapter.getChapterMetaMap import suwayomi.tachidesk.manga.model.dataclass.ChapterDataClass +import suwayomi.tachidesk.manga.model.table.MangaTable.nullable object ChapterTable : IntIdTable() { val url = varchar("url", 2048) @@ -29,6 +30,9 @@ object ChapterTable : IntIdTable() { 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 pageCount = integer("page_count").default(-1) @@ -38,21 +42,22 @@ object ChapterTable : IntIdTable() { fun ChapterTable.toDataClass(chapterEntry: ResultRow) = ChapterDataClass( - chapterEntry[id].value, - chapterEntry[url], - chapterEntry[name], - chapterEntry[date_upload], - chapterEntry[chapter_number], - chapterEntry[scanlator], - chapterEntry[manga].value, - chapterEntry[isRead], - chapterEntry[isBookmarked], - chapterEntry[lastPageRead], - chapterEntry[lastReadAt], - chapterEntry[sourceOrder], - chapterEntry[fetchedAt], - chapterEntry[isDownloaded], - chapterEntry[pageCount], - transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() }, - getChapterMetaMap(chapterEntry[id]) + id = chapterEntry[id].value, + url = chapterEntry[url], + name = chapterEntry[name], + uploadDate = chapterEntry[date_upload], + chapterNumber = chapterEntry[chapter_number], + scanlator = chapterEntry[scanlator], + mangaId = chapterEntry[manga].value, + read = chapterEntry[isRead], + bookmarked = chapterEntry[isBookmarked], + lastPageRead = chapterEntry[lastPageRead], + lastReadAt = chapterEntry[lastReadAt], + index = chapterEntry[sourceOrder], + fetchedAt = chapterEntry[fetchedAt], + realUrl = chapterEntry[realUrl], + downloaded = chapterEntry[isDownloaded], + pageCount = chapterEntry[pageCount], + chapterCount = transaction { ChapterTable.select { manga eq chapterEntry[manga].value }.count().toInt() }, + meta = getChapterMetaMap(chapterEntry[id]) ) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt index e5475cc78..ca18cbacf 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/manga/model/table/MangaTable.kt @@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.model.table * 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.UpdateStrategy import org.jetbrains.exposed.dao.id.IntIdTable import org.jetbrains.exposed.sql.ResultRow import suwayomi.tachidesk.manga.impl.Manga.getMangaMetaMap @@ -42,31 +43,34 @@ object MangaTable : IntIdTable() { val lastFetchedAt = long("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) = MangaDataClass( - mangaEntry[this.id].value, - mangaEntry[sourceReference].toString(), - - mangaEntry[url], - mangaEntry[title], - proxyThumbnailUrl(mangaEntry[this.id].value), - mangaEntry[MangaTable.thumbnailUrlLastFetched], - - mangaEntry[initialized], - - mangaEntry[artist], - mangaEntry[author], - mangaEntry[description], - mangaEntry[genre].toGenreList(), - Companion.valueOf(mangaEntry[status]).name, - mangaEntry[inLibrary], - mangaEntry[inLibraryAt], + id = mangaEntry[this.id].value, + sourceId = mangaEntry[sourceReference].toString(), + + url = mangaEntry[url], + title = mangaEntry[title], + thumbnailUrl = proxyThumbnailUrl(mangaEntry[this.id].value), + thumbnailUrlLastFetched = mangaEntry[thumbnailUrlLastFetched], + + initialized = mangaEntry[initialized], + + artist = mangaEntry[artist], + author = mangaEntry[author], + description = mangaEntry[description], + genre = mangaEntry[genre].toGenreList(), + status = Companion.valueOf(mangaEntry[status]).name, + inLibrary = mangaEntry[inLibrary], + inLibraryAt = mangaEntry[inLibraryAt], meta = getMangaMetaMap(mangaEntry[id].value), realUrl = mangaEntry[realUrl], lastFetchedAt = mangaEntry[lastFetchedAt], - chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt] + chaptersLastFetchedAt = mangaEntry[chaptersLastFetchedAt], + updateStrategy = UpdateStrategy.valueOf(mangaEntry[updateStrategy]) ) enum class MangaStatus(val value: Int) { diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt new file mode 100644 index 000000000..3565b3992 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0024_MangaUpdateStrategy.kt @@ -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}'" +) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt new file mode 100644 index 000000000..dd9d42343 --- /dev/null +++ b/server/src/main/kotlin/suwayomi/tachidesk/server/database/migration/M0025_ChapterRealUrl.kt @@ -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" +)