Store every date in the database as an Instant

This commit is contained in:
AntsyLich
2026-10-10 17:37:43 +06:00
parent 8972882a34
commit 44c1e1915d
68 changed files with 599 additions and 284 deletions
@@ -5,11 +5,11 @@ import eu.kanade.tachiyomi.source.model.UpdateStrategy
import kotlinx.serialization.json.JsonObject
import mihon.core.common.extensions.toByteArray
import mihon.core.common.extensions.toJsonObject
import java.util.Date
import kotlin.time.Instant
object DateColumnAdapter : ColumnAdapter<Date, Long> {
override fun decode(databaseValue: Long): Date = Date(databaseValue)
override fun encode(value: Date): Long = value.time
object InstantColumnAdapter : ColumnAdapter<Instant, Long> {
override fun decode(databaseValue: Long): Instant = Instant.fromEpochMilliseconds(databaseValue)
override fun encode(value: Instant): Long = value.toEpochMilliseconds()
}
private const val LIST_OF_STRINGS_SEPARATOR = ", "
@@ -67,15 +67,25 @@ object DatabaseBindings {
return Database(
driver = driver,
historyAdapter = History.Adapter(
read_atAdapter = DateColumnAdapter,
read_atAdapter = InstantColumnAdapter,
),
mangaAdapter = Manga.Adapter(
remote_genreAdapter = StringListColumnAdapter,
remote_update_strategyAdapter = UpdateStrategyColumnAdapter,
remote_memoAdapter = MemoColumnAdapter,
user_favorite_atAdapter = InstantColumnAdapter,
state_chapter_last_updateAdapter = InstantColumnAdapter,
state_chapter_next_updateAdapter = InstantColumnAdapter,
state_cover_last_modifiedAdapter = InstantColumnAdapter,
),
chapterAdapter = Chapter.Adapter(
remote_date_uploadAdapter = InstantColumnAdapter,
remote_memoAdapter = MemoColumnAdapter,
state_date_fetchAdapter = InstantColumnAdapter,
),
manga_trackAdapter = Manga_track.Adapter(
start_dateAdapter = InstantColumnAdapter,
finish_dateAdapter = InstantColumnAdapter,
),
)
}
@@ -21,8 +21,8 @@ import tachiyomi.domain.manga.model.MangaUpdate
import tachiyomi.domain.manga.repository.MangaRepository
import tachiyomi.domain.track.model.Track
import tachiyomi.domain.track.repository.TrackRepository
import java.util.Date
import kotlin.math.max
import kotlin.time.Instant
@Inject
@SingleIn(AppScope::class)
@@ -74,7 +74,10 @@ class RestoreRepositoryImpl(
val details = if (dbManga.initialized || !manga.initialized) dbManga else manga
return dbManga.copy(
favoriteAt = if (dbManga.favorite || manga.favorite) {
listOfNotNull(dbManga.favoriteAt, manga.favoriteAt).filter { it > 0 }.minOrNull() ?: 0L
listOfNotNull(dbManga.favoriteAt, manga.favoriteAt)
.filter { it > Manga.UNKNOWN_FAVORITE_AT }
.minOrNull()
?: Manga.UNKNOWN_FAVORITE_AT
} else {
null
},
@@ -128,7 +131,7 @@ class RestoreRepositoryImpl(
remoteStatus = manga.status,
remoteCover = manga.thumbnailUrl,
stateChapterLastUpdate = manga.lastUpdate,
stateChapterNextUpdate = 0L,
stateChapterNextUpdate = null,
stateChapterFetchInterval = 0L,
stateInitialized = manga.initialized,
userReaderFlags = manga.viewerFlags,
@@ -212,7 +215,7 @@ class RestoreRepositoryImpl(
}
private fun Chapter.forComparison() =
this.copy(id = 0L, mangaId = 0L, dateFetch = 0L, dateUpload = 0L)
this.copy(id = 0L, mangaId = 0L, dateFetch = Instant.DISTANT_PAST, dateUpload = null)
private suspend fun restoreHistory(manga: Manga, restoredHistory: List<RestoredHistory>) {
if (restoredHistory.isEmpty()) return
@@ -229,17 +232,16 @@ class RestoreRepositoryImpl(
// each copy; they are one chapter now, read as late as any copy and for as long as all
.groupBy { chapterIdsByUrl.getValue(it.chapterUrl) }
.map { (chapterId, copies) ->
val readAt = copies.maxOf { it.readAt?.time ?: 0L }
val readAt = copies.mapNotNull { it.readAt }.maxOrNull()
val readDuration = copies.sumOf { it.readDuration }
val dbHistory = dbHistoryByChapterId[chapterId]
// New history entry
?: return@map Triple(chapterId, Date(readAt), readDuration)
?: return@map Triple(chapterId, readAt, readDuration)
// Update history entry. 0 is kept rather than written as NULL, since it marks history
// the user removed.
// Update history entry
Triple(
chapterId,
Date(max(readAt, dbHistory.read_at?.time ?: 0L)),
listOfNotNull(readAt, dbHistory.read_at).maxOrNull(),
max(readDuration, dbHistory.read_duration) - dbHistory.read_duration,
)
}
@@ -16,6 +16,7 @@ import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.ChapterRemoteUpdate
import tachiyomi.domain.chapter.model.ChapterUpdate
import tachiyomi.domain.chapter.repository.ChapterRepository
import kotlin.time.Instant
@Inject
@SingleIn(AppScope::class)
@@ -158,13 +159,13 @@ class ChapterRepositoryImpl(
remoteName: String,
remoteScanlator: String?,
remoteChapterNumber: Double,
remoteDateUpload: Long,
remoteDateUpload: Instant?,
remoteOrder: Long,
remoteMemo: JsonObject,
userRead: Boolean,
userBookmark: Boolean,
userLastPageRead: Long,
stateDateFetch: Long,
stateDateFetch: Instant,
): Chapter = Chapter(
id = id,
mangaId = mangaId,
@@ -3,13 +3,13 @@ package tachiyomi.data.history
import tachiyomi.domain.history.model.History
import tachiyomi.domain.history.model.HistoryWithRelations
import tachiyomi.domain.manga.model.MangaCover
import java.util.Date
import kotlin.time.Instant
object HistoryMapper {
fun mapHistory(
id: Long,
chapterId: Long,
readAt: Date?,
readAt: Instant?,
readDuration: Long,
): History = History(
id = id,
@@ -26,9 +26,9 @@ object HistoryMapper {
thumbnailUrl: String?,
sourceId: Long,
isFavorite: Boolean,
coverLastModified: Long,
coverLastModified: Instant?,
chapterNumber: Double,
readAt: Date?,
readAt: Instant?,
readDuration: Long,
): HistoryWithRelations = HistoryWithRelations(
id = historyId,
@@ -5,6 +5,7 @@ import kotlinx.serialization.json.JsonObject
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.MangaWithChapterCount
import kotlin.time.Instant
object MangaMapper {
fun mapManga(
@@ -20,21 +21,21 @@ object MangaMapper {
remoteCover: String?,
remoteUpdateStrategy: UpdateStrategy,
remoteMemo: JsonObject,
userFavoriteAt: Long?,
userFavoriteAt: Instant?,
userNotes: String,
userReaderFlags: Long,
userChapterFlags: Long,
stateChapterLastUpdate: Long?,
stateChapterNextUpdate: Long?,
stateChapterLastUpdate: Instant?,
stateChapterNextUpdate: Instant?,
stateChapterFetchInterval: Long,
stateCoverLastModified: Long,
stateCoverLastModified: Instant?,
stateInitialized: Boolean,
): Manga = Manga(
id = id,
source = sourceId,
favoriteAt = userFavoriteAt,
lastUpdate = stateChapterLastUpdate ?: 0,
nextUpdate = stateChapterNextUpdate ?: 0,
lastUpdate = stateChapterLastUpdate,
nextUpdate = stateChapterNextUpdate,
fetchInterval = stateChapterFetchInterval.toInt(),
viewerFlags = userReaderFlags,
chapterFlags = userChapterFlags,
@@ -66,20 +67,20 @@ object MangaMapper {
remoteCover: String?,
remoteUpdateStrategy: UpdateStrategy,
remoteMemo: JsonObject,
userFavoriteAt: Long,
userFavoriteAt: Instant,
userNotes: String,
userReaderFlags: Long,
userChapterFlags: Long,
stateChapterLastUpdate: Long?,
stateChapterNextUpdate: Long?,
stateChapterLastUpdate: Instant?,
stateChapterNextUpdate: Instant?,
stateChapterFetchInterval: Long,
stateCoverLastModified: Long,
stateCoverLastModified: Instant?,
stateInitialized: Boolean,
totalCount: Long,
readCount: Double,
latestUpload: Long,
chapterFetchedAt: Long,
lastRead: Long,
latestUpload: Instant?,
chapterFetchedAt: Instant?,
lastRead: Instant?,
bookmarkCount: Double,
categories: String,
): LibraryManga = LibraryManga(
@@ -128,14 +129,14 @@ object MangaMapper {
remoteCover: String?,
remoteUpdateStrategy: UpdateStrategy,
remoteMemo: JsonObject,
userFavoriteAt: Long?,
userFavoriteAt: Instant?,
userNotes: String,
userReaderFlags: Long,
userChapterFlags: Long,
stateChapterLastUpdate: Long?,
stateChapterNextUpdate: Long?,
stateChapterLastUpdate: Instant?,
stateChapterNextUpdate: Instant?,
stateChapterFetchInterval: Long,
stateCoverLastModified: Long,
stateCoverLastModified: Instant?,
stateInitialized: Boolean,
chapterCount: Long,
): MangaWithChapterCount = MangaWithChapterCount(
@@ -100,11 +100,10 @@ class MangaRepositoryImpl(
includedCategories: List<Long>,
): Flow<List<Manga>> {
val timeZone = TimeZone.currentSystemDefault()
val epochMillis =
Clock.System.now().toLocalDateTime(timeZone).date.atStartOfDayIn(timeZone).toEpochMilliseconds()
val startOfDay = Clock.System.now().toLocalDateTime(timeZone).date.atStartOfDayIn(timeZone)
return database.mangaQueries
.getUpcomingManga(
startOfDay = epochMillis,
startOfDay = startOfDay,
statuses = statuses,
includedEmpty = includedCategories.isEmpty(),
includedCategories = includedCategories,
@@ -1,6 +1,7 @@
package tachiyomi.data.track
import tachiyomi.domain.track.model.Track
import kotlin.time.Instant
object TrackMapper {
fun mapTrack(
@@ -15,8 +16,8 @@ object TrackMapper {
totalChapters: Long,
status: Long,
score: Double,
startDate: Long,
finishDate: Long,
startDate: Instant?,
finishDate: Instant?,
private: Boolean,
): Track = Track(
id = id,
@@ -13,6 +13,7 @@ import tachiyomi.data.subscribeToList
import tachiyomi.domain.manga.model.MangaCover
import tachiyomi.domain.updates.model.UpdatesWithRelations
import tachiyomi.domain.updates.repository.UpdatesRepository
import kotlin.time.Instant
@Inject
@SingleIn(AppScope::class)
@@ -23,14 +24,14 @@ class UpdatesRepositoryImpl(
override suspend fun awaitWithRead(
read: Boolean,
after: Long,
after: Instant,
limit: Long,
): List<UpdatesWithRelations> {
return getUpdatesByReadStatus(read = read, after = after, limit = limit).awaitAsList()
}
override fun subscribeAll(
after: Long,
after: Instant,
limit: Long,
unread: Boolean?,
started: Boolean?,
@@ -58,13 +59,13 @@ class UpdatesRepositoryImpl(
override fun subscribeWithRead(
read: Boolean,
after: Long,
after: Instant,
limit: Long,
): Flow<List<UpdatesWithRelations>> {
return getUpdatesByReadStatus(read = read, after = after, limit = limit).subscribeToList()
}
private fun getUpdatesByReadStatus(read: Boolean, after: Long, limit: Long): Query<UpdatesWithRelations> {
private fun getUpdatesByReadStatus(read: Boolean, after: Instant, limit: Long): Query<UpdatesWithRelations> {
return if (read) {
database.updatesViewQueries.getReadUpdates(
after = after,
@@ -94,9 +95,9 @@ class UpdatesRepositoryImpl(
sourceId: Long,
favorite: Boolean,
thumbnailUrl: String?,
coverLastModified: Long,
dateUpload: Long,
dateFetch: Long,
coverLastModified: Instant?,
dateUpload: Instant?,
dateFetch: Instant,
excludedScanlator: String?,
): UpdatesWithRelations = UpdatesWithRelations(
mangaId = mangaId,