diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt index beaf289195..d39038242a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt @@ -5,6 +5,7 @@ import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.MangaImpl import eu.kanade.tachiyomi.data.database.models.TrackImpl import eu.kanade.tachiyomi.data.library.CustomMangaManager +import eu.kanade.tachiyomi.source.model.UpdateStrategy import kotlinx.serialization.Serializable import kotlinx.serialization.protobuf.ProtoNumber @@ -38,6 +39,7 @@ data class BackupManga( @ProtoNumber(102) var brokenHistory: List = emptyList(), @ProtoNumber(103) var viewer_flags: Int? = null, @ProtoNumber(104) var history: List = emptyList(), + @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE, // SY specific values @ProtoNumber(602) var customStatus: Int = 0, @@ -69,6 +71,7 @@ data class BackupManga( ).takeIf { it != 0 } ?: -1 chapter_flags = this@BackupManga.chapterFlags + update_strategy = this@BackupManga.updateStrategy } } @@ -122,6 +125,7 @@ data class BackupManga( viewer = manga.readingModeType, viewer_flags = manga.viewer_flags.takeIf { it != -1 } ?: 0, chapterFlags = manga.chapter_flags, + updateStrategy = manga.update_strategy, ).also { backupManga -> customMangaManager?.getManga(manga)?.let { backupManga.customTitle = it.title diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt new file mode 100644 index 0000000000..2ae54be1ff --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseAdapter.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.data.database + +import eu.kanade.tachiyomi.source.model.UpdateStrategy +import java.util.Date + +val dateAdapter = object : ColumnAdapter { + override fun decode(databaseValue: Long): Date = Date(databaseValue) + override fun encode(value: Date): Long = value.time +} + +private const val listOfStringsSeparator = ", " +val listOfStringsAdapter = object : ColumnAdapter, String> { + override fun decode(databaseValue: String) = + if (databaseValue.isEmpty()) { + emptyList() + } else { + databaseValue.split(listOfStringsSeparator) + } + override fun encode(value: List) = value.joinToString(separator = listOfStringsSeparator) +} + +val updateStrategyAdapter = object : ColumnAdapter { + private val enumValues by lazy { UpdateStrategy.values() } + + override fun decode(databaseValue: Int): UpdateStrategy = + enumValues.getOrElse(databaseValue) { UpdateStrategy.ALWAYS_UPDATE } + + override fun encode(value: UpdateStrategy): Int = value.ordinal +} + +interface ColumnAdapter { + /** + * @return [databaseValue] decoded as type [T]. + */ + fun decode(databaseValue: S): T + + /** + * @return [value] encoded as database type [S]. + */ + fun encode(value: T): S +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt index c3fff57c3f..563d1a126f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DbOpenCallback.kt @@ -20,7 +20,7 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { /** * Version of the database. */ - const val DATABASE_VERSION = 15 + const val DATABASE_VERSION = 16 } override fun onOpen(db: SupportSQLiteDatabase) { @@ -109,6 +109,9 @@ class DbOpenCallback : SupportSQLiteOpenHelper.Callback(DATABASE_VERSION) { db.execSQL(TrackTable.insertFromTempTable) db.execSQL(TrackTable.dropTempTable) } + if (oldVersion < 16) { + db.execSQL(MangaTable.addUpdateStrategy) + } } override fun onConfigure(db: SupportSQLiteDatabase) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt index a0b0b0388d..aeb2b9fc5e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/mappers/MangaTypeMapping.kt @@ -27,9 +27,11 @@ import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_TITLE +import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_UPDATE_STRATEGY import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_URL import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE +import eu.kanade.tachiyomi.data.database.updateStrategyAdapter class MangaTypeMapping : SQLiteTypeMapping( MangaPutResolver(), @@ -68,6 +70,7 @@ class MangaPutResolver : DefaultPutResolver() { put(COL_CHAPTER_FLAGS, obj.chapter_flags) put(COL_DATE_ADDED, obj.date_added) put(COL_FILTERED_SCANLATORS, obj.filtered_scanlators) + put(COL_UPDATE_STRATEGY, obj.update_strategy.let(updateStrategyAdapter::encode)) } } @@ -91,6 +94,9 @@ interface BaseMangaGetResolver { hide_title = cursor.getInt(cursor.getColumnIndex(COL_HIDE_TITLE)) == 1 date_added = cursor.getLong(cursor.getColumnIndex(COL_DATE_ADDED)) filtered_scanlators = cursor.getString(cursor.getColumnIndex(COL_FILTERED_SCANLATORS)) + update_strategy = cursor.getInt(cursor.getColumnIndex(COL_UPDATE_STRATEGY)).let( + updateStrategyAdapter::decode, + ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt index c963c731d6..84992576b3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt @@ -4,6 +4,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.source.model.UpdateStrategy import uy.kohesive.injekt.injectLazy open class MangaImpl : Manga { @@ -68,6 +69,8 @@ open class MangaImpl : Manga { override var date_added: Long = 0 + override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE + override var filtered_scanlators: String? = null lateinit var ogTitle: String diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt index d14cbb5618..ab42221edf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/tables/MangaTable.kt @@ -46,6 +46,8 @@ object MangaTable { const val COL_FILTERED_SCANLATORS = "filtered_scanlators" + const val COL_UPDATE_STRATEGY = "update_strategy" + val createTableQuery: String get() = """CREATE TABLE $TABLE( @@ -66,7 +68,8 @@ object MangaTable { $COL_HIDE_TITLE INTEGER NOT NULL, $COL_CHAPTER_FLAGS INTEGER NOT NULL, $COL_DATE_ADDED LONG, - $COL_FILTERED_SCANLATORS TEXT + $COL_FILTERED_SCANLATORS TEXT, + $COL_UPDATE_STRATEGY INTEGER AS UpdateStrategy NOT NULL DEFAULT 0 )""" @@ -85,4 +88,7 @@ object MangaTable { val addFilteredScanlators: String get() = "ALTER TABLE $TABLE ADD COLUMN $COL_FILTERED_SCANLATORS TEXT" + + val addUpdateStrategy: String + get() = "ALTER TABLE $TABLE ADD COLUMN $COL_UPDATE_STRATEGY INTEGER NOT NULL DEFAULT 0" } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index b837ef7c13..2dbb305f65 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -31,6 +31,7 @@ import eu.kanade.tachiyomi.extension.ExtensionUpdateJob import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.UnmeteredSource 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.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay @@ -300,18 +301,24 @@ class LibraryUpdateService( private fun filterMangaToUpdate(mangaToAdd: List): List { val restrictions = preferences.libraryUpdateMangaRestriction().get() return mangaToAdd.filter { manga -> - return@filter if (MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED) { - skippedUpdates[manga] = getString(R.string.skipped_reason_completed) - false - } else if (MANGA_HAS_UNREAD in restrictions && manga.unread != 0) { - skippedUpdates[manga] = getString(R.string.skipped_reason_not_caught_up) - false - } else if (MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead) { - skippedUpdates[manga] = getString(R.string.skipped_reason_not_started) - false - } else { - true + when { + MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED -> { + skippedUpdates[manga] = getString(R.string.skipped_reason_completed) + } + MANGA_HAS_UNREAD in restrictions && manga.unread != 0 -> { + skippedUpdates[manga] = getString(R.string.skipped_reason_not_caught_up) + } + MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasRead -> { + skippedUpdates[manga] = getString(R.string.skipped_reason_not_started) + } + manga.update_strategy != UpdateStrategy.ALWAYS_UPDATE -> { + skippedUpdates[manga] = getString(R.string.skipped_reason_not_always_update) + } + else -> { + return@filter true + } } + return@filter false } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 7eeef6c447..5b805bdfda 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -39,7 +39,7 @@ internal object ExtensionLoader { private const val METADATA_HAS_README = "tachiyomi.extension.hasReadme" private const val METADATA_HAS_CHANGELOG = "tachiyomi.extension.hasChangelog" const val LIB_VERSION_MIN = 1.2 - const val LIB_VERSION_MAX = 1.3 + const val LIB_VERSION_MAX = 1.4 private const val PACKAGE_FLAGS = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt index 8fb5ec2aa8..6adb0de8ef 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/Requests.kt @@ -3,6 +3,8 @@ package eu.kanade.tachiyomi.network import okhttp3.CacheControl import okhttp3.FormBody import okhttp3.Headers +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Request import okhttp3.RequestBody import java.util.concurrent.TimeUnit.MINUTES @@ -15,6 +17,17 @@ fun GET( url: String, headers: Headers = DEFAULT_HEADERS, cache: CacheControl = DEFAULT_CACHE_CONTROL, +): Request { + return GET(url.toHttpUrl(), headers, cache) +} + +/** + * @since extensions-lib 1.4 + */ +fun GET( + url: HttpUrl, + headers: Headers = DEFAULT_HEADERS, + cache: CacheControl = DEFAULT_CACHE_CONTROL, ): Request { return Request.Builder() .url(url) diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt index ba6b05ab9d..76ec794db3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/SManga.kt @@ -21,6 +21,8 @@ interface SManga : Serializable { var thumbnail_url: String? + var update_strategy: UpdateStrategy + var initialized: Boolean val originalTitle: String @@ -59,6 +61,8 @@ interface SManga : Serializable { status = other.originalStatus + update_strategy = other.update_strategy + if (!initialized) { initialized = other.initialized } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt b/app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt new file mode 100644 index 0000000000..91b5f5e290 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/source/model/UpdateStrategy.kt @@ -0,0 +1,22 @@ +package eu.kanade.tachiyomi.source.model + +/** + * Define the update strategy for a single [SManga]. + * The strategy used will only take effect on the library update. + * + * @since extensions-lib 1.4 + */ +enum class UpdateStrategy { + /** + * Series marked as always update will be included in the library + * update if they aren't excluded by additional restrictions. + */ + ALWAYS_UPDATE, + + /** + * Series marked as only fetch once will be automatically skipped + * during library updates. Useful for cases where the series is previously + * known to be finished and have only a single chapter, for example. + */ + ONLY_FETCH_ONCE, +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7bb6a5767d..4b389c5002 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -211,6 +211,7 @@ Skipped because series is complete Skipped because there are unread chapters Skipped because no chapters are read + Skipped because series does not require updates Errors Skipped