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 {
fun getTracks(manga: Manga) = db.get()
fun getTracks(manga: Manga) = getTracks(manga.id)
fun getTracks(mangaId: Long?) = db.get()
.listOfObjects(Track::class.java)
.withQuery(
Query.builder()
.table(TrackTable.TABLE)
.where("${TrackTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id)
.whereArgs(mangaId)
.build()
)
.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.reader.ReaderActivity
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.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager
@ -192,18 +193,21 @@ class NotificationReceiver : BroadcastReceiver() {
*/
private fun markAsRead(chapterUrls: Array<String>, mangaId: Long) {
val db: DatabaseHelper = Injekt.get()
chapterUrls.forEach {
val preferences: PreferencesHelper = Injekt.get()
val chapters = chapterUrls.map {
val chapter = db.getChapter(it, mangaId).executeAsBlocking() ?: return
chapter.read = true
db.updateChapterProgress(chapter).executeAsBlocking()
val preferences: PreferencesHelper = Injekt.get()
if (preferences.removeAfterMarkedAsRead()) {
val manga = db.getManga(mangaId).executeAsBlocking() ?: return
val sourceManager: SourceManager = Injekt.get()
val source = sourceManager.get(manga.source) ?: return
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

View file

@ -95,6 +95,8 @@ object PreferenceKeys {
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 lastUsedCatalogueSource = "last_catalogue_source"

View file

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

View file

@ -44,9 +44,7 @@ import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.ArrayList
import java.util.Calendar
import java.util.Comparator
import java.util.Date
import java.util.Locale
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.webview.WebViewActivity
import eu.kanade.tachiyomi.util.addOrRemoveToFavorites
import eu.kanade.tachiyomi.util.chapter.updateTrackChapterMarkedAsRead
import eu.kanade.tachiyomi.util.isLocal
import eu.kanade.tachiyomi.util.moveCategories
import eu.kanade.tachiyomi.util.storage.getUriCompat
@ -864,6 +865,8 @@ class MangaDetailsController :
}
fun toggleReadChapter(position: Int) {
val preferences = presenter.preferences
val db = presenter.db
val item = adapter?.getItem(position) as? ChapterItem ?: return
val chapter = item.chapter
val lastRead = chapter.last_page_read
@ -885,8 +888,13 @@ class MangaDetailsController :
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!undoing && !read && presenter.preferences.removeAfterMarkedAsRead()) {
presenter.deleteChapters(listOf(item))
if (!undoing && !read) {
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.syncChaptersWithSource
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.manga.MangaShortcutManager
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
@ -488,6 +489,12 @@ class MangaDetailsPresenter(
}
getChapters()
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() }
trackList = loggedServices.map { 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.Notifications
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.Source
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.ChapterSort
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.storage.DiskUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
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.withUIContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -491,7 +489,7 @@ class ReaderPresenter(
)
) {
selectedChapter.chapter.read = true
updateTrackChapterRead(selectedChapter)
updateTrackChapterAfterReading(selectedChapter)
deleteChapterIfNeeded(selectedChapter)
}
@ -877,41 +875,12 @@ class ReaderPresenter(
* Starts the service that updates the last chapter read in sync services. This operation
* will run in a background thread and errors are ignored.
*/
private fun updateTrackChapterRead(readerChapter: ReaderChapter) {
private fun updateTrackChapterAfterReading(readerChapter: ReaderChapter) {
if (!preferences.autoUpdateTrack()) return
val manga = manga ?: return
val chapterRead = readerChapter.chapter.chapter_number.toInt()
val trackManager = Injekt.get<TrackManager>()
// 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)
}
}
}
}
}
launchIO {
val newChapterRead = readerChapter.chapter.chapter_number.toInt()
updateTrackChapterRead(db, preferences, manga?.id, newChapterRead, true)
}
}

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.recents.options.TabbedRecentsOptionsSheet
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.getBottomGestureInsets
import eu.kanade.tachiyomi.util.system.getResourceColor
@ -668,6 +669,8 @@ class RecentsController(bundle: Bundle? = null) :
}
override fun markAsRead(position: Int) {
val preferences = presenter.preferences
val db = presenter.db
val item = adapter.getItem(position) as? RecentMangaItem ?: return
val chapter = item.chapter
val manga = item.mch.manga
@ -691,11 +694,14 @@ class RecentsController(bundle: Bundle? = null) :
object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
override fun onDismissed(transientBottomBar: Snackbar?, event: Int) {
super.onDismissed(transientBottomBar, event)
if (!undoing && presenter.preferences.removeAfterMarkedAsRead() &&
!wasRead
) {
lastChapterId = chapter.id
presenter.deleteChapter(chapter, manga)
if (!undoing && !wasRead) {
if (preferences.removeAfterMarkedAsRead()) {
lastChapterId = chapter.id
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(
val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
private val chapterFilter: ChapterFilter = Injekt.get()
) : BaseCoroutinePresenter<RecentsController>(), DownloadQueue.DownloadListener, LibraryServiceListener, DownloadServiceListener {

View file

@ -33,9 +33,14 @@ class SettingsTrackingController :
switchPreference {
key = Keys.autoUpdateTrack
titleRes = R.string.sync_chapters_after_reading
titleRes = R.string.update_tracking_after_reading
defaultValue = true
}
switchPreference {
key = Keys.trackMarkedAsRead
titleRes = R.string.update_tracking_marked_read
defaultValue = false
}
preferenceCategory {
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.models.Chapter
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.util.system.isOnline
import eu.kanade.tachiyomi.util.system.launchIO
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
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
@ -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="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="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="only_applies_silent_trackers">Only applies to silent trackers, such as Komga</string>
<string name="reading">Reading</string>