From 0cf6393ad6555e2a7bbe7234c41ea312ea3a4e4f Mon Sep 17 00:00:00 2001 From: nzoba <55888232+nzoba@users.noreply.github.com> Date: Wed, 17 Aug 2022 20:21:55 +0200 Subject: [PATCH] Save read duration (#1360) * Save read duration * Set readStartTime when switching chapters in a single reader session --- .../data/backup/full/FullBackupManager.kt | 6 +- .../data/backup/full/FullBackupRestore.kt | 3 +- .../data/backup/full/models/BackupHistory.kt | 2 + .../tachiyomi/data/database/models/History.kt | 2 +- .../data/database/models/HistoryImpl.kt | 2 +- .../data/database/queries/HistoryQueries.kt | 15 +---- .../resolvers/HistoryLastReadPutResolver.kt | 62 ------------------- .../resolvers/HistoryUpsertResolver.kt | 1 + .../manga/process/MigrationProcessAdapter.kt | 7 ++- .../tachiyomi/ui/reader/ReaderActivity.kt | 7 ++- .../tachiyomi/ui/reader/ReaderPresenter.kt | 53 ++++++++++------ .../tachiyomi/ui/recents/RecentsPresenter.kt | 10 ++- 12 files changed, 65 insertions(+), 105 deletions(-) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt index 814ee91338..c83fca5e25 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupManager.kt @@ -184,7 +184,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { if (historyForManga.isNotEmpty()) { val history = historyForManga.mapNotNull { history -> val url = databaseHelper.getChapter(history.chapter_id).executeAsBlocking()?.url - url?.let { BackupHistory(url, history.last_read) } + url?.let { BackupHistory(url, history.last_read, history.time_read) } } if (history.isNotEmpty()) { mangaObject.history = history @@ -283,12 +283,13 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { internal fun restoreHistoryForManga(history: List) { // List containing history to be updated val historyToBeUpdated = ArrayList(history.size) - for ((url, lastRead) in history) { + for ((url, lastRead, readDuration) in history) { val dbHistory = databaseHelper.getHistoryByChapterUrl(url).executeAsBlocking() // Check if history already in database and update if (dbHistory != null) { dbHistory.apply { last_read = max(lastRead, dbHistory.last_read) + time_read = max(readDuration, dbHistory.time_read) } historyToBeUpdated.add(dbHistory) } else { @@ -296,6 +297,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) { databaseHelper.getChapter(url).executeAsBlocking()?.let { val historyToAdd = History.create(it).apply { last_read = lastRead + time_read = readDuration } historyToBeUpdated.add(historyToAdd) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt index 7bde590f6e..011eec8b62 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/FullBackupRestore.kt @@ -66,7 +66,8 @@ class FullBackupRestore(context: Context, notifier: BackupNotifier) : AbstractBa val manga = backupManga.getMangaImpl() val chapters = backupManga.getChaptersImpl() val categories = backupManga.categories - val history = backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead) } + backupManga.history + val history = + backupManga.brokenHistory.map { BackupHistory(it.url, it.lastRead, it.readDuration) } + backupManga.history val tracks = backupManga.getTrackingImpl() val customManga = backupManga.getCustomMangaInfo() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt index 790e433c91..a2b66ccedc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/backup/full/models/BackupHistory.kt @@ -7,10 +7,12 @@ import kotlinx.serialization.protobuf.ProtoNumber data class BrokenBackupHistory( @ProtoNumber(0) var url: String, @ProtoNumber(1) var lastRead: Long, + @ProtoNumber(2) var readDuration: Long = 0, ) @Serializable data class BackupHistory( @ProtoNumber(1) var url: String, @ProtoNumber(2) var lastRead: Long, + @ProtoNumber(3) var readDuration: Long = 0, ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt index dff3bcb155..10429f8b1d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/History.kt @@ -23,7 +23,7 @@ interface History : Serializable { var last_read: Long /** - * Total time chapter was read - todo not yet implemented + * Total time chapter was read */ var time_read: Long diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt index 94efcf2666..8b9dbe7662 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/models/HistoryImpl.kt @@ -21,7 +21,7 @@ class HistoryImpl : History { override var last_read: Long = 0 /** - * Total time chapter was read - todo not yet implemented + * Total time chapter was read */ override var time_read: Long = 0 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt index 62ecf8118d..36aa8ffe93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/HistoryQueries.kt @@ -5,7 +5,6 @@ import com.pushtorefresh.storio.sqlite.queries.RawQuery import eu.kanade.tachiyomi.data.database.DbProvider import eu.kanade.tachiyomi.data.database.models.History import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory -import eu.kanade.tachiyomi.data.database.resolvers.HistoryLastReadPutResolver import eu.kanade.tachiyomi.data.database.resolvers.HistoryUpsertResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaChapterHistoryGetResolver import eu.kanade.tachiyomi.data.database.tables.HistoryTable @@ -122,9 +121,9 @@ interface HistoryQueries : DbProvider { * Inserts history object if not yet in database * @param history history object */ - fun updateHistoryLastRead(history: History) = db.put() + fun upsertHistoryLastRead(history: History) = db.put() .`object`(history) - .withPutResolver(HistoryLastReadPutResolver()) + .withPutResolver(HistoryUpsertResolver()) .prepare() /** @@ -137,16 +136,6 @@ interface HistoryQueries : DbProvider { .withPutResolver(HistoryUpsertResolver()) .prepare() - /** - * Updates the history last read. - * Inserts history object if not yet in database - * @param historyList history object list - */ - fun updateHistoryLastRead(historyList: List) = db.put() - .objects(historyList) - .withPutResolver(HistoryLastReadPutResolver()) - .prepare() - fun deleteHistory() = db.delete() .byQuery( DeleteQuery.builder() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt deleted file mode 100644 index 9b035dcc6a..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryLastReadPutResolver.kt +++ /dev/null @@ -1,62 +0,0 @@ -package eu.kanade.tachiyomi.data.database.resolvers - -import androidx.annotation.NonNull -import androidx.core.content.contentValuesOf -import com.pushtorefresh.storio.sqlite.StorIOSQLite -import com.pushtorefresh.storio.sqlite.operations.put.PutResult -import com.pushtorefresh.storio.sqlite.queries.Query -import com.pushtorefresh.storio.sqlite.queries.UpdateQuery -import eu.kanade.tachiyomi.data.database.inTransactionReturn -import eu.kanade.tachiyomi.data.database.mappers.HistoryPutResolver -import eu.kanade.tachiyomi.data.database.models.History -import eu.kanade.tachiyomi.data.database.tables.HistoryTable - -class HistoryLastReadPutResolver : HistoryPutResolver() { - - /** - * Updates last_read time of chapter - */ - override fun performPut(@NonNull db: StorIOSQLite, @NonNull history: History): PutResult = db.inTransactionReturn { - val updateQuery = mapToUpdateQuery(history) - - val cursor = db.lowLevel().query( - Query.builder() - .table(updateQuery.table()) - .where(updateQuery.where()) - .whereArgs(updateQuery.whereArgs()) - .build(), - ) - - val putResult = cursor.use { putCursor -> - if (putCursor.count == 0) { - val insertQuery = mapToInsertQuery(history) - val insertedId = db.lowLevel().insert(insertQuery, mapToContentValues(history)) - PutResult.newInsertResult(insertedId, insertQuery.table()) - } else { - val numberOfRowsUpdated = db.lowLevel().update(updateQuery, mapToUpdateContentValues(history)) - PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table()) - } - } - - putResult - } - - /** - * Creates update query - * @param obj history object - */ - override fun mapToUpdateQuery(obj: History) = UpdateQuery.builder() - .table(HistoryTable.TABLE) - .where("${HistoryTable.COL_CHAPTER_ID} = ?") - .whereArgs(obj.chapter_id) - .build() - - /** - * Create content query - * @param history object - */ - private fun mapToUpdateContentValues(history: History) = - contentValuesOf( - HistoryTable.COL_LAST_READ to history.last_read, - ) -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt index 908aca16d3..a98837373b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/HistoryUpsertResolver.kt @@ -48,5 +48,6 @@ class HistoryUpsertResolver : HistoryPutResolver() { private fun mapToUpdateContentValues(history: History) = contentValuesOf( HistoryTable.COL_LAST_READ to history.last_read, + HistoryTable.COL_TIME_READ to history.time_read, ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt index ec4a7d3fff..4d12cb7303 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessAdapter.kt @@ -164,7 +164,10 @@ class MigrationProcessAdapter( prevHistoryList.find { it.chapter_id == prevChapter.id } ?.let { prevHistory -> val history = History.create(chapter) - .apply { last_read = prevHistory.last_read } + .apply { + last_read = prevHistory.last_read + time_read = prevHistory.time_read + } historyList.add(history) } } else if (chapter.chapter_number <= maxChapterRead) { @@ -173,7 +176,7 @@ class MigrationProcessAdapter( } } db.insertChapters(dbChapters).executeAsBlocking() - db.updateHistoryLastRead(historyList).executeAsBlocking() + db.upsertHistoryLastRead(historyList).executeAsBlocking() } // Update categories if (MigrationFlags.hasCategories(flags)) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 26fc2ff42a..6157197f08 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -1114,10 +1114,15 @@ class ReaderActivity : BaseRxActivity() { } override fun onPause() { - presenter.saveProgress() + presenter.saveCurrentChapterReadingProgress() super.onPause() } + override fun onResume() { + super.onResume() + presenter.setReadStartTime() + } + fun reloadChapters(doublePages: Boolean, force: Boolean = false) { val pViewer = viewer as? PagerViewer ?: return pViewer.updateShifting() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index b375e3cfa7..f05c90d62d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -94,6 +94,11 @@ class ReaderPresenter( */ private var loader: ChapterLoader? = null + /** + * The time the chapter was started reading + */ + private var chapterReadStartTime: Long? = null + /** * Subscription to prevent setting chapters as active from multiple threads. */ @@ -184,8 +189,7 @@ class ReaderPresenter( val isChapterDownloaded = currentChapters.currChapter.pageLoader is DownloadPageLoader currentChapters.unref() val currentChapter = currentChapters.currChapter - saveChapterProgress(currentChapter) - saveChapterHistory(currentChapter) + saveReadingProgress(currentChapter) val currentChapterPageCount = currentChapter.chapter.last_page_read + currentChapter.chapter.pages_left if ((currentChapter.chapter.last_page_read + 1.0) / currentChapterPageCount > 0.33 || isAnyPrevChapterDownloaded) { downloadNextChapters(isChapterDownloaded) @@ -425,7 +429,7 @@ class ReaderPresenter( fun loadChapter(chapter: Chapter) { val loader = loader ?: return - viewerChaptersRelay.value?.currChapter?.let(::onChapterChanged) + viewerChaptersRelay.value?.currChapter?.let(::saveReadingProgress) Timber.d("Loading ${chapter.url}") @@ -532,7 +536,8 @@ class ReaderPresenter( if (selectedChapter != currentChapters.currChapter) { Timber.d("Setting ${selectedChapter.chapter.url} as active") - onChapterChanged(currentChapters.currChapter) + saveReadingProgress(currentChapters.currChapter) + setReadStartTime() loadNewChapter(selectedChapter) } } @@ -592,39 +597,49 @@ class ReaderPresenter( } /** - * Called when a chapter changed from [fromChapter] to [toChapter]. It updates [fromChapter] - * on the database. + * Called when reader chapter is changed in reader or when activity is paused. */ - private fun onChapterChanged(fromChapter: ReaderChapter) { - saveChapterProgress(fromChapter) - saveChapterHistory(fromChapter) + private fun saveReadingProgress(readerChapter: ReaderChapter) { + saveChapterProgress(readerChapter) + saveChapterHistory(readerChapter) } - fun saveProgress() = getCurrentChapter()?.let { onChapterChanged(it) } + fun saveCurrentChapterReadingProgress() = getCurrentChapter()?.let { saveReadingProgress(it) } /** - * Saves this [chapter] progress (last read page and whether it's read). + * Saves this [readerChapter] progress (last read page and whether it's read). * If incognito mode isn't on or has at least 1 tracker */ - private fun saveChapterProgress(chapter: ReaderChapter) { - db.getChapter(chapter.chapter.id!!).executeAsBlocking()?.let { dbChapter -> - chapter.chapter.bookmark = dbChapter.bookmark + private fun saveChapterProgress(readerChapter: ReaderChapter) { + db.getChapter(readerChapter.chapter.id!!).executeAsBlocking()?.let { dbChapter -> + readerChapter.chapter.bookmark = dbChapter.bookmark } if (!preferences.incognitoMode().get() || hasTrackers) { - db.updateChapterProgress(chapter.chapter).executeAsBlocking() + db.updateChapterProgress(readerChapter.chapter).executeAsBlocking() } } /** - * Saves this [chapter] last read history. + * Saves this [readerChapter] last read history. */ - private fun saveChapterHistory(chapter: ReaderChapter) { + private fun saveChapterHistory(readerChapter: ReaderChapter) { if (!preferences.incognitoMode().get()) { - val history = History.create(chapter.chapter).apply { last_read = Date().time } - db.updateHistoryLastRead(history).executeAsBlocking() + val readAt = Date().time + val sessionReadDuration = chapterReadStartTime?.let { readAt - it } ?: 0 + val oldTimeRead = db.getHistoryByChapterUrl(readerChapter.chapter.url).executeAsBlocking()?.time_read ?: 0 + val history = History.create(readerChapter.chapter).apply { + last_read = readAt + time_read = sessionReadDuration + oldTimeRead + } + db.upsertHistoryLastRead(history).executeAsBlocking() + chapterReadStartTime = null } } + fun setReadStartTime() { + chapterReadStartTime = Date().time + } + /** * Called from the activity to preload the given [chapter]. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt index 69703a9d61..900c5c6dab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentsPresenter.kt @@ -488,7 +488,8 @@ class RecentsPresenter( */ fun removeFromHistory(history: History) { history.last_read = 0L - db.updateHistoryLastRead(history).executeAsBlocking() + history.time_read = 0L + db.upsertHistoryLastRead(history).executeAsBlocking() getRecents() } @@ -498,8 +499,11 @@ class RecentsPresenter( */ fun removeAllFromHistory(mangaId: Long) { val history = db.getHistoryByMangaId(mangaId).executeAsBlocking() - history.forEach { it.last_read = 0L } - db.updateHistoryLastRead(history).executeAsBlocking() + history.forEach { + it.last_read = 0L + it.time_read = 0L + } + db.upsertHistoryLastRead(history).executeAsBlocking() getRecents() }