Refactor some tracking-related logic

This commit is contained in:
arkon 2023-08-26 18:30:17 -04:00
parent 6922792ad1
commit dde2f42138
17 changed files with 96 additions and 96 deletions

View file

@ -16,6 +16,7 @@ import eu.kanade.domain.source.interactor.SetMigrateSorting
import eu.kanade.domain.source.interactor.ToggleLanguage
import eu.kanade.domain.source.interactor.ToggleSource
import eu.kanade.domain.source.interactor.ToggleSourcePin
import eu.kanade.domain.track.interactor.RefreshTracks
import eu.kanade.domain.track.interactor.TrackChapter
import tachiyomi.data.category.CategoryRepositoryImpl
import tachiyomi.data.chapter.ChapterRepositoryImpl
@ -113,6 +114,7 @@ class DomainModule : InjektModule {
addSingletonFactory<TrackRepository> { TrackRepositoryImpl(get()) }
addFactory { TrackChapter(get(), get(), get(), get()) }
addFactory { RefreshTracks(get(), get(), get(), get()) }
addFactory { DeleteTrack(get()) }
addFactory { GetTracksPerManga(get()) }
addFactory { GetTracks(get()) }
@ -125,7 +127,7 @@ class DomainModule : InjektModule {
addFactory { SetReadStatus(get(), get(), get(), get()) }
addFactory { ShouldUpdateDbChapter() }
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get()) }
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get()) }
addFactory { SyncChaptersWithTrackServiceTwoWay(get(), get(), get()) }
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
addFactory { GetHistory(get()) }

View file

