From 94cab02c1ef0d6c7483a05b059f94a2771e37cf6 Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Mon, 15 Jun 2026 23:32:39 +0600 Subject: [PATCH] Adapt model implementation for TachiyomiX 1.6 (#3412) --- .../create/creators/MangaBackupCreator.kt | 2 ++ .../data/backup/models/BackupChapter.kt | 9 +++++++- .../data/backup/models/BackupManga.kt | 7 +++++- .../backup/restore/restorers/MangaRestorer.kt | 6 +++++ .../tachiyomi/data/database/models/Chapter.kt | 1 + .../data/database/models/ChapterImpl.kt | 4 ++++ .../java/eu/kanade/tachiyomi/di/AppModule.kt | 6 +++++ .../interactor/UpdateMangaFromRemote.kt | 1 + .../core/common/extensions/JsonObject.kt | 8 +++++++ .../java/tachiyomi/data/DatabaseAdapter.kt | 14 ++++++++++++ .../data/chapter/ChapterRepositoryImpl.kt | 7 ++++++ .../java/tachiyomi/data/manga/MangaMapper.kt | 7 ++++++ .../data/manga/MangaRepositoryImpl.kt | 3 +++ .../sqldelight/tachiyomi/data/chapters.sq | 13 ++++++----- .../main/sqldelight/tachiyomi/data/mangas.sq | 20 ++++++++++------- .../sqldelight/tachiyomi/migrations/12.sqm | 2 ++ .../GetExtensionStoreCountAsFlow.kt | 1 + .../tachiyomi/domain/chapter/model/Chapter.kt | 5 +++++ .../domain/chapter/model/ChapterUpdate.kt | 4 ++++ .../tachiyomi/domain/manga/model/Manga.kt | 4 ++++ .../domain/manga/model/MangaUpdate.kt | 3 +++ .../tachiyomi/source/model/MangasPage.kt | 18 ++++++++++++++- .../kanade/tachiyomi/source/model/SChapter.kt | 18 +++++++++++++-- .../tachiyomi/source/model/SChapterImpl.kt | 8 +++++-- .../kanade/tachiyomi/source/model/SManga.kt | 22 +++++++++++++++---- .../tachiyomi/source/model/SMangaImpl.kt | 12 ++++++---- 26 files changed, 177 insertions(+), 28 deletions(-) create mode 100644 core/common/src/main/kotlin/mihon/core/common/extensions/JsonObject.kt create mode 100644 data/src/main/sqldelight/tachiyomi/migrations/12.sqm diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt index 618c0c8f0..76a82fbb5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/create/creators/MangaBackupCreator.kt @@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.data.backup.models.backupChapterMapper import eu.kanade.tachiyomi.data.backup.models.backupTrackMapper import eu.kanade.tachiyomi.ui.reader.setting.ReadingMode import tachiyomi.data.Database +import tachiyomi.data.MemoColumnAdapter import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.history.interactor.GetHistory import tachiyomi.domain.manga.model.Manga @@ -107,4 +108,5 @@ private fun Manga.toBackupManga() = version = this.version, notes = this.notes, initialized = this.initialized, + memo = MemoColumnAdapter.encode(this.memo), ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt index b0d081a40..c74dbc6e4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/models/BackupChapter.kt @@ -1,11 +1,14 @@ package eu.kanade.tachiyomi.data.backup.models import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.protobuf.ProtoNumber +import mihon.core.common.extensions.EMPTY +import tachiyomi.data.MemoColumnAdapter import tachiyomi.domain.chapter.model.Chapter @Serializable -data class BackupChapter( +class BackupChapter( // in 1.x some of these values have different names // url is called key in 1.x @ProtoNumber(1) var url: String, @@ -22,6 +25,7 @@ data class BackupChapter( @ProtoNumber(10) var sourceOrder: Long = 0, @ProtoNumber(11) var lastModifiedAt: Long = 0, @ProtoNumber(12) var version: Long = 0, + @ProtoNumber(13) var memo: ByteArray = byteArrayOf(), ) { fun toChapterImpl(): Chapter { return Chapter.create().copy( @@ -37,6 +41,7 @@ data class BackupChapter( sourceOrder = this@BackupChapter.sourceOrder, lastModifiedAt = this@BackupChapter.lastModifiedAt, version = this@BackupChapter.version, + memo = MemoColumnAdapter.decode(this@BackupChapter.memo), ) } } @@ -57,6 +62,7 @@ val backupChapterMapper = { lastModifiedAt: Long, version: Long, _: Long, + memo: JsonObject, -> BackupChapter( url = url, @@ -71,5 +77,6 @@ val backupChapterMapper = { sourceOrder = sourceOrder, lastModifiedAt = lastModifiedAt, version = version, + memo = MemoColumnAdapter.encode(memo), ) } 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 9c5368f38..20012fc39 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 @@ -2,12 +2,15 @@ package eu.kanade.tachiyomi.data.backup.models import eu.kanade.tachiyomi.source.model.UpdateStrategy import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonObject import kotlinx.serialization.protobuf.ProtoNumber +import mihon.core.common.extensions.EMPTY +import tachiyomi.data.MemoColumnAdapter import tachiyomi.domain.manga.model.Manga @Suppress("DEPRECATION") @Serializable -data class BackupManga( +class BackupManga( // in 1.x some of these values have different names @ProtoNumber(1) var source: Long, // url is called key in 1.x @@ -43,6 +46,7 @@ data class BackupManga( @ProtoNumber(109) var version: Long = 0, @ProtoNumber(110) var notes: String = "", @ProtoNumber(111) var initialized: Boolean = false, + @ProtoNumber(112) var memo: ByteArray = byteArrayOf(), ) { fun getMangaImpl(): Manga { return Manga.create().copy( @@ -65,6 +69,7 @@ data class BackupManga( version = this@BackupManga.version, notes = this@BackupManga.notes, initialized = this@BackupManga.initialized, + memo = MemoColumnAdapter.decode(this@BackupManga.memo), ) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt index ee939e748..4c0a95e42 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/restore/restorers/MangaRestorer.kt @@ -10,6 +10,8 @@ import eu.kanade.tachiyomi.data.backup.models.BackupHistory import eu.kanade.tachiyomi.data.backup.models.BackupManga import eu.kanade.tachiyomi.data.backup.models.BackupTracking import tachiyomi.data.Database +import tachiyomi.data.MemoColumnAdapter +import tachiyomi.data.MemoColumnAdapter.encode import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId @@ -134,6 +136,7 @@ class MangaRestorer( version = manga.version, isSyncing = 1, notes = manga.notes, + memo = manga.memo.let(MemoColumnAdapter::encode), ) return manga } @@ -207,6 +210,7 @@ class MangaRestorer( chapter.dateFetch, chapter.dateUpload, chapter.version, + chapter.memo, ) } } @@ -230,6 +234,7 @@ class MangaRestorer( chapterId = chapter.id, version = chapter.version, isSyncing = 0, + memo = chapter.memo.let(MemoColumnAdapter::encode), ) } } @@ -263,6 +268,7 @@ class MangaRestorer( updateStrategy = manga.updateStrategy, version = manga.version, notes = manga.notes, + memo = manga.memo, ) .awaitAsOne() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt index 50a9f8889..f184879f5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/Chapter.kt @@ -47,5 +47,6 @@ fun Chapter.toDomainChapter(): DomainChapter? { scanlator = scanlator, lastModifiedAt = last_modified, version = version, + memo = memo, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt index fe5c5a30d..2672d0887 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/ChapterImpl.kt @@ -2,6 +2,8 @@ package eu.kanade.tachiyomi.data.database.models +import kotlinx.serialization.json.JsonObject + class ChapterImpl : Chapter { override var id: Long? = null @@ -32,6 +34,8 @@ class ChapterImpl : Chapter { override var version: Long = 0 + override var memo: JsonObject = JsonObject(emptyMap()) + override fun equals(other: Any?): Boolean { if (this === other) return true if (other == null || javaClass != other.javaClass) return false diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index 4ae504e37..e4e0b92b4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -31,10 +31,12 @@ import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.serialization.XML import tachiyomi.core.common.storage.AndroidStorageFolderProvider +import tachiyomi.data.Chapters import tachiyomi.data.Database import tachiyomi.data.DateColumnAdapter import tachiyomi.data.History import tachiyomi.data.Mangas +import tachiyomi.data.MemoColumnAdapter import tachiyomi.data.StringListColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.domain.source.service.SourceManager @@ -81,6 +83,10 @@ class AppModule(val app: Application) : InjektModule { mangasAdapter = Mangas.Adapter( genreAdapter = StringListColumnAdapter, update_strategyAdapter = UpdateStrategyColumnAdapter, + memoAdapter = MemoColumnAdapter, + ), + chaptersAdapter = Chapters.Adapter( + memoAdapter = MemoColumnAdapter, ), ) } diff --git a/app/src/main/java/mihon/domain/source/interactor/UpdateMangaFromRemote.kt b/app/src/main/java/mihon/domain/source/interactor/UpdateMangaFromRemote.kt index 624770577..f8855234a 100644 --- a/app/src/main/java/mihon/domain/source/interactor/UpdateMangaFromRemote.kt +++ b/app/src/main/java/mihon/domain/source/interactor/UpdateMangaFromRemote.kt @@ -132,6 +132,7 @@ class UpdateMangaFromRemote( status = remoteManga.status.toLong(), updateStrategy = remoteManga.update_strategy, initialized = true, + memo = remoteManga.memo, ), ) if (success && title != null) { diff --git a/core/common/src/main/kotlin/mihon/core/common/extensions/JsonObject.kt b/core/common/src/main/kotlin/mihon/core/common/extensions/JsonObject.kt new file mode 100644 index 000000000..8ad4e6dbb --- /dev/null +++ b/core/common/src/main/kotlin/mihon/core/common/extensions/JsonObject.kt @@ -0,0 +1,8 @@ +package mihon.core.common.extensions + +import kotlinx.serialization.json.JsonObject + +val JsonObjectEmpty = JsonObject(emptyMap()) + +val JsonObject.Companion.EMPTY: JsonObject + inline get() = JsonObjectEmpty diff --git a/data/src/main/java/tachiyomi/data/DatabaseAdapter.kt b/data/src/main/java/tachiyomi/data/DatabaseAdapter.kt index 596a31286..a631f9979 100644 --- a/data/src/main/java/tachiyomi/data/DatabaseAdapter.kt +++ b/data/src/main/java/tachiyomi/data/DatabaseAdapter.kt @@ -2,6 +2,10 @@ package tachiyomi.data import app.cash.sqldelight.ColumnAdapter import eu.kanade.tachiyomi.source.model.UpdateStrategy +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.encodeToJsonElement +import kotlinx.serialization.json.jsonObject import java.util.Date object DateColumnAdapter : ColumnAdapter { @@ -27,3 +31,13 @@ object UpdateStrategyColumnAdapter : ColumnAdapter { override fun encode(value: UpdateStrategy): Long = value.ordinal.toLong() } + +object MemoColumnAdapter : ColumnAdapter { + override fun decode(databaseValue: ByteArray): JsonObject { + return Json.decodeFromString(databaseValue.decodeToString()) + } + + override fun encode(value: JsonObject): ByteArray { + return value.toString().encodeToByteArray() + } +} diff --git a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt index 1a2ceaa0b..e82a652f9 100644 --- a/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/chapter/ChapterRepositoryImpl.kt @@ -4,10 +4,13 @@ import app.cash.sqldelight.async.coroutines.awaitAsList import app.cash.sqldelight.async.coroutines.awaitAsOne import app.cash.sqldelight.async.coroutines.awaitAsOneOrNull import kotlinx.coroutines.flow.Flow +import kotlinx.serialization.json.JsonObject import logcat.LogPriority import tachiyomi.core.common.util.lang.toLong import tachiyomi.core.common.util.system.logcat import tachiyomi.data.Database +import tachiyomi.data.MemoColumnAdapter +import tachiyomi.data.MemoColumnAdapter.encode import tachiyomi.data.subscribeToList import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.ChapterUpdate @@ -34,6 +37,7 @@ class ChapterRepositoryImpl( chapter.dateFetch, chapter.dateUpload, chapter.version, + chapter.memo, ) .awaitAsOne() chapter.copy(id = chapterId) @@ -71,6 +75,7 @@ class ChapterRepositoryImpl( chapterId = chapterUpdate.id, version = chapterUpdate.version, isSyncing = 0, + memo = chapterUpdate.memo?.let(MemoColumnAdapter::encode), ) } } @@ -143,6 +148,7 @@ class ChapterRepositoryImpl( lastModifiedAt: Long, version: Long, isSyncing: Long, + memo: JsonObject, ): Chapter = Chapter( id = id, mangaId = mangaId, @@ -158,5 +164,6 @@ class ChapterRepositoryImpl( scanlator = scanlator, lastModifiedAt = lastModifiedAt, version = version, + memo = memo, ) } diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index 7087a0b95..bfa819a24 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -1,6 +1,7 @@ package tachiyomi.data.manga import eu.kanade.tachiyomi.source.model.UpdateStrategy +import kotlinx.serialization.json.JsonObject import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaWithChapterCount @@ -33,6 +34,7 @@ object MangaMapper { version: Long, isSyncing: Long, notes: String, + memo: JsonObject, ): Manga = Manga( id = id, source = source, @@ -58,6 +60,7 @@ object MangaMapper { favoriteModifiedAt = favoriteModifiedAt, version = version, notes = notes, + memo = memo, ) fun mapLibraryManga( @@ -86,6 +89,7 @@ object MangaMapper { version: Long, isSyncing: Long, notes: String, + memo: JsonObject, totalCount: Long, readCount: Double, latestUpload: Long, @@ -120,6 +124,7 @@ object MangaMapper { version, isSyncing, notes, + memo, ), categories = categories.split(",").map { it.toLong() }, totalChapters = totalCount, @@ -156,6 +161,7 @@ object MangaMapper { version: Long, isSyncing: Long, notes: String, + memo: JsonObject, totalCount: Long, ): MangaWithChapterCount = MangaWithChapterCount( manga = mapManga( @@ -184,6 +190,7 @@ object MangaMapper { version, isSyncing, notes, + memo, ), chapterCount = totalCount, ) diff --git a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt index c230ad7d9..0b10eca1e 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.Flow import logcat.LogPriority import tachiyomi.core.common.util.system.logcat import tachiyomi.data.Database +import tachiyomi.data.MemoColumnAdapter import tachiyomi.data.StringListColumnAdapter import tachiyomi.data.UpdateStrategyColumnAdapter import tachiyomi.data.subscribeToList @@ -154,6 +155,7 @@ class MangaRepositoryImpl( dateAdded = it.dateAdded, updateStrategy = it.updateStrategy, version = it.version, + memo = it.memo, updateTitle = it.title.isNotBlank(), updateCover = !it.thumbnailUrl.isNullOrBlank(), updateDetails = it.initialized, @@ -191,6 +193,7 @@ class MangaRepositoryImpl( version = value.version, isSyncing = 0, notes = value.notes, + memo = value.memo?.let(MemoColumnAdapter::encode), ) } } diff --git a/data/src/main/sqldelight/tachiyomi/data/chapters.sq b/data/src/main/sqldelight/tachiyomi/data/chapters.sq index fbb774239..a1e6f3ab7 100644 --- a/data/src/main/sqldelight/tachiyomi/data/chapters.sq +++ b/data/src/main/sqldelight/tachiyomi/data/chapters.sq @@ -1,4 +1,5 @@ import kotlin.Boolean; +import kotlinx.serialization.json.JsonObject; CREATE TABLE chapters( _id INTEGER NOT NULL PRIMARY KEY, @@ -16,6 +17,7 @@ CREATE TABLE chapters( last_modified_at INTEGER NOT NULL DEFAULT 0, version INTEGER NOT NULL DEFAULT 0, is_syncing INTEGER NOT NULL DEFAULT 0, + memo BLOB AS JsonObject NOT NULL DEFAULT '{}', FOREIGN KEY(manga_id) REFERENCES mangas (_id) ON DELETE CASCADE ); @@ -98,12 +100,12 @@ SET is_syncing = 0 WHERE is_syncing = 1; insert: -INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0); +INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, memo) +VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :memo); insertReturningId: -INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing) -VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0) +INSERT INTO chapters(manga_id, url, name, scanlator, read, bookmark, last_page_read, chapter_number, source_order, date_fetch, date_upload, last_modified_at, version, is_syncing, memo) +VALUES (:mangaId, :url, :name, :scanlator, :read, :bookmark, :lastPageRead, :chapterNumber, :sourceOrder, :dateFetch, :dateUpload, 0, :version, 0, :memo) RETURNING _id; update: @@ -120,5 +122,6 @@ SET manga_id = coalesce(:mangaId, manga_id), date_fetch = coalesce(:dateFetch, date_fetch), date_upload = coalesce(:dateUpload, date_upload), version = coalesce(:version, version), - is_syncing = coalesce(:isSyncing, is_syncing) + is_syncing = coalesce(:isSyncing, is_syncing), + memo = coalesce(:memo, memo) WHERE _id = :chapterId; diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index 291291b68..65f647a9d 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -1,7 +1,8 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy; -import kotlin.collections.List; import kotlin.Boolean; import kotlin.String; +import kotlin.collections.List; +import kotlinx.serialization.json.JsonObject; CREATE TABLE mangas( _id INTEGER NOT NULL PRIMARY KEY, @@ -28,7 +29,8 @@ CREATE TABLE mangas( favorite_modified_at INTEGER, version INTEGER NOT NULL DEFAULT 0, is_syncing INTEGER NOT NULL DEFAULT 0, - notes TEXT NOT NULL DEFAULT "" + notes TEXT NOT NULL DEFAULT "", + memo BLOB AS JsonObject NOT NULL DEFAULT '{}' ); CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; @@ -194,8 +196,8 @@ AND ( ); insertReturningId: -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, calculate_interval, last_modified_at, version, notes) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes) +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, calculate_interval, last_modified_at, version, notes, memo) +VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, 0, :version, :notes, :memo) RETURNING _id; update: @@ -221,7 +223,8 @@ UPDATE mangas SET calculate_interval = coalesce(:calculateInterval, calculate_interval), version = coalesce(:version, version), is_syncing = coalesce(:isSyncing, is_syncing), - notes = coalesce(:notes, notes) + notes = coalesce(:notes, notes), + memo = coalesce(:memo, memo) WHERE _id = :mangaId; insertNetworkManga { @@ -229,12 +232,12 @@ insertNetworkManga { 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, calculate_interval, last_modified_at, version + update_strategy, calculate_interval, last_modified_at, version, memo ) SELECT :source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, - :updateStrategy, :calculateInterval, 0, :version + :updateStrategy, :calculateInterval, 0, :version, :memo WHERE NOT EXISTS(SELECT 0 FROM mangas WHERE source = :source AND url = :url); -- Update the relevant details if applicable and not favorite @@ -248,7 +251,8 @@ insertNetworkManga { genre = CASE WHEN :updateDetails THEN :genre ELSE genre END, status = CASE WHEN :updateDetails THEN :status ELSE status END, update_strategy = CASE WHEN :updateDetails THEN :updateStrategy ELSE update_strategy END, - initialized = :updateDetails + initialized = :updateDetails, + memo = :memo WHERE source = :source AND url = :url AND favorite = 0; diff --git a/data/src/main/sqldelight/tachiyomi/migrations/12.sqm b/data/src/main/sqldelight/tachiyomi/migrations/12.sqm new file mode 100644 index 000000000..852497ad6 --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/12.sqm @@ -0,0 +1,2 @@ +ALTER TABLE mangas ADD COLUMN memo BLOB NOT NULL DEFAULT '{}'; +ALTER TABLE chapters ADD COLUMN memo BLOB NOT NULL DEFAULT '{}'; diff --git a/domain/src/main/java/mihon/domain/extension/interactor/GetExtensionStoreCountAsFlow.kt b/domain/src/main/java/mihon/domain/extension/interactor/GetExtensionStoreCountAsFlow.kt index 2871e7f67..9fa4baab7 100644 --- a/domain/src/main/java/mihon/domain/extension/interactor/GetExtensionStoreCountAsFlow.kt +++ b/domain/src/main/java/mihon/domain/extension/interactor/GetExtensionStoreCountAsFlow.kt @@ -1,5 +1,6 @@ package mihon.domain.extension.interactor +import kotlinx.serialization.json.Json import mihon.domain.extension.repository.ExtensionStoreRepository class GetExtensionStoreCountAsFlow( diff --git a/domain/src/main/java/tachiyomi/domain/chapter/model/Chapter.kt b/domain/src/main/java/tachiyomi/domain/chapter/model/Chapter.kt index f993e0256..29919319b 100644 --- a/domain/src/main/java/tachiyomi/domain/chapter/model/Chapter.kt +++ b/domain/src/main/java/tachiyomi/domain/chapter/model/Chapter.kt @@ -1,5 +1,8 @@ package tachiyomi.domain.chapter.model +import kotlinx.serialization.json.JsonObject +import mihon.core.common.extensions.EMPTY + data class Chapter( val id: Long, val mangaId: Long, @@ -15,6 +18,7 @@ data class Chapter( val scanlator: String?, val lastModifiedAt: Long, val version: Long, + val memo: JsonObject, ) { val isRecognizedNumber: Boolean get() = chapterNumber >= 0f @@ -45,6 +49,7 @@ data class Chapter( scanlator = null, lastModifiedAt = 0, version = 1, + memo = JsonObject.EMPTY, ) } } diff --git a/domain/src/main/java/tachiyomi/domain/chapter/model/ChapterUpdate.kt b/domain/src/main/java/tachiyomi/domain/chapter/model/ChapterUpdate.kt index 5a9193dc6..01f568523 100644 --- a/domain/src/main/java/tachiyomi/domain/chapter/model/ChapterUpdate.kt +++ b/domain/src/main/java/tachiyomi/domain/chapter/model/ChapterUpdate.kt @@ -1,5 +1,7 @@ package tachiyomi.domain.chapter.model +import kotlinx.serialization.json.JsonObject + data class ChapterUpdate( val id: Long, val mangaId: Long? = null, @@ -14,6 +16,7 @@ data class ChapterUpdate( val chapterNumber: Double? = null, val scanlator: String? = null, val version: Long? = null, + val memo: JsonObject? = null, ) fun Chapter.toChapterUpdate(): ChapterUpdate { @@ -31,5 +34,6 @@ fun Chapter.toChapterUpdate(): ChapterUpdate { chapterNumber, scanlator, version, + memo, ) } diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt index 2402fddb7..5fbca9446 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt @@ -3,6 +3,8 @@ package tachiyomi.domain.manga.model import androidx.compose.runtime.Immutable import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.UpdateStrategy +import kotlinx.serialization.json.JsonObject +import mihon.core.common.extensions.EMPTY import tachiyomi.core.common.preference.TriState import java.io.Serializable import java.time.Instant @@ -33,6 +35,7 @@ data class Manga( val favoriteModifiedAt: Long?, val version: Long, val notes: String, + val memo: JsonObject, ) : Serializable { val expectedNextUpdate: Instant? @@ -128,6 +131,7 @@ data class Manga( favoriteModifiedAt = null, version = 0L, notes = "", + memo = JsonObject.EMPTY, ) } } diff --git a/domain/src/main/java/tachiyomi/domain/manga/model/MangaUpdate.kt b/domain/src/main/java/tachiyomi/domain/manga/model/MangaUpdate.kt index 67882d848..50545f468 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/model/MangaUpdate.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/model/MangaUpdate.kt @@ -1,6 +1,7 @@ package tachiyomi.domain.manga.model import eu.kanade.tachiyomi.source.model.UpdateStrategy +import kotlinx.serialization.json.JsonObject data class MangaUpdate( val id: Long, @@ -25,6 +26,7 @@ data class MangaUpdate( val initialized: Boolean? = null, val version: Long? = null, val notes: String? = null, + val memo: JsonObject? = null, ) fun Manga.toMangaUpdate(): MangaUpdate { @@ -51,5 +53,6 @@ fun Manga.toMangaUpdate(): MangaUpdate { initialized = initialized, version = version, notes = notes, + memo = memo, ) } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt index a377c36ea..2424957f8 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/MangasPage.kt @@ -1,3 +1,19 @@ package eu.kanade.tachiyomi.source.model -data class MangasPage(val mangas: List, val hasNextPage: Boolean) +class MangasPage(val mangas: List, val hasNextPage: Boolean) { + + @Deprecated("MangasPage is now a regular class") + operator fun component1(): List = mangas + + @Deprecated("MangasPage is now a regular class") + operator fun component2(): Boolean = hasNextPage + + @Deprecated("MangasPage is now a regular class") + fun copy( + mangas: List = this.mangas, + hasNextPage: Boolean = this.hasNextPage, + ): MangasPage = MangasPage( + mangas = mangas, + hasNextPage = hasNextPage, + ) +} diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt index 31d05d87e..9ed836af6 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapter.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.source.model +import kotlinx.serialization.json.JsonObject import java.io.Serializable interface SChapter : Serializable { @@ -10,12 +11,25 @@ interface SChapter : Serializable { var name: String - var date_upload: Long - var chapter_number: Float var scanlator: String? + var date_upload: Long + + /** + * Extra metadata associated with the chapter. + * + * The JSON object is not visible to users and intended for internal or source-specific + * purposes. Apps may define their own namespaced keys (e.g., `"mihon.*"`) for sources to populate. + * + * This allows apps to attach and ask for custom information without affecting the visible + * chapter data. + * + * @since tachiyomix 1.6 + */ + var memo: JsonObject + fun copyFrom(other: SChapter) { name = other.name url = other.url diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt index 4832faa70..597271997 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SChapterImpl.kt @@ -2,15 +2,19 @@ package eu.kanade.tachiyomi.source.model +import kotlinx.serialization.json.JsonObject + class SChapterImpl : SChapter { override lateinit var url: String override lateinit var name: String - override var date_upload: Long = 0 - override var chapter_number: Float = -1f override var scanlator: String? = null + + override var date_upload: Long = 0 + + override var memo: JsonObject = JsonObject(emptyMap()) } diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt index 5b76602e6..2f0c8852c 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SManga.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.source.model +import kotlinx.serialization.json.JsonObject import java.io.Serializable interface SManga : Serializable { @@ -10,22 +11,35 @@ interface SManga : Serializable { var title: String + var thumbnail_url: String? + var artist: String? var author: String? + var status: Int + var description: String? var genre: String? - var status: Int - - var thumbnail_url: String? - var update_strategy: UpdateStrategy var initialized: Boolean + /** + * Extra metadata associated with the manga. + * + * The JSON object is not visible to users and intended for internal or source-specific + * purposes. Apps may define their own namespaced keys (e.g., `"mihon.*"`) for sources to populate. + * + * This allows apps to attach and ask for custom information without affecting the visible + * manga data. + * + * @since tachiyomix 1.6 + */ + var memo: JsonObject + fun getGenres(): List? { if (genre.isNullOrBlank()) return null return genre?.split(", ")?.map { it.trim() }?.filterNot { it.isBlank() }?.distinct() diff --git a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt index df0fc3312..248cf6163 100644 --- a/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt +++ b/source-api/src/commonMain/kotlin/eu/kanade/tachiyomi/source/model/SMangaImpl.kt @@ -2,25 +2,29 @@ package eu.kanade.tachiyomi.source.model +import kotlinx.serialization.json.JsonObject + class SMangaImpl : SManga { override lateinit var url: String override lateinit var title: String + override var thumbnail_url: String? = null + override var artist: String? = null override var author: String? = null + override var status: Int = 0 + override var description: String? = null override var genre: String? = null - override var status: Int = 0 - - override var thumbnail_url: String? = null - override var update_strategy: UpdateStrategy = UpdateStrategy.ALWAYS_UPDATE override var initialized: Boolean = false + + override var memo: JsonObject = JsonObject(emptyMap()) }