Browse Source

Add support to update strategy on global update (#7902)

* Add support to update strategy.

* Add JavaDoc and bump the LIB_VERSION_MAX constant.

* Fix a word typo.

* Store update strategy enum as integer in the DB.
Alessandro Jean 2 years ago
parent
commit
ba533f30ce

+ 10 - 0
app/src/main/java/eu/kanade/data/DatabaseAdapter.kt

@@ -1,6 +1,7 @@
 package eu.kanade.data
 
 import com.squareup.sqldelight.ColumnAdapter
+import eu.kanade.tachiyomi.source.model.UpdateStrategy
 import java.util.Date
 
 val dateAdapter = object : ColumnAdapter<Date, Long> {
@@ -18,3 +19,12 @@ val listOfStringsAdapter = object : ColumnAdapter<List<String>, String> {
         }
     override fun encode(value: List<String>) = value.joinToString(separator = listOfStringsSeparator)
 }
+
+val updateStrategyAdapter = object : ColumnAdapter<UpdateStrategy, Long> {
+    private val enumValues by lazy { UpdateStrategy.values() }
+
+    override fun decode(databaseValue: Long): UpdateStrategy =
+        enumValues.getOrElse(databaseValue.toInt()) { UpdateStrategy.ALWAYS_UPDATE }
+
+    override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong()
+}

+ 10 - 6
app/src/main/java/eu/kanade/data/manga/MangaMapper.kt

@@ -3,9 +3,10 @@ package eu.kanade.data.manga
 import eu.kanade.domain.chapter.model.Chapter
 import eu.kanade.domain.manga.model.Manga
 import eu.kanade.tachiyomi.data.database.models.LibraryManga
+import eu.kanade.tachiyomi.source.model.UpdateStrategy
 
-val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long) -> Manga =
-    { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded ->
+val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy) -> Manga =
+    { id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded, updateStrategy ->
         Manga(
             id = id,
             source = source,
@@ -23,12 +24,13 @@ val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?,
             genre = genre,
             status = status,
             thumbnailUrl = thumbnailUrl,
+            updateStrategy = updateStrategy,
             initialized = initialized,
         )
     }
 
-val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Pair<Manga, Chapter> =
-    { _id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, next_update, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, chapterId, mangaId, chapterUrl, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
+val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, String, String, String?, Boolean, Boolean, Long, Float, Long, Long, Long) -> Pair<Manga, Chapter> =
+    { _id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, next_update, initialized, viewerFlags, chapterFlags, coverLastModified, dateAdded, updateStrategy, chapterId, mangaId, chapterUrl, name, scanlator, read, bookmark, lastPageRead, chapterNumber, sourceOrder, dateFetch, dateUpload ->
         Manga(
             id = _id,
             source = source,
@@ -46,6 +48,7 @@ val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<Str
             genre = genre,
             status = status,
             thumbnailUrl = thumbnailUrl,
+            updateStrategy = updateStrategy,
             initialized = initialized,
         ) to Chapter(
             id = chapterId,
@@ -63,8 +66,8 @@ val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<Str
         )
     }
 
-val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga =
-    { _id, source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, unread_count, read_count, category ->
+val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, UpdateStrategy, Long, Long, Long) -> LibraryManga =
+    { _id, source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, update_strategy, unread_count, read_count, category ->
         LibraryManga().apply {
             this.id = _id
             this.source = source
@@ -78,6 +81,7 @@ val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?,
             this.thumbnail_url = thumbnail_url
             this.favorite = favorite
             this.last_update = last_update ?: 0
+            this.update_strategy = update_strategy
             this.initialized = initialized
             this.viewer_flags = viewer.toInt()
             this.chapter_flags = chapter_flags.toInt()

+ 3 - 0
app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt

@@ -2,6 +2,7 @@ package eu.kanade.data.manga
 
 import eu.kanade.data.DatabaseHandler
 import eu.kanade.data.listOfStringsAdapter
+import eu.kanade.data.updateStrategyAdapter
 import eu.kanade.domain.manga.model.Manga
 import eu.kanade.domain.manga.model.MangaUpdate
 import eu.kanade.domain.manga.repository.MangaRepository
@@ -92,6 +93,7 @@ class MangaRepositoryImpl(
                 chapterFlags = manga.chapterFlags,
                 coverLastModified = manga.coverLastModified,
                 dateAdded = manga.dateAdded,
+                updateStrategy = manga.updateStrategy,
             )
             mangasQueries.selectLastInsertedRowId()
         }
@@ -138,6 +140,7 @@ class MangaRepositoryImpl(
                     coverLastModified = value.coverLastModified,
                     dateAdded = value.dateAdded,
                     mangaId = value.id,
+                    updateStrategy = value.updateStrategy?.let(updateStrategyAdapter::encode),
                 )
             }
         }