@ -1,11 +1,12 @@
package eu.kanade.domain.chapter.interactor
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackService
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.toChapterUpdate
import tachiyomi.domain.track.interactor.InsertTrack
import tachiyomi.domain.track.model.Track
@ -13,14 +14,22 @@ import tachiyomi.domain.track.model.Track
class SyncChaptersWithTrackServiceTwoWay(
private val updateChapter: UpdateChapter,
private val insertTrack: InsertTrack,
private val getChapterByMangaId: GetChapterByMangaId,
) {
suspend fun await(
chapters: List<Chapter>,
mangaId: Long,
remoteTrack: Track,
service: TrackService,
) {
val sortedChapters = chapters.sortedBy { it.chapterNumber }
if (service !is EnhancedTrackService) {
return
}
val sortedChapters = getChapterByMangaId.await(mangaId)
.sortedBy { it.chapterNumber }
.filter { it.isRecognizedNumber }
val chapterUpdates = sortedChapters
.filter { chapter -> chapter.chapterNumber <= remoteTrack.lastChapterRead && !chapter.read }
.map { it.copy(read = true).toChapterUpdate() }

View file

@ -0,0 +1,43 @@
package eu.kanade.domain.track.interactor
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.track.TrackManager
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.supervisorScope
import logcat.LogPriority
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
class RefreshTracks(
private val getTracks: GetTracks,
private val trackManager: TrackManager,
private val insertTrack: InsertTrack,
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay,
) {
suspend fun await(mangaId: Long) {
supervisorScope {
getTracks.await(mangaId)
.map { track ->
async {
val service = trackManager.getService(track.syncId)
if (service != null && service.isLoggedIn) {
try {
val updatedTrack = service.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!)
syncChaptersWithTrackServiceTwoWay.await(mangaId, track, service)
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
}
}
.awaitAll()
}
}
}

View file

@ -24,33 +24,31 @@ class TrackChapter(
suspend fun await(context: Context, mangaId: Long, chapterNumber: Double) = coroutineScope {
launchNonCancellable {
val tracks = getTracks.await(mangaId)
if (tracks.isEmpty()) return@launchNonCancellable
tracks.mapNotNull { track ->
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged && chapterNumber > track.lastChapterRead) {
val updatedTrack = track.copy(lastChapterRead = chapterNumber)
if (service == null || !service.isLoggedIn || chapterNumber <= track.lastChapterRead) {
return@mapNotNull null
}
async {
runCatching {
try {
service.update(updatedTrack.toDbTrack(), true)
insertTrack.await(updatedTrack)
} catch (e: Exception) {
delayedTrackingStore.addItem(updatedTrack)
DelayedTrackingUpdateJob.setupTask(context)
throw e
}
val updatedTrack = track.copy(lastChapterRead = chapterNumber)
async {
runCatching {
try {
service.update(updatedTrack.toDbTrack(), true)
insertTrack.await(updatedTrack)
} catch (e: Exception) {
delayedTrackingStore.addItem(updatedTrack)
DelayedTrackingUpdateJob.setupTask(context)
throw e
}
}
} else {
null
}
}
.awaitAll()
.mapNotNull { it.exceptionOrNull() }
.forEach { logcat(LogPriority.INFO, it) }
.forEach { logcat(LogPriority.WARN, it) }
}
}
}

View file

@ -48,7 +48,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
.forEach { track ->
try {
val service = trackManager.getService(track.syncId)
if (service != null && service.isLogged) {
if (service != null && service.isLoggedIn) {
logcat(LogPriority.DEBUG) { "Updating delayed track item: ${track.id}, last chapter read: ${track.lastChapterRead}" }
service.update(track.toDbTrack(), true)
insertTrack.await(track)

View file

@ -164,7 +164,7 @@ internal fun PreferenceItem(
TrackingPreferenceWidget(
service = this,
checked = uName.isNotEmpty(),
onClick = { if (isLogged) item.logout() else item.login() },
onClick = { if (isLoggedIn) item.logout() else item.login() },
)
}
}

View file

@ -135,7 +135,7 @@ object Migrations {
// Force MAL log out due to login flow change
// v52: switched from scraping to WebView
// v53: switched from WebView to OAuth
if (trackManager.myAnimeList.isLogged) {
if (trackManager.myAnimeList.isLoggedIn) {
trackManager.myAnimeList.logout()
context.toast(R.string.myanimelist_relogin)
}

View file

@ -51,7 +51,7 @@ class BackupFileValidator(
.distinct()
val missingTrackers = trackers
.mapNotNull { trackManager.getService(it.toLong()) }
.filter { !it.isLogged }
.filter { !it.isLoggedIn }
.map { context.getString(it.nameRes()) }
.sorted()

View file

@ -15,19 +15,14 @@ import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.interactor.RefreshTracks
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.track.EnhancedTrackService
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
@ -44,7 +39,6 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority
@ -53,7 +47,6 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.NoChaptersException
import tachiyomi.domain.download.service.DownloadPreferences
@ -73,8 +66,6 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.model.SourceNotInstalledException
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.GetTracks
import tachiyomi.domain.track.interactor.InsertTrack
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.io.File
@ -92,17 +83,13 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private val downloadPreferences: DownloadPreferences = Injekt.get()
private val libraryPreferences: LibraryPreferences = Injekt.get()
private val downloadManager: DownloadManager = Injekt.get()
private val trackManager: TrackManager = Injekt.get()
private val coverCache: CoverCache = Injekt.get()
private val getLibraryManga: GetLibraryManga = Injekt.get()
private val getManga: GetManga = Injekt.get()
private val updateManga: UpdateManga = Injekt.get()
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get()
private val getCategories: GetCategories = Injekt.get()
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
private val getTracks: GetTracks = Injekt.get()
private val insertTrack: InsertTrack = Injekt.get()
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get()
private val refreshTracks: RefreshTracks = Injekt.get()
private val setFetchInterval: SetFetchInterval = Injekt.get()
private val notifier = LibraryUpdateNotifier(context)
@ -296,8 +283,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
}
if (libraryPreferences.autoUpdateTrackers().get()) {
val loggedServices = trackManager.services.filter { it.isLogged }
updateTrackings(manga, loggedServices)
refreshTracks.await(manga.id)
}
}
}
@ -417,49 +403,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
private suspend fun updateTrackings() {
coroutineScope {
var progressCount = 0
val loggedServices = trackManager.services.filter { it.isLogged }
mangaToUpdate.forEach { libraryManga ->
val manga = libraryManga.manga
ensureActive()
val manga = libraryManga.manga
notifier.showProgressNotification(listOf(manga), progressCount++, mangaToUpdate.size)
// Update the tracking details.
updateTrackings(manga, loggedServices)
refreshTracks.await(manga.id)
}
notifier.cancelProgressNotification()
}
}
private suspend fun updateTrackings(manga: Manga, loggedServices: List<TrackService>) {
getTracks.await(manga.id)
.map { track ->
supervisorScope {
async {
val service = trackManager.getService(track.syncId)
if (service != null && service in loggedServices) {
try {
val updatedTrack = service.refresh(track.toDbTrack())
insertTrack.await(updatedTrack.toDomainTrack()!!)
if (service is EnhancedTrackService) {
val chapters = getChapterByMangaId.await(manga.id)
syncChaptersWithTrackServiceTwoWay.await(chapters, track, service)
}
} catch (e: Throwable) {
// Ignore errors and continue
logcat(LogPriority.ERROR, e)
}
}
}
}
}
.awaitAll()
}
private suspend fun withUpdateNotification(
updatingManga: CopyOnWriteArrayList<Manga>,
completed: AtomicInteger,

View file

@ -39,5 +39,5 @@ class TrackManager(context: Context) {
fun getService(id: Long) = services.find { it.id == id }
fun hasLoggedServices() = services.any { it.isLogged }
fun hasLoggedServices() = services.any { it.isLoggedIn }
}

View file

@ -33,6 +33,7 @@ abstract class TrackService(val id: Long) {
val trackPreferences: TrackPreferences by injectLazy()
val networkService: NetworkHelper by injectLazy()
private val insertTrack: InsertTrack by injectLazy()
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay by injectLazy()
open val client: OkHttpClient
get() = networkService.client
@ -89,7 +90,7 @@ abstract class TrackService(val id: Long) {
trackPreferences.setTrackCredentials(this, "", "")
}
open val isLogged: Boolean
open val isLoggedIn: Boolean
get() = getUsername().isNotEmpty() &&
getPassword().isNotEmpty()
@ -101,6 +102,7 @@ abstract class TrackService(val id: Long) {
trackPreferences.setTrackCredentials(this, username, password)
}
// TODO: move this to an interactor, and update all trackers based on common data
suspend fun registerTracking(item: Track, mangaId: Long) {
item.manga_id = mangaId
try {
@ -113,6 +115,7 @@ abstract class TrackService(val id: Long) {
insertTrack.await(track)
// TODO: merge into SyncChaptersWithTrackServiceTwoWay?
// Update chapter progress if newer chapters marked read locally
if (hasReadChapters) {
val latestLocalReadChapterNumber = allChapters
@ -144,9 +147,7 @@ abstract class TrackService(val id: Long) {
}
}
if (this is EnhancedTrackService) {
Injekt.get<SyncChaptersWithTrackServiceTwoWay>().await(allChapters, track, this@TrackService)
}
syncChaptersWithTrackServiceTwoWay.await(mangaId, track, this@TrackService)
}
} catch (e: Throwable) {
withUIContext { Injekt.get<Application>().toast(e.message) }

View file

@ -45,7 +45,6 @@ import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.SetMangaCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.chapter.interactor.GetChapterByMangaId
import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga
@ -72,7 +71,6 @@ class BrowseSourceScreenModel(
private val getRemoteManga: GetRemoteManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
@ -82,7 +80,7 @@ class BrowseSourceScreenModel(
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
) : StateScreenModel<BrowseSourceScreenModel.State>(State(Listing.valueOf(listingQuery))) {
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLogged } }
private val loggedServices by lazy { Injekt.get<TrackManager>().services.filter { it.isLoggedIn } }
var displayMode by sourcePreferences.sourceDisplayMode().asState(coroutineScope)
@ -299,8 +297,7 @@ class BrowseSourceScreenModel(
(service as TrackService).bind(track)
insertTrack.await(track.toDomainTrack()!!)
val chapters = getChapterByMangaId.await(manga.id)
syncChaptersWithTrackServiceTwoWay.await(chapters, track.toDomainTrack()!!, service)
syncChaptersWithTrackServiceTwoWay.await(manga.id, track.toDomainTrack()!!, service)
}
} catch (e: Exception) {
logcat(LogPriority.WARN, e) { "Could not match manga: ${manga.title} with service $service" }

View file

@ -366,7 +366,7 @@ class LibraryScreenModel(
* @return map of track id with the filter value
*/
private fun getTrackingFilterFlow(): Flow<Map<Long, TriState>> {
val loggedServices = trackManager.services.filter { it.isLogged }
val loggedServices = trackManager.services.filter { it.isLoggedIn }
return if (loggedServices.isNotEmpty()) {
val prefFlows = loggedServices
.map { libraryPreferences.filterTracking(it.id.toInt()).changes() }

View file

@ -26,7 +26,7 @@ class LibrarySettingsScreenModel(
) : ScreenModel {
val trackServices
get() = trackManager.services.filter { it.isLogged }
get() = trackManager.services.filter { it.isLoggedIn }
fun toggleFilter(preference: (LibraryPreferences) -> Preference<TriState>) {
preference(libraryPreferences).getAndSet {

View file

@ -105,7 +105,7 @@ class MangaScreenModel(
private val successState: State.Success?
get() = state.value as? State.Success
private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
private val loggedServices by lazy { trackManager.services.filter { it.isLoggedIn } }
val manga: Manga?
get() = successState?.manga

View file

@ -71,7 +71,6 @@ import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.domain.track.interactor.DeleteTrack
import tachiyomi.domain.track.interactor.GetTracks
@ -218,8 +217,7 @@ data class TrackInfoDialogHomeScreen(
private suspend fun refreshTrackers() {
val insertTrack = Injekt.get<InsertTrack>()
val getMangaWithChapters = Injekt.get<GetMangaWithChapters>()
val syncTwoWayService = Injekt.get<SyncChaptersWithTrackServiceTwoWay>()
val syncChaptersWithTrackServiceTwoWay = Injekt.get<SyncChaptersWithTrackServiceTwoWay>()
val context = Injekt.get<Application>()
try {
@ -229,11 +227,7 @@ data class TrackInfoDialogHomeScreen(
val track = trackItem.track ?: continue
val domainTrack = trackItem.service.refresh(track.toDbTrack()).toDomainTrack() ?: continue
insertTrack.await(domainTrack)
if (trackItem.service is EnhancedTrackService) {
val allChapters = getMangaWithChapters.awaitChapters(mangaId)
syncTwoWayService.await(allChapters, domainTrack, trackItem.service)
}
syncChaptersWithTrackServiceTwoWay.await(mangaId, domainTrack, trackItem.service)
} catch (e: Exception) {
logcat(
LogPriority.ERROR,
@ -257,7 +251,7 @@ data class TrackInfoDialogHomeScreen(
}
private fun List<Track>.mapToTrackItem(): List<TrackItem> {
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLogged }
val loggedServices = Injekt.get<TrackManager>().services.filter { it.isLoggedIn }
val source = Injekt.get<SourceManager>().getOrStub(sourceId)
return loggedServices
// Map to TrackItem

View file

@ -36,7 +36,7 @@ class StatsScreenModel(
private val trackManager: TrackManager = Injekt.get(),
) : StateScreenModel<StatsScreenState>(StatsScreenState.Loading) {
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLogged } }
private val loggedServices by lazy { trackManager.services.fastFilter { it.isLoggedIn } }
init {
coroutineScope.launchIO {