Tracking from everywhere (#1173)

* Add tracking everywhere

* Add update chapters progress from tracker

* Change tracking everywhere to track when marked as read

* Mutualize updateTrack method

* Mutualize updateTrack method with reader and notification

* Revert "Add paused tracking"

This reverts commit 9eb37533

* Revert change for library tracking

* Revert "Add update chapters progress from tracker"

This reverts commit 193a4a8a

* rework of updateTrackChapterMarkedAsRead

* mutualization after rework

* remove oldChapter

* reorder updateTrackChapterMarkedAsRead parameters
This commit is contained in:
nzoba 2022-04-23 23:52:18 +02:00 committed by GitHub
parent c1e4effeb5
commit 0d693a910c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 132 additions and 55 deletions

View file

@ -10,13 +10,15 @@ import eu.kanade.tachiyomi.data.track.TrackService
interface TrackQueries : DbProvider { interface TrackQueries : DbProvider {
fun getTracks(manga: Manga) = db.get() fun getTracks(manga: Manga) = getTracks(manga.id)
fun getTracks(mangaId: Long?) = db.get()
.listOfObjects(Track::class.java) .listOfObjects(Track::class.java)
.withQuery( .withQuery(
Query.builder() Query.builder()
.table(TrackTable.TABLE) .table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ?") .where("${TrackTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id) .whereArgs(mangaId)
.build() .build()
) )
.prepare() .prepare()

View file

@ -23,6 +23,7 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.setting.AboutController import eu.kanade.tachiyomi.ui.setting.AboutController
import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
@ -192,18 +193,21 @@ class NotificationReceiver : BroadcastReceiver() {
*/ */
private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) { private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) {
val db: DatabaseHelper = Injekt.get() val db: DatabaseHelper = Injekt.get()
chapterUrls.forEach { val preferences: PreferencesHelper = Injekt.get()
val chapters = chapterUrls.map {
val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return
chapter.read = true chapter.read = true
db.updateChapterProgress(chapter).executeAsBlocking() db.updateChapterProgress(chapter).executeAsBlocking()
val preferences: PreferencesHelper = Injekt.get()
if (preferences.removeAfterMarkedAsRead()) { if (preferences.removeAfterMarkedAsRead()) {
val manga = db.getManga(mangaId).executeAsBlocking() ?: return val manga = db.getManga(mangaId).executeAsBlocking() ?: return
val sourceManager: SourceManager = Injekt.get() val sourceManager: SourceManager = Injekt.get()
val source = sourceManager.get(manga.source) ?: return val source = sourceManager.get(manga.source) ?: return
downloadManager.deleteChapters(listOf(chapter), manga, source) downloadManager.deleteChapters(listOf(chapter), manga, source)
} }
return@map chapter
} }
val newLastChapter = chapters.maxByOrNull { it.chapter_number.toInt() }
updateTrackChapterMarkedAsRead(db, preferences, newLastChapter, mangaId, 0)
} }
/** Method called when user wants to stop a restore /** Method called when user wants to stop a restore

View file

@ -95,6 +95,8 @@ object PreferenceKeys {
const val autoUpdateTrack = "pref_auto_update_manga_sync_key" const val autoUpdateTrack = "pref_auto_update_manga_sync_key"
const val trackMarkedAsRead = "track_marked_as_read"
const val trackingsToAddOnline = "pref_tracking_for_online" const val trackingsToAddOnline = "pref_tracking_for_online"
const val lastUsedCatalogueSource = "last_catalogue_source" const val lastUsedCatalogueSource = "last_catalogue_source"

View file

@ -194,6 +194,8 @@ class PreferencesHelper(val context: Context) {
fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true) fun autoUpdateTrack() = prefs.getBoolean(Keys.autoUpdateTrack, true)
fun trackMarkedAsRead() = prefs.getBoolean(Keys.trackMarkedAsRead, false)
fun trackingsToAddOnline() = flowPrefs.getStringSet(Keys.trackingsToAddOnline, emptySet()) fun trackingsToAddOnline() = flowPrefs.getStringSet(Keys.trackingsToAddOnline, emptySet())
fun lastUsedCatalogueSource() = flowPrefs.getLong(Keys.lastUsedCatalogueSource, -1) fun lastUsedCatalogueSource() = flowPrefs.getLong(Keys.lastUsedCatalogueSource, -1)

View file

@ -44,9 +44,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.ArrayList
import java.util.Calendar import java.util.Calendar
import java.util.Comparator
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit

View file

@ -80,6 +80,7 @@ import eu.kanade.tachiyomi.ui.source.browse.BrowseSourceController
import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.webview.WebViewActivity import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.addOrRemoveToFavorites import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead
import eu.kanade.tachiyomi.util.isLocal import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.moveCategories import eu.kanade.tachiyomi.util.moveCategories
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
@ -864,6 +865,8 @@ class MangaDetailsController :
} }
fun toggleReadChapter(position: Int) { fun toggleReadChapter(position: Int) {
val preferences = presenter.preferences
val db = presenter.db
val item = adapter?.getItem(position) as? ChapterItem ?: return val item = adapter?.getItem(position) as? ChapterItem ?: return
val chapter = item.chapter val chapter = item.chapter
val lastRead = chapter.last_page_read val lastRead = chapter.last_page_read
@ -885,8 +888,13 @@ class MangaDetailsController :
object : BaseTransientBottomBar.BaseCallback<Snackbar>() { object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (!undoing && !read && presenter.preferences.removeAfterMarkedAsRead()) { if (!undoing && !read) {
presenter.deleteChapters(listOf(item)) if (preferences.removeAfterMarkedAsRead()) {
presenter.deleteChapters(listOf(item))
}
updateTrackChapterMarkedAsRead(db, preferences, chapter, manga?.id) {
presenter.fetchTracks()
}
} }
} }
} }

View file

@ -48,6 +48,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.chapter.ChapterUtil import eu.kanade.tachiyomi.util.chapter.ChapterUtil
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead
import eu.kanade.tachiyomi.util.lang.trimOrNull import eu.kanade.tachiyomi.util.lang.trimOrNull
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
@ -488,6 +489,12 @@ class MangaDetailsPresenter(
} }
getChapters() getChapters()
withContext(Dispatchers.Main) { controller?.updateChapters(chapters) } withContext(Dispatchers.Main) { controller?.updateChapters(chapters) }
if (read && deleteNow) {
val latestReadChapter = selectedChapters.maxByOrNull { it.chapter_number.toInt() }?.chapter
updateTrackChapterMarkedAsRead(db, preferences, latestReadChapter, manga.id) {
fetchTracks()
}
}
} }
} }
@ -858,7 +865,7 @@ class MangaDetailsPresenter(
} }
} }
private suspend fun fetchTracks() { suspend fun fetchTracks() {
tracks = withContext(Dispatchers.IO) { db.getTracks(manga).executeAsBlocking() } tracks = withContext(Dispatchers.IO) { db.getTracks(manga).executeAsBlocking() }
trackList = loggedServices.map { service -> trackList = loggedServices.map { service ->
TrackItem(tracks.find { it.sync_id == service.id }, service) TrackItem(tracks.find { it.sync_id == service.id }, service)

View file

@ -17,8 +17,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.DelayedTrackingUpdateJob
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -35,16 +33,16 @@ import eu.kanade.tachiyomi.ui.reader.settings.ReadingModeType
import eu.kanade.tachiyomi.util.chapter.ChapterFilter import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.chapter.ChapterSort import eu.kanade.tachiyomi.util.chapter.ChapterSort
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.updateTrackChapterRead
import eu.kanade.tachiyomi.util.lang.getUrlWithoutDomain import eu.kanade.tachiyomi.util.lang.getUrlWithoutDomain
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.executeOnIO import eu.kanade.tachiyomi.util.system.executeOnIO
import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.withUIContext import eu.kanade.tachiyomi.util.system.withUIContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -491,7 +489,7 @@ class ReaderPresenter(
) )
) { ) {
selectedChapter.chapter.read = true selectedChapter.chapter.read = true
updateTrackChapterRead(selectedChapter) updateTrackChapterAfterReading(selectedChapter)
deleteChapterIfNeeded(selectedChapter) deleteChapterIfNeeded(selectedChapter)
} }
@ -877,41 +875,12 @@ class ReaderPresenter(
* Starts the service that updates the last chapter read in sync services. This operation * Starts the service that updates the last chapter read in sync services. This operation
* will run in a background thread and errors are ignored. * will run in a background thread and errors are ignored.
*/ */
private fun updateTrackChapterRead(readerChapter: ReaderChapter) { private fun updateTrackChapterAfterReading(readerChapter: ReaderChapter) {
if (!preferences.autoUpdateTrack()) return if (!preferences.autoUpdateTrack()) return
val manga = manga ?: return
val chapterRead = readerChapter.chapter.chapter_number.toInt() launchIO {
val newChapterRead = readerChapter.chapter.chapter_number.toInt()
val trackManager = Injekt.get<TrackManager>() updateTrackChapterRead(db, preferences, manga?.id, newChapterRead, true)
// We want these to execute even if the presenter is destroyed so launch on GlobalScope
GlobalScope.launch {
withContext(Dispatchers.IO) {
val trackList = db.getTracks(manga).executeAsBlocking()
trackList.map { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && chapterRead > track.last_chapter_read) {
if (!preferences.context.isOnline()) {
val mangaId = manga.id ?: return@map
val trackings = preferences.trackingsToAddOnline().get().toMutableSet()
val currentTracking = trackings.find { it.startsWith("$mangaId:${track.sync_id}:") }
trackings.remove(currentTracking)
trackings.add("$mangaId:${track.sync_id}:$chapterRead")
preferences.trackingsToAddOnline().set(trackings)
DelayedTrackingUpdateJob.setupTask(preferences.context)
} else {
try {
track.last_chapter_read = chapterRead
service.update(track, true)
db.insertTrack(track).executeAsBlocking()
} catch (e: Exception) {
Timber.e(e)
}
}
}
}
}
} }
} }

View file

@ -50,6 +50,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.recents.options.TabbedRecentsOptionsSheet import eu.kanade.tachiyomi.ui.recents.options.TabbedRecentsOptionsSheet
import eu.kanade.tachiyomi.ui.source.browse.ProgressItem import eu.kanade.tachiyomi.ui.source.browse.ProgressItem
import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getBottomGestureInsets import eu.kanade.tachiyomi.util.system.getBottomGestureInsets
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
@ -668,6 +669,8 @@ class RecentsController(bundle: Bundle? = null) :
} }
override fun markAsRead(position: Int) { override fun markAsRead(position: Int) {
val preferences = presenter.preferences
val db = presenter.db
val item = adapter.getItem(position) as? RecentMangaItem ?: return val item = adapter.getItem(position) as? RecentMangaItem ?: return
val chapter = item.chapter val chapter = item.chapter
val manga = item.mch.manga val manga = item.mch.manga
@ -691,11 +694,14 @@ class RecentsController(bundle: Bundle? = null) :
object : BaseTransientBottomBar.BaseCallback<Snackbar>() { object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) { override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event) super.onDismissed(transientBottomBar, event)
if (!undoing && presenter.preferences.removeAfterMarkedAsRead() && if (!undoing && !wasRead) {
!wasRead if (preferences.removeAfterMarkedAsRead()) {
) { lastChapterId = chapter.id
lastChapterId = chapter.id presenter.deleteChapter(chapter, manga)
presenter.deleteChapter(chapter, manga) }
updateTrackChapterMarkedAsRead(db, preferences, chapter, manga.id) {
(router.backstack.lastOrNull()?.controller as? MangaDetailsController)?.presenter?.fetchTracks()
}
} }
} }
} }