+ 1 - 0
app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt

@@ -68,6 +68,7 @@ class UpdateManga(
                 genre = remoteManga.getGenres(),
                 thumbnailUrl = thumbnailUrl,
                 status = remoteManga.status.toLong(),
+                updateStrategy = remoteManga.update_strategy,
                 initialized = true,
             ),
         )

+ 5 - 0
app/src/main/java/eu/kanade/domain/manga/model/Manga.kt

@@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.database.models.MangaImpl
 import eu.kanade.tachiyomi.source.LocalSource
 import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.model.UpdateStrategy
 import eu.kanade.tachiyomi.widget.ExtendedNavigationView
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
@@ -29,6 +30,7 @@ data class Manga(
     val genre: List<String>?,
     val status: Long,
     val thumbnailUrl: String?,
+    val updateStrategy: UpdateStrategy,
     val initialized: Boolean,
 ) : Serializable {
 
@@ -143,6 +145,7 @@ data class Manga(
             genre = null,
             status = 0L,
             thumbnailUrl = null,
+            updateStrategy = UpdateStrategy.ALWAYS_UPDATE,
             initialized = false,
         )
     }
@@ -180,6 +183,7 @@ fun Manga.toDbManga(): DbManga = MangaImpl().also {
     it.genre = genre?.let(listOfStringsAdapter::encode)
     it.status = status.toInt()
     it.thumbnail_url = thumbnailUrl
+    it.update_strategy = updateStrategy
     it.initialized = initialized
 }
 
@@ -201,6 +205,7 @@ fun Manga.toMangaUpdate(): MangaUpdate {
         genre = genre,
         status = status,
         thumbnailUrl = thumbnailUrl,
+        updateStrategy = updateStrategy,
         initialized = initialized,
     )
 }

+ 3 - 0
app/src/main/java/eu/kanade/domain/manga/model/MangaUpdate.kt

@@ -1,5 +1,7 @@
 package eu.kanade.domain.manga.model
 
+import eu.kanade.tachiyomi.source.model.UpdateStrategy
+
 data class MangaUpdate(
     val id: Long,
     val source: Long? = null,
@@ -17,5 +19,6 @@ data class MangaUpdate(
     val genre: List<String>? = null,
     val status: Long? = null,
     val thumbnailUrl: String? = null,
+    val updateStrategy: UpdateStrategy? = null,
     val initialized: Boolean? = null,
 )

+ 2 - 0
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt

@@ -13,6 +13,7 @@ import eu.kanade.data.AndroidDatabaseHandler
 import eu.kanade.data.DatabaseHandler
 import eu.kanade.data.dateAdapter
 import eu.kanade.data.listOfStringsAdapter
+import eu.kanade.data.updateStrategyAdapter
 import eu.kanade.domain.backup.service.BackupPreferences
 import eu.kanade.domain.base.BasePreferences
 import eu.kanade.domain.download.service.DownloadPreferences
@@ -87,6 +88,7 @@ class AppModule(val app: Application) : InjektModule {
                 ),
                 mangasAdapter = Mangas.Adapter(
                     genreAdapter = listOfStringsAdapter,
+                    update_strategyAdapter = updateStrategyAdapter,
                 ),
             )
         }

