Save read duration (#1360)

* Save read duration

* Set readStartTime when switching chapters in a single reader session
This commit is contained in:
nzoba 2022-08-17 20:21:55 +02:00 committed by GitHub
parent 26dae149a7
commit 0cf6393ad6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 65 additions and 105 deletions

View file

@ -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<BackupHistory>) {
// List containing history to be updated
val historyToBeUpdated = ArrayList<History>(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)
}

View file

@ -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()

View file

@ -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,
)

View file

@ -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

View file

@ -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
}

View file

@ -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<History>) = db.put()
.objects(historyList)
.withPutResolver(HistoryLastReadPutResolver())
.prepare()
fun deleteHistory() = db.delete()
.byQuery(
DeleteQuery.builder()

View file

@ -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,
)
}

View file

@ -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,
)
}

View file

@ -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)) {

View file

@ -1114,10 +1114,15 @@ class ReaderActivity : BaseRxActivity<ReaderPresenter>() {
}
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()

View file

@ -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].
*/

View file

@ -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()
}