View file

@ -40,7 +40,7 @@ import kotlin.math.abs
class RecentsPresenter( class RecentsPresenter(
val preferences: PreferencesHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(), val downloadManager: DownloadManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(), val db: DatabaseHelper = Injekt.get(),
private val chapterFilter: ChapterFilter = Injekt.get() private val chapterFilter: ChapterFilter = Injekt.get()
) : BaseCoroutinePresenter<RecentsController>(), DownloadQueue.DownloadListener, LibraryServiceListener, DownloadServiceListener { ) : BaseCoroutinePresenter<RecentsController>(), DownloadQueue.DownloadListener, LibraryServiceListener, DownloadServiceListener {

View file

@ -33,9 +33,14 @@ class SettingsTrackingController :
switchPreference { switchPreference {
key = Keys.autoUpdateTrack key = Keys.autoUpdateTrack
titleRes = R.string.sync_chapters_after_reading titleRes = R.string.update_tracking_after_reading
defaultValue = true defaultValue = true
} }
switchPreference {
key = Keys.trackMarkedAsRead
titleRes = R.string.update_tracking_marked_read
defaultValue = false
}
preferenceCategory { preferenceCategory {
titleRes = R.string.services titleRes = R.string.services

View file

@ -3,9 +3,17 @@ package eu.kanade.tachiyomi.util.chapter
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.DelayedTrackingUpdateJob
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.launchIO import eu.kanade.tachiyomi.util.system.launchIO
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
/** /**
* Helper method for syncing a remote track with the local chapters, and back * Helper method for syncing a remote track with the local chapters, and back
@ -40,3 +48,68 @@ fun syncChaptersWithTrackServiceTwoWay(db: DatabaseHelper, chapters: List<Chapte
} }
} }
} }
private var trackingJobs = HashMap<Long, Pair<Job?, Int?>>()
/**
* Starts the service that updates the last chapter read in sync services. This operation
* will run in a background thread and errors are ignored.
*/
fun updateTrackChapterMarkedAsRead(
db: DatabaseHelper,
preferences: PreferencesHelper,
newLastChapter: Chapter?,
mangaId: Long?,
delay: Long = 3000,
fetchTracks: (suspend () -> Unit)? = null
) {
if (!preferences.trackMarkedAsRead()) return
mangaId ?: return
val newChapterRead = newLastChapter?.chapter_number?.toInt() ?: 0
// To avoid unnecessary calls if multiple marked as read for same manga
if (trackingJobs[mangaId]?.second ?: 0 < newChapterRead) {
trackingJobs[mangaId]?.first?.cancel()
// We want these to execute even if the presenter is destroyed
trackingJobs[mangaId] = launchIO {
delay(delay)
updateTrackChapterRead(db, preferences, mangaId, newChapterRead)
fetchTracks?.invoke()
trackingJobs.remove(mangaId)
} to newChapterRead
}
}
suspend fun updateTrackChapterRead(
db: DatabaseHelper,
preferences: PreferencesHelper,
mangaId: Long?,
newChapterRead: Int,
retryWhenOnline: Boolean = false
) {
val trackManager = Injekt.get<TrackManager>()
val trackList = db.getTracks(mangaId).executeAsBlocking()
trackList.map { track ->
val service = trackManager.getService(track.sync_id)
if (service != null && service.isLogged && newChapterRead > track.last_chapter_read) {
if (retryWhenOnline && !preferences.context.isOnline()) {
val trackings = preferences.trackingsToAddOnline().get().toMutableSet()
val currentTracking = trackings.find { it.startsWith("$mangaId:${track.sync_id}:") }
trackings.remove(currentTracking)
trackings.add("$mangaId:${track.sync_id}:$newChapterRead")
preferences.trackingsToAddOnline().set(trackings)
DelayedTrackingUpdateJob.setupTask(preferences.context)
} else if (preferences.context.isOnline()) {
try {
track.last_chapter_read = newChapterRead
service.update(track, true)
db.insertTrack(track).executeAsBlocking()
} catch (e: Exception) {
Timber.e(e)
}
}
}
}
}

View file

@ -562,7 +562,8 @@
<string name="tracking_info">One-way sync to update the chapter progress in tracking services. Set up tracking for individual manga entries from their tracking button.</string> <string name="tracking_info">One-way sync to update the chapter progress in tracking services. Set up tracking for individual manga entries from their tracking button.</string>
<string name="enhanced_services">Enhanced services</string> <string name="enhanced_services">Enhanced services</string>
<string name="enhanced_tracking_info">Services that provide enhanced features for specific sources. Manga are automatically tracked when added to your library.</string> <string name="enhanced_tracking_info">Services that provide enhanced features for specific sources. Manga are automatically tracked when added to your library.</string>
<string name="sync_chapters_after_reading">Sync chapters after reading</string> <string name="update_tracking_after_reading">Update tracking after reading</string>
<string name="update_tracking_marked_read">Update tracking when marked as read</string>
<string name="track_when_adding_to_library">Track when adding to library</string> <string name="track_when_adding_to_library">Track when adding to library</string>
<string name="only_applies_silent_trackers">Only applies to silent trackers, such as Komga</string> <string name="only_applies_silent_trackers">Only applies to silent trackers, such as Komga</string>
<string name="reading">Reading</string> <string name="reading">Reading</string>