+ 3 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupManager.kt

@@ -7,6 +7,7 @@ import com.hippo.unifile.UniFile
 import data.Manga_sync
 import data.Mangas
 import eu.kanade.data.DatabaseHandler
+import eu.kanade.data.updateStrategyAdapter
 import eu.kanade.domain.backup.service.BackupPreferences
 import eu.kanade.domain.category.interactor.GetCategories
 import eu.kanade.domain.category.model.Category
@@ -506,6 +507,7 @@ class BackupManager(
                 chapterFlags = manga.chapter_flags.toLong(),
                 coverLastModified = manga.cover_last_modified,
                 dateAdded = manga.date_added,
+                updateStrategy = manga.update_strategy,
             )
             mangasQueries.selectLastInsertedRowId()
         }
@@ -531,6 +533,7 @@ class BackupManager(
                 coverLastModified = manga.cover_last_modified,
                 dateAdded = manga.date_added,
                 mangaId = manga.id!!,
+                updateStrategy = manga.update_strategy.let(updateStrategyAdapter::encode),
             )
         }
         return manga.id!!

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupManga.kt

@@ -4,6 +4,7 @@ import eu.kanade.domain.manga.model.Manga
 import eu.kanade.tachiyomi.data.database.models.ChapterImpl
 import eu.kanade.tachiyomi.data.database.models.MangaImpl
 import eu.kanade.tachiyomi.data.database.models.TrackImpl
+import eu.kanade.tachiyomi.source.model.UpdateStrategy
 import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
 import kotlinx.serialization.Serializable
 import kotlinx.serialization.protobuf.ProtoNumber
@@ -38,6 +39,7 @@ data class BackupManga(
     @ProtoNumber(102) var brokenHistory: List<BrokenBackupHistory> = emptyList(),
     @ProtoNumber(103) var viewer_flags: Int? = null,
     @ProtoNumber(104) var history: List<BackupHistory> = emptyList(),
+    @ProtoNumber(105) var updateStrategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE,
 ) {
     fun getMangaImpl(): MangaImpl {
         return MangaImpl().apply {
@@ -54,6 +56,7 @@ data class BackupManga(
             date_added = [email protected]
             viewer_flags = [email protected]_flags ?: [email protected]
             chapter_flags = [email protected]
+            update_strategy = [email protected]
         }
     }
 
@@ -86,6 +89,7 @@ data class BackupManga(
                 viewer = (manga.viewerFlags.toInt() and ReadingModeType.MASK),
                 viewer_flags = manga.viewerFlags.toInt(),
                 chapterFlags = manga.chapterFlags.toInt(),
+                updateStrategy = manga.updateStrategy,
             )
         }
     }

+ 1 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/Manga.kt

@@ -93,6 +93,7 @@ fun Manga.toDomainManga(): DomainManga? {
         genre = getGenres(),
         status = status.toLong(),
         thumbnailUrl = thumbnail_url,
+        updateStrategy = update_strategy,
         initialized = initialized,
     )
 }

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/data/database/models/MangaImpl.kt

@@ -1,5 +1,7 @@
 package eu.kanade.tachiyomi.data.database.models
 
