浏览代码

Refactor some tracking-related logic

arkon 1 年之前
父节点
当前提交
dde2f42138

+ 3 - 1
app/src/main/java/eu/kanade/domain/DomainModule.kt

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

+ 12 - 3
app/src/main/java/eu/kanade/domain/chapter/interactor/SyncChaptersWithTrackServiceTwoWay.kt

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

+ 43 - 0
app/src/main/java/eu/kanade/domain/track/interactor/RefreshTracks.kt

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

+ 14 - 16
app/src/main/java/eu/kanade/domain/track/interactor/TrackChapter.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/domain/track/service/DelayedTrackingUpdateJob.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/presentation/more/settings/PreferenceItem.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/Migrations.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/backup/BackupFileValidator.kt

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

+ 5 - 49
app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackManager.kt

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

+ 5 - 4
app/src/main/java/eu/kanade/tachiyomi/data/track/TrackService.kt

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

+ 2 - 5
app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryScreenModel.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsScreenModel.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt

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

+ 3 - 9
app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackInfoDialog.kt

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

+ 1 - 1
app/src/main/java/eu/kanade/tachiyomi/ui/stats/StatsScreenModel.kt

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