diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 202aca121..607ae02db 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -36,7 +36,7 @@ android { defaultConfig { applicationId = "app.mihon" - versionCode = 31 + versionCode = 32 versionName = "0.20.4" buildConfigField("boolean", "TELEMETRY_INCLUDED", "${Config.includeTelemetry}") diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt index 1e7c6010a..d998e0c30 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt @@ -48,15 +48,10 @@ class UpdateManga( } suspend fun awaitUpdateFavorite(mangaId: Long, favorite: Boolean): Boolean { - val dateAdded = when (favorite) { - true -> Clock.System.now().toEpochMilliseconds() - false -> 0 + val update = when (favorite) { + true -> MangaUpdate(mangaId) { favoriteAt = Clock.System.now().toEpochMilliseconds() } + false -> MangaUpdate(mangaId) { favoriteAt = null } } - return mangaRepository.update( - MangaUpdate(mangaId) { - this.favorite = favorite - this.dateAdded = dateAdded - }, - ) + return mangaRepository.update(update) } } 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 a01e92123..b22a29bd6 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 @@ -88,12 +88,11 @@ private fun Manga.toBackupManga() = thumbnailUrl = this.thumbnailUrl, favorite = this.favorite, source = this.source, - dateAdded = this.dateAdded, + dateAdded = this.favoriteAt ?: 0L, viewer = (this.viewerFlags.toInt() and ReadingMode.MASK), viewer_flags = this.viewerFlags.toInt(), chapterFlags = this.chapterFlags.toInt(), updateStrategy = this.updateStrategy, - favoriteModifiedAt = this.favoriteModifiedAt, notes = this.notes, initialized = this.initialized, memo = this.memo.toByteArray(), 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 428958a5a..95a126171 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 @@ -57,13 +57,19 @@ class BackupManga( genre = this@BackupManga.genre, status = this@BackupManga.status.toLong(), thumbnailUrl = this@BackupManga.thumbnailUrl, - favorite = this@BackupManga.favorite, source = this@BackupManga.source, - dateAdded = this@BackupManga.dateAdded, + // For libraries older than dateAdded, the favorite timestamp is the only record of when + // an entry was added. It was written by a trigger in seconds, unlike dateAdded. + favoriteAt = if (this@BackupManga.favorite) { + this@BackupManga.dateAdded.takeIf { it != 0L } + ?: this@BackupManga.favoriteModifiedAt?.times(1000) + ?: 0L + } else { + null + }, viewerFlags = (this@BackupManga.viewer_flags ?: this@BackupManga.viewer).toLong(), chapterFlags = this@BackupManga.chapterFlags.toLong(), updateStrategy = this@BackupManga.updateStrategy, - favoriteModifiedAt = this@BackupManga.favoriteModifiedAt, notes = this@BackupManga.notes, initialized = this@BackupManga.initialized, memo = this@BackupManga.memo.toJsonObject(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt index 5b4e771de..bb5814957 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceViewModel.kt @@ -236,17 +236,13 @@ class BrowseSourceViewModel( val update = if (manga.favorite) { val coverLastModified = manga.removeCovers(coverCache).coverLastModified MangaUpdate(manga.id) { - favorite = false - dateAdded = 0 + favoriteAt = null if (coverLastModified != manga.coverLastModified) this.coverLastModified = coverLastModified } } else { setMangaDefaultChapterFlags.await(manga) addTracks.bindEnhancedTrackers(manga, sourceManager.getOrStub(manga.source)) - MangaUpdate(manga.id) { - favorite = true - dateAdded = Clock.System.now().toEpochMilliseconds() - } + MangaUpdate(manga.id) { favoriteAt = Clock.System.now().toEpochMilliseconds() } } updateManga.await(update) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt index bf42d0878..9d992eed6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryViewModel.kt @@ -338,7 +338,7 @@ class LibraryViewModel( manga1.libraryManga.chapterFetchedAt.compareTo(manga2.libraryManga.chapterFetchedAt) } LibrarySort.Type.DateAdded -> { - manga1.libraryManga.manga.dateAdded.compareTo(manga2.libraryManga.manga.dateAdded) + compareValues(manga1.libraryManga.manga.favoriteAt, manga2.libraryManga.manga.favoriteAt) } LibrarySort.Type.TrackerMean -> { val item1Score = trackerScores[manga1.id] ?: defaultTrackerScoreSortValue @@ -572,7 +572,7 @@ class LibraryViewModel( val toDelete = mangas.map { it.removeCovers(coverCache) MangaUpdate(it.id) { - favorite = false + favoriteAt = null } } updateManga.awaitAll(toDelete) diff --git a/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt b/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt index 630a0c9ec..cfec40f57 100644 --- a/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt +++ b/app/src/main/java/mihon/domain/migration/usecases/MigrateMangaUseCase.kt @@ -111,15 +111,13 @@ class MigrateMangaUseCase( } val currentMangaUpdate = MangaUpdate(current.id) { - favorite = false - dateAdded = 0 + favoriteAt = null } .takeIf { replace } val targetMangaUpdate = MangaUpdate(target.id) { - favorite = true + favoriteAt = current.favoriteAt?.takeIf { replace } ?: Clock.System.now().toEpochMilliseconds() chapterFlags = current.chapterFlags viewerFlags = current.viewerFlags - dateAdded = if (replace) current.dateAdded else Clock.System.now().toEpochMilliseconds() if (MigrationFlag.NOTES in flags) notes = current.notes } diff --git a/app/src/main/java/mihon/feature/library/QueryNodeExtensions.kt b/app/src/main/java/mihon/feature/library/QueryNodeExtensions.kt index 40a14ec6c..24d648f6e 100644 --- a/app/src/main/java/mihon/feature/library/QueryNodeExtensions.kt +++ b/app/src/main/java/mihon/feature/library/QueryNodeExtensions.kt @@ -117,7 +117,7 @@ private fun ComparisonQueryNode.matches(item: LibraryItem): Boolean { val match = when (field) { ComparisonField.ID -> value.toLongOrNull()?.let { queryComparator.apply(manga.id, it) } - ComparisonField.DATE_ADDED -> compareDates(manga.dateAdded, value) + ComparisonField.DATE_ADDED -> manga.favoriteAt?.let { compareDates(it, value) } ComparisonField.FETCH_INTERVAL -> value.toIntOrNull() ?.let { queryComparator.apply(abs(manga.fetchInterval), it) } diff --git a/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt b/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt index a2c3342ad..1f9c8988a 100644 --- a/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/backup/RestoreRepositoryImpl.kt @@ -72,11 +72,11 @@ class RestoreRepositoryImpl( private fun mergeManga(manga: Manga, dbManga: Manga): Manga { val details = if (dbManga.initialized || !manga.initialized) dbManga else manga return dbManga.copy( - favorite = dbManga.favorite || manga.favorite, - dateAdded = listOfNotNull( - dbManga.dateAdded.takeIf { it > 0 }, - manga.dateAdded.takeIf { it > 0 }, - ).minOrNull() ?: 0L, + favoriteAt = if (dbManga.favorite || manga.favorite) { + listOfNotNull(dbManga.favoriteAt, manga.favoriteAt).filter { it > 0 }.minOrNull() ?: 0L + } else { + null + }, title = details.title, artist = details.artist, author = details.author, @@ -93,6 +93,7 @@ class RestoreRepositoryImpl( private suspend fun updateManga(manga: Manga): Manga { database.mangasQueries.updateFromBackup( + favoriteAt = manga.favoriteAt, artist = manga.artist, author = manga.author, description = manga.description, @@ -100,13 +101,11 @@ class RestoreRepositoryImpl( title = manga.title, status = manga.status, thumbnailUrl = manga.thumbnailUrl, - favorite = manga.favorite, lastUpdate = manga.lastUpdate, initialized = manga.initialized, viewer = manga.viewerFlags, chapterFlags = manga.chapterFlags, coverLastModified = manga.coverLastModified, - dateAdded = manga.dateAdded, mangaId = manga.id, updateStrategy = manga.updateStrategy, notes = manga.notes, @@ -117,6 +116,7 @@ class RestoreRepositoryImpl( private suspend fun insertManga(manga: Manga): Long { return database.mangasQueries.insertReturningId( + favoriteAt = manga.favoriteAt, source = manga.source, url = manga.url, artist = manga.artist, @@ -126,7 +126,6 @@ class RestoreRepositoryImpl( title = manga.title, status = manga.status, thumbnailUrl = manga.thumbnailUrl, - favorite = manga.favorite, lastUpdate = manga.lastUpdate, nextUpdate = 0L, calculateInterval = 0L, @@ -134,7 +133,6 @@ class RestoreRepositoryImpl( viewerFlags = manga.viewerFlags, chapterFlags = manga.chapterFlags, coverLastModified = manga.coverLastModified, - dateAdded = manga.dateAdded, updateStrategy = manga.updateStrategy, notes = manga.notes, memo = manga.memo, diff --git a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt index 4699d9c7f..1a77acaa7 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaMapper.kt @@ -18,27 +18,24 @@ object MangaMapper { title: String, status: Long, thumbnailUrl: String?, - favorite: Boolean, lastUpdate: Long?, nextUpdate: Long?, initialized: Boolean, viewerFlags: Long, chapterFlags: Long, coverLastModified: Long, - dateAdded: Long, updateStrategy: UpdateStrategy, calculateInterval: Long, - favoriteModifiedAt: Long?, notes: String, memo: JsonObject, + favoriteAt: Long?, ): Manga = Manga( id = id, source = source, - favorite = favorite, lastUpdate = lastUpdate ?: 0, nextUpdate = nextUpdate ?: 0, fetchInterval = calculateInterval.toInt(), - dateAdded = dateAdded, + favoriteAt = favoriteAt, viewerFlags = viewerFlags, chapterFlags = chapterFlags, coverLastModified = coverLastModified, @@ -52,7 +49,6 @@ object MangaMapper { thumbnailUrl = thumbnailUrl, updateStrategy = updateStrategy, initialized = initialized, - favoriteModifiedAt = favoriteModifiedAt, notes = notes, memo = memo, ) @@ -68,19 +64,17 @@ object MangaMapper { title: String, status: Long, thumbnailUrl: String?, - favorite: Boolean, lastUpdate: Long?, nextUpdate: Long?, initialized: Boolean, viewerFlags: Long, chapterFlags: Long, coverLastModified: Long, - dateAdded: Long, updateStrategy: UpdateStrategy, calculateInterval: Long, - favoriteModifiedAt: Long?, notes: String, memo: JsonObject, + favoriteAt: Long?, totalCount: Long, readCount: Double, latestUpload: Long, @@ -100,19 +94,17 @@ object MangaMapper { title, status, thumbnailUrl, - favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, - dateAdded, updateStrategy, calculateInterval, - favoriteModifiedAt, notes, memo, + favoriteAt, ), categories = categories.split(",").map { it.toLong() }, totalChapters = totalCount, @@ -134,19 +126,17 @@ object MangaMapper { title: String, status: Long, thumbnailUrl: String?, - favorite: Boolean, lastUpdate: Long?, nextUpdate: Long?, initialized: Boolean, viewerFlags: Long, chapterFlags: Long, coverLastModified: Long, - dateAdded: Long, updateStrategy: UpdateStrategy, calculateInterval: Long, - favoriteModifiedAt: Long?, notes: String, memo: JsonObject, + favoriteAt: Long?, totalCount: Long, ): MangaWithChapterCount = MangaWithChapterCount( manga = mapManga( @@ -160,19 +150,17 @@ object MangaMapper { title, status, thumbnailUrl, - favorite, lastUpdate, nextUpdate, initialized, viewerFlags, chapterFlags, coverLastModified, - dateAdded, updateStrategy, calculateInterval, - favoriteModifiedAt, notes, memo, + favoriteAt, ), 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 d631b70f4..4253ec19a 100644 --- a/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt +++ b/data/src/main/java/tachiyomi/data/manga/MangaRepositoryImpl.kt @@ -198,7 +198,7 @@ class MangaRepositoryImpl( title = it.title, status = it.status, thumbnailUrl = it.thumbnailUrl, - favorite = it.favorite, + favoriteAt = it.favoriteAt, lastUpdate = it.lastUpdate, nextUpdate = it.nextUpdate, calculateInterval = it.fetchInterval.toLong(), @@ -206,7 +206,6 @@ class MangaRepositoryImpl( viewerFlags = it.viewerFlags, chapterFlags = it.chapterFlags, coverLastModified = it.coverLastModified, - dateAdded = it.dateAdded, updateStrategy = it.updateStrategy, memo = it.memo, updateTitle = it.title.isNotBlank(), @@ -247,14 +246,14 @@ class MangaRepositoryImpl( mangaUpdates.forEach { value -> with(value) { database.mangasQueries.update( - favorite = favorite, + favoriteAtSet = isSet(::favoriteAt), + favoriteAt = favoriteAt, lastUpdate = lastUpdate, nextUpdate = nextUpdate, calculateInterval = fetchInterval?.toLong(), viewer = viewerFlags, chapterFlags = chapterFlags, coverLastModified = coverLastModified, - dateAdded = dateAdded, mangaId = id, notes = notes, ) diff --git a/data/src/main/sqldelight/tachiyomi/data/mangas.sq b/data/src/main/sqldelight/tachiyomi/data/mangas.sq index 1f44449f8..16e715a36 100644 --- a/data/src/main/sqldelight/tachiyomi/data/mangas.sq +++ b/data/src/main/sqldelight/tachiyomi/data/mangas.sq @@ -15,33 +15,23 @@ CREATE TABLE mangas( title TEXT NOT NULL, status INTEGER NOT NULL, thumbnail_url TEXT, - favorite INTEGER AS Boolean NOT NULL, last_update INTEGER, next_update INTEGER, initialized INTEGER AS Boolean NOT NULL, viewer INTEGER NOT NULL, chapter_flags INTEGER NOT NULL, cover_last_modified INTEGER NOT NULL, - date_added INTEGER NOT NULL, update_strategy INTEGER AS UpdateStrategy NOT NULL DEFAULT 0, calculate_interval INTEGER DEFAULT 0 NOT NULL, - favorite_modified_at INTEGER, notes TEXT NOT NULL DEFAULT "", - memo BLOB AS JsonObject NOT NULL DEFAULT '{}' + memo BLOB AS JsonObject NOT NULL DEFAULT '{}', + favorite_at INTEGER ); -CREATE INDEX library_favorite_index ON mangas(favorite) WHERE favorite = 1; +CREATE INDEX library_favorite_at_index ON mangas(favorite_at) WHERE favorite_at IS NOT NULL; CREATE INDEX mangas_url_index ON mangas(url); CREATE INDEX idx_mangas_source ON mangas(source); -CREATE TRIGGER update_last_favorited_at_mangas -AFTER UPDATE OF favorite ON mangas -BEGIN - UPDATE mangas - SET favorite_modified_at = strftime('%s', 'now') - WHERE _id = new._id; -END; - getMangaById: SELECT * FROM mangas @@ -58,12 +48,12 @@ LIMIT 1; getFavorites: SELECT * FROM mangas -WHERE favorite = 1; +WHERE favorite_at IS NOT NULL; getReadMangaNotInLibrary: SELECT * FROM mangas -WHERE favorite = 0 AND _id IN ( +WHERE favorite_at IS NULL AND _id IN ( SELECT DISTINCT chapters.manga_id FROM chapters WHERE read = 1 OR last_page_read != 0 @@ -77,23 +67,18 @@ getAllMangaSourceAndUrl: SELECT source, url FROM mangas; -getMangasWithFavoriteTimestamp: -SELECT * -FROM mangas -WHERE favorite_modified_at IS NOT NULL; - getSourceIdWithFavoriteCount: SELECT source, count(*) FROM mangas -WHERE favorite = 1 +WHERE favorite_at IS NOT NULL GROUP BY source; getFavoriteBySourceId: SELECT * FROM mangas -WHERE favorite = 1 +WHERE favorite_at IS NOT NULL AND source = :sourceId; getDuplicateLibraryManga: @@ -113,7 +98,7 @@ duplicates AS ( FROM mangas M LEFT JOIN track_dupes D ON D.manga_id = _id - WHERE favorite = 1 + WHERE favorite_at IS NOT NULL AND _id != :id AND (lower(title) LIKE '%' || lower(:title) || '%' OR D.manga_id IS NOT NULL) ), @@ -141,7 +126,7 @@ getUpcomingManga: SELECT * FROM mangas WHERE next_update >= :startOfDay -AND favorite = 1 +AND favorite_at IS NOT NULL AND status IN :statuses AND ( -- includedEmpty being true expresses "don't care" state and bypasses the membership filter @@ -179,12 +164,12 @@ SET viewer = 0; getSourceIdsWithNonLibraryManga: SELECT source, COUNT(*) AS manga_count FROM mangas -WHERE favorite = 0 +WHERE favorite_at IS NULL GROUP BY source; deleteNonLibraryManga: DELETE FROM mangas -WHERE favorite = 0 +WHERE favorite_at IS NULL AND source IN :sourceIds AND ( :keepReadManga = 0 @@ -197,19 +182,19 @@ 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, notes, memo) -VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, :updateStrategy, :calculateInterval, :notes, :memo) +INSERT INTO mangas(source, url, artist, author, description, genre, title, status, thumbnail_url, favorite_at, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, update_strategy, calculate_interval, notes, memo) +VALUES (:source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :updateStrategy, :calculateInterval, :notes, :memo) RETURNING _id; update: UPDATE mangas SET - favorite = coalesce(:favorite, favorite), + -- NULL is a real value here (not in the library), which coalesce cannot write + favorite_at = CASE WHEN :favoriteAtSet THEN :favoriteAt ELSE favorite_at END, last_update = coalesce(:lastUpdate, last_update), next_update = coalesce(:nextUpdate, next_update), viewer = coalesce(:viewer, viewer), chapter_flags = coalesce(:chapterFlags, chapter_flags), cover_last_modified = coalesce(:coverLastModified, cover_last_modified), - date_added = coalesce(:dateAdded, date_added), calculate_interval = coalesce(:calculateInterval, calculate_interval), notes = coalesce(:notes, notes) WHERE _id = :mangaId; @@ -223,13 +208,12 @@ UPDATE mangas SET title = coalesce(:title, title), status = coalesce(:status, status), thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url), - favorite = coalesce(:favorite, favorite), + favorite_at = :favoriteAt, last_update = coalesce(:lastUpdate, last_update), initialized = coalesce(:initialized, initialized), viewer = coalesce(:viewer, viewer), chapter_flags = coalesce(:chapterFlags, chapter_flags), cover_last_modified = coalesce(:coverLastModified, cover_last_modified), - date_added = coalesce(:dateAdded, date_added), update_strategy = coalesce(:updateStrategy, update_strategy), notes = coalesce(:notes, notes), memo = coalesce(:memo, memo) @@ -253,13 +237,13 @@ WHERE _id = :mangaId; insertNetworkManga { -- Insert the manga if it doesn't exist already 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, + source, url, artist, author, description, genre, title, status, thumbnail_url, favorite_at, + last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, update_strategy, calculate_interval, memo ) SELECT - :source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favorite, - :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :dateAdded, + :source, :url, :artist, :author, :description, :genre, :title, :status, :thumbnailUrl, :favoriteAt, + :lastUpdate, :nextUpdate, :initialized, :viewerFlags, :chapterFlags, :coverLastModified, :updateStrategy, :calculateInterval, :memo WHERE NOT EXISTS(SELECT 0 FROM mangas WHERE source = :source AND url = :url); @@ -278,7 +262,7 @@ insertNetworkManga { memo = :memo WHERE source = :source AND url = :url - AND favorite = 0; + AND favorite_at IS NULL; -- Finally return the manga SELECT * diff --git a/data/src/main/sqldelight/tachiyomi/migrations/16.sqm b/data/src/main/sqldelight/tachiyomi/migrations/16.sqm new file mode 100644 index 000000000..4b839aa6c --- /dev/null +++ b/data/src/main/sqldelight/tachiyomi/migrations/16.sqm @@ -0,0 +1,113 @@ +-- Merge favorite, date_added and favorite_modified_at into favorite_at + +DROP VIEW historyView; +DROP VIEW libraryView; +DROP VIEW updatesView; + +DROP TRIGGER update_last_favorited_at_mangas; +DROP INDEX library_favorite_index; + +ALTER TABLE mangas ADD COLUMN favorite_at INTEGER; + +UPDATE mangas +SET favorite_at = coalesce(nullif(date_added, 0), favorite_modified_at * 1000, 0) +WHERE favorite = 1; + +ALTER TABLE mangas DROP COLUMN favorite; +ALTER TABLE mangas DROP COLUMN date_added; +ALTER TABLE mangas DROP COLUMN favorite_modified_at; + +CREATE INDEX library_favorite_at_index ON mangas(favorite_at) WHERE favorite_at IS NOT NULL; + +CREATE VIEW libraryView AS +SELECT + M.*, + coalesce(C.total, 0) AS totalCount, + coalesce(C.readCount, 0) AS readCount, + coalesce(C.latestUpload, 0) AS latestUpload, + coalesce(C.fetchedAt, 0) AS chapterFetchedAt, + coalesce(C.lastRead, 0) AS lastRead, + coalesce(C.bookmarkCount, 0) AS bookmarkCount, + coalesce(MC.categories, '0') AS categories +FROM mangas M +LEFT JOIN ( + SELECT + chapters.manga_id, + count(*) AS total, + sum(read) AS readCount, + coalesce(max(chapters.date_upload), 0) AS latestUpload, + coalesce(max(history.last_read), 0) AS lastRead, + coalesce(max(chapters.date_fetch), 0) AS fetchedAt, + sum(chapters.bookmark) AS bookmarkCount + FROM chapters + LEFT JOIN excluded_scanlators + ON chapters.manga_id = excluded_scanlators.manga_id + AND chapters.scanlator = excluded_scanlators.scanlator + LEFT JOIN history + ON chapters._id = history.chapter_id + WHERE excluded_scanlators.scanlator IS NULL + GROUP BY chapters.manga_id +) AS C +ON M._id = C.manga_id +LEFT JOIN ( + SELECT manga_id, group_concat(category_id) AS categories + FROM mangas_categories + GROUP BY manga_id +) AS MC +ON MC.manga_id = M._id +WHERE M.favorite_at IS NOT NULL; + +CREATE VIEW updatesView AS +SELECT + mangas._id AS mangaId, + mangas.title AS mangaTitle, + chapters._id AS chapterId, + chapters.name AS chapterName, + chapters.scanlator, + chapters.url AS chapterUrl, + chapters.read, + chapters.bookmark, + chapters.last_page_read, + mangas.source, + mangas.favorite_at IS NOT NULL AS favorite, + mangas.thumbnail_url AS thumbnailUrl, + mangas.cover_last_modified AS coverLastModified, + chapters.date_upload AS dateUpload, + chapters.date_fetch AS datefetch, + excluded_scanlators.scanlator AS excludedScanlator +FROM mangas JOIN chapters +ON mangas._id = chapters.manga_id +LEFT JOIN excluded_scanlators +ON mangas._id = excluded_scanlators.manga_id +AND chapters.scanlator = excluded_scanlators.scanlator +WHERE favorite_at IS NOT NULL +AND date_fetch > favorite_at +ORDER BY date_fetch DESC; + +CREATE VIEW historyView AS +SELECT + history._id AS id, + mangas._id AS mangaId, + chapters._id AS chapterId, + mangas.title, + mangas.thumbnail_url AS thumbnailUrl, + mangas.source, + mangas.favorite_at IS NOT NULL AS favorite, + mangas.cover_last_modified, + chapters.chapter_number AS chapterNumber, + history.last_read AS readAt, + history.time_read AS readDuration, + max_last_read.last_read AS maxReadAt, + max_last_read.chapter_id AS maxReadAtChapterId +FROM mangas +JOIN chapters +ON mangas._id = chapters.manga_id +JOIN history +ON chapters._id = history.chapter_id +JOIN ( + SELECT chapters.manga_id,chapters._id AS chapter_id, MAX(history.last_read) AS last_read + FROM chapters JOIN history + ON chapters._id = history.chapter_id + GROUP BY chapters.manga_id +) AS max_last_read +ON chapters.manga_id = max_last_read.manga_id; diff --git a/data/src/main/sqldelight/tachiyomi/view/historyView.sq b/data/src/main/sqldelight/tachiyomi/view/historyView.sq index bdaeb5259..f037c4329 100644 --- a/data/src/main/sqldelight/tachiyomi/view/historyView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/historyView.sq @@ -6,7 +6,7 @@ SELECT mangas.title, mangas.thumbnail_url AS thumbnailUrl, mangas.source, - mangas.favorite, + mangas.favorite_at IS NOT NULL AS favorite, mangas.cover_last_modified, chapters.chapter_number AS chapterNumber, history.last_read AS readAt, diff --git a/data/src/main/sqldelight/tachiyomi/view/libraryView.sq b/data/src/main/sqldelight/tachiyomi/view/libraryView.sq index 8ce19ae1d..1d9c98edf 100644 --- a/data/src/main/sqldelight/tachiyomi/view/libraryView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/libraryView.sq @@ -34,7 +34,7 @@ LEFT JOIN ( GROUP BY manga_id ) AS MC ON MC.manga_id = M._id -WHERE M.favorite = 1; +WHERE M.favorite_at IS NOT NULL; library: SELECT * diff --git a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq index 5995b1bed..b2c8861df 100644 --- a/data/src/main/sqldelight/tachiyomi/view/updatesView.sq +++ b/data/src/main/sqldelight/tachiyomi/view/updatesView.sq @@ -10,7 +10,7 @@ SELECT chapters.bookmark, chapters.last_page_read, mangas.source, - mangas.favorite, + mangas.favorite_at IS NOT NULL AS favorite, mangas.thumbnail_url AS thumbnailUrl, mangas.cover_last_modified AS coverLastModified, chapters.date_upload AS dateUpload, @@ -21,8 +21,8 @@ ON mangas._id = chapters.manga_id LEFT JOIN excluded_scanlators ON mangas._id = excluded_scanlators.manga_id AND chapters.scanlator = excluded_scanlators.scanlator -WHERE favorite = 1 -AND date_fetch > date_added +WHERE favorite_at IS NOT NULL +AND date_fetch > favorite_at ORDER BY date_fetch DESC; getRecentUpdates: 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 bc3ee0584..be790b87a 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/model/Manga.kt @@ -19,11 +19,10 @@ import java.io.Serializable as JavaSerializable data class Manga( val id: Long, val source: Long, - val favorite: Boolean, val lastUpdate: Long, val nextUpdate: Long, val fetchInterval: Int, - val dateAdded: Long, + val favoriteAt: Long?, val viewerFlags: Long, val chapterFlags: Long, val coverLastModified: Long, @@ -37,11 +36,13 @@ data class Manga( val thumbnailUrl: String?, val updateStrategy: UpdateStrategy, val initialized: Boolean, - val favoriteModifiedAt: Long?, val notes: String, val memo: JsonObject, ) : JavaSerializable { + val favorite: Boolean + get() = favoriteAt != null + val expectedNextUpdate: Instant? get() = nextUpdate .takeIf { status != SManga.COMPLETED.toLong() } @@ -115,11 +116,10 @@ data class Manga( url = "", title = "", source = -1L, - favorite = false, lastUpdate = 0L, nextUpdate = 0L, fetchInterval = 0, - dateAdded = 0L, + favoriteAt = null, viewerFlags = 0L, chapterFlags = 0L, coverLastModified = 0L, @@ -131,7 +131,6 @@ data class Manga( thumbnailUrl = null, updateStrategy = UpdateStrategy.ALWAYS_UPDATE, initialized = false, - favoriteModifiedAt = null, 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 d2a92196c..8fa6ccfa8 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/model/MangaUpdate.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/model/MangaUpdate.kt @@ -3,11 +3,10 @@ package tachiyomi.domain.manga.model import mihon.domain.common.PartialUpdate class MangaUpdate(val id: Long, block: MangaUpdate.() -> Unit) : PartialUpdate() { - var favorite: Boolean? by field(null) + var favoriteAt: Long? by field(null) var lastUpdate: Long? by field(null) var nextUpdate: Long? by field(null) var fetchInterval: Int? by field(null) - var dateAdded: Long? by field(null) var viewerFlags: Long? by field(null) var chapterFlags: Long? by field(null) var coverLastModified: Long? by field(null)