+import eu.kanade.tachiyomi.source.model.UpdateStrategy
+
 open class MangaImpl : Manga {
 
     override var id: Long? = null
@@ -28,6 +30,8 @@ open class MangaImpl : Manga {
 
     override var date_added: Long = 0
 
+    override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE
+
     override var initialized: Boolean = false
 
     override var viewer_flags: Int = 0

+ 4 - 0
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt

@@ -43,6 +43,7 @@ import eu.kanade.tachiyomi.data.track.TrackService
 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.util.lang.withIOContext
 import eu.kanade.tachiyomi.util.prepUpdateCover
 import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
@@ -347,6 +348,9 @@ class LibraryUpdateService(
                                             MANGA_NON_READ in restrictions && mangaWithNotif.totalChapters > 0 && !mangaWithNotif.hasStarted ->
                                                 skippedUpdates.add(mangaWithNotif to getString(R.string.skipped_reason_not_started))
 
+                                            mangaWithNotif.update_strategy != UpdateStrategy.ALWAYS_UPDATE ->
+                                                skippedUpdates.add(mangaWithNotif to getString(R.string.skipped_reason_not_always_update))
+
                                             else -> {
                                                 // Convert to the manga that contains new chapters
                                                 mangaWithNotif.toDomainManga()?.let { domainManga ->

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt

@@ -38,7 +38,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
 

+ 7 - 4
app/src/main/sqldelight/data/mangas.sq

@@ -1,3 +1,4 @@
+import eu.kanade.tachiyomi.source.model.UpdateStrategy;
 import java.lang.String;
 import kotlin.collections.List;
 
@@ -19,7 +20,8 @@ CREATE TABLE mangas(
     viewer INTEGER NOT NULL,
     chapter_flags INTEGER NOT NULL,
     cover_last_modified INTEGER AS Long NOT NULL,
-    date_added INTEGER AS Long NOT NULL
+    date_added INTEGER AS Long NOT NULL,
+    update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0
 );
 
 CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1;
@@ -133,8 +135,8 @@ DELETE FROM mangas
 WHERE favorite = 0 AND source IN :sourceIds;
 
 insert:
-INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added)
-VALUES (:source,:url,:artist,:author,:description,:genre,:title,:status,:thumbnailUrl,:favorite,:lastUpdate,:nextUpdate,:initialized,:viewerFlags,:chapterFlags,:coverLastModified,:dateAdded);
+INSERT INTO mangas(source,url,artist,author,description,genre,title,status,thumbnail_url,favorite,last_update,next_update,initialized,viewer,chapter_flags,cover_last_modified,date_added,update_strategy)
+VALUES (:source,:url,:artist,:author,:description,:genre,:title,:status,:thumbnailUrl,:favorite,:lastUpdate,:nextUpdate,:initialized,:viewerFlags,:chapterFlags,:coverLastModified,:dateAdded,:updateStrategy);
 
 update:
 UPDATE mangas SET
@@ -153,7 +155,8 @@ UPDATE mangas SET
     viewer = coalesce(:viewer, viewer),
     chapter_flags = coalesce(:chapterFlags, chapter_flags),
     cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
-    date_added = coalesce(:dateAdded, date_added)
+    date_added = coalesce(:dateAdded, date_added),
+    update_strategy = coalesce(:updateStrategy, update_strategy)
 WHERE _id = :mangaId;
 
 selectLastInsertedRowId:

+ 1 - 0
app/src/main/sqldelight/migrations/20.sqm

@@ -0,0 +1 @@
+ALTER TABLE mangas ADD COLUMN update_strategy INTEGER NOT NULL DEFAULT 0;

+ 1 - 0
i18n/src/main/res/values/strings.xml

@@ -801,6 +801,7 @@
     <string name="skipped_reason_completed">Skipped because series is complete</string>
     <string name="skipped_reason_not_caught_up">Skipped because there are unread chapters</string>
     <string name="skipped_reason_not_started">Skipped because no chapters are read</string>
+    <string name="skipped_reason_not_always_update">Skipped because series does not require updates</string>
 
     <!-- File Picker Titles -->
     <string name="file_select_cover">Select cover image</string>

+ 5 - 0
source-api/src/main/java/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 getGenres(): List<String>? {
@@ -50,6 +52,8 @@ interface SManga : Serializable {
 
         status = other.status
 
+        update_strategy = other.update_strategy
+
         if (!initialized) {
             initialized = other.initialized
         }
@@ -64,6 +68,7 @@ interface SManga : Serializable {
         it.genre = genre
         it.status = status
         it.thumbnail_url = thumbnail_url
+        it.update_strategy = update_strategy
         it.initialized = initialized
     }
 

+ 2 - 0
source-api/src/main/java/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
 }

+ 22 - 0
source-api/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
+}