From 80541831bb02f893f4ff56670a738b25452ea17d Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Thu, 18 Jun 2026 17:31:52 +0600 Subject: [PATCH] Revert "Use app scoped CoroutineScope (#3403)" This reverts commit 509eee5dfb81266ea7056587f695910506248a8a. Fixes #3429 --- .../tachiyomi/data/download/DownloadCache.kt | 9 ++--- .../data/download/DownloadManager.kt | 8 ++--- .../tachiyomi/data/download/Downloader.kt | 8 +++-- .../data/library/LibraryUpdateNotifier.kt | 6 ++-- .../data/notification/NotificationReceiver.kt | 28 +++------------ .../java/eu/kanade/tachiyomi/di/AppModule.kt | 19 ++++------ .../tachiyomi/extension/ExtensionManager.kt | 7 ++-- .../extension/util/ExtensionInstaller.kt | 8 +++-- .../tachiyomi/source/AndroidSourceManager.kt | 15 ++++---- .../tachiyomi/ui/reader/ReaderViewModel.kt | 2 +- .../ui/reader/loader/ChapterLoader.kt | 4 +-- .../ui/reader/loader/HttpPageLoader.kt | 12 ++++--- .../kanade/tachiyomi/network/NetworkHelper.kt | 4 +-- .../interceptor/CloudflareInterceptor.kt | 4 +-- .../network/interceptor/WebViewInterceptor.kt | 6 ++-- .../common/util/lang/CoroutinesExtensions.kt | 36 +++++++++++++++++++ .../domain/storage/service/StorageManager.kt | 7 ++-- 17 files changed, 96 insertions(+), 87 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt index 9b08a310a..abc60e9ce 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadCache.kt @@ -19,13 +19,13 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.first -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withTimeoutOrNull @@ -63,16 +63,17 @@ import kotlin.time.Duration.Companion.seconds */ class DownloadCache( private val context: Context, - private val scope: CoroutineScope, private val provider: DownloadProvider = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(), private val storageManager: StorageManager = Injekt.get(), ) { + + private val scope = CoroutineScope(Dispatchers.IO) + private val _changes: Channel = Channel(Channel.UNLIMITED) val changes = _changes.receiveAsFlow() .onStart { emit(Unit) } - .flowOn(Dispatchers.IO) .shareIn(scope, SharingStarted.Lazily, 1) /** @@ -100,7 +101,7 @@ class DownloadCache( init { // Attempt to read cache file - scope.launchIO { + scope.launch { rootDownloadsDirMutex.withLock { try { if (diskCacheFile.exists()) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 3e7adc617..8255b22c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -4,7 +4,6 @@ import android.content.Context import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.Page -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.drop @@ -36,7 +35,6 @@ import uy.kohesive.injekt.api.get */ class DownloadManager( private val context: Context, - private val scope: CoroutineScope, private val provider: DownloadProvider = Injekt.get(), private val cache: DownloadCache = Injekt.get(), private val getCategories: GetCategories = Injekt.get(), @@ -47,7 +45,7 @@ class DownloadManager( /** * Downloader whose only task is to download chapters. */ - private val downloader = Downloader(context, provider, cache, scope) + private val downloader = Downloader(context, provider, cache) val isRunning: Boolean get() = downloader.isRunning @@ -223,7 +221,7 @@ class DownloadManager( * @param source the source of the chapters. */ fun deleteChapters(chapters: List, manga: Manga, source: Source) { - scope.launchIO { + launchIO { val filteredChapters = getChaptersToDelete(chapters, manga) if (filteredChapters.isEmpty()) { return@launchIO @@ -250,7 +248,7 @@ class DownloadManager( * @param removeQueued whether to also remove queued downloads. */ fun deleteManga(manga: Manga, source: Source, removeQueued: Boolean = true) { - scope.launchIO { + launchIO { if (removeQueued) { downloader.removeFromQueue(manga) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 4a8b618ec..ebcfb58ab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -18,6 +18,7 @@ import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.async import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow @@ -43,6 +44,7 @@ import okhttp3.Response import tachiyomi.core.common.i18n.stringResource import tachiyomi.core.common.storage.extension import tachiyomi.core.common.util.lang.launchIO +import tachiyomi.core.common.util.lang.launchNow import tachiyomi.core.common.util.lang.withIOContext import tachiyomi.core.common.util.system.ImageUtil import tachiyomi.core.common.util.system.logcat @@ -70,7 +72,6 @@ class Downloader( private val context: Context, private val provider: DownloadProvider, private val cache: DownloadCache, - private val scope: CoroutineScope, private val sourceManager: SourceManager = Injekt.get(), private val chapterCache: ChapterCache = Injekt.get(), private val downloadPreferences: DownloadPreferences = Injekt.get(), @@ -95,6 +96,7 @@ class Downloader( */ private val notifier by lazy { DownloadNotifier(context) } + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private var downloaderJob: Job? = null /** @@ -110,7 +112,7 @@ class Downloader( var isPaused: Boolean = false init { - scope.launch { + launchNow { val chapters = async { store.restore() } addAllToQueue(chapters.await()) } @@ -189,7 +191,7 @@ class Downloader( private fun launchDownloaderJob() { if (isRunning) return - downloaderJob = scope.launchIO { + downloaderJob = scope.launch { val activeDownloadsFlow = combine( queueState, downloadPreferences.parallelSourceLimit.changes(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 3377040ea..b7f60fdd8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -28,11 +28,10 @@ import eu.kanade.tachiyomi.util.system.cancelNotification import eu.kanade.tachiyomi.util.system.getBitmapOrNull import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notify -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import tachiyomi.core.common.Constants import tachiyomi.core.common.i18n.pluralStringResource import tachiyomi.core.common.i18n.stringResource +import tachiyomi.core.common.util.lang.launchUI import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.library.model.LibraryManga import tachiyomi.domain.manga.model.Manga @@ -45,7 +44,6 @@ import java.text.NumberFormat class LibraryUpdateNotifier( private val context: Context, - private val scope: CoroutineScope = Injekt.get(), private val securityPreferences: SecurityPreferences = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), ) { @@ -210,7 +208,7 @@ class LibraryUpdateNotifier( // Per-manga notification if (!securityPreferences.hideNotificationContent.get()) { - scope.launch { + launchUI { context.notify( updates.map { (manga, chapters) -> NotificationManagerCompat.NotificationWithIdAndTag( diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 1d29e8b6d..e23b3f78c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -17,12 +17,9 @@ import eu.kanade.tachiyomi.util.system.getParcelableExtraCompat import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import tachiyomi.core.common.Constants +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.chapter.interactor.GetChapter import tachiyomi.domain.chapter.interactor.UpdateChapter import tachiyomi.domain.chapter.model.Chapter @@ -35,8 +32,6 @@ import tachiyomi.i18n.MR import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID /** @@ -50,7 +45,6 @@ class NotificationReceiver : BroadcastReceiver() { private val getChapter: GetChapter by injectLazy() private val updateChapter: UpdateChapter by injectLazy() private val downloadManager: DownloadManager by injectLazy() - private val scope: CoroutineScope by injectLazy() override fun onReceive(context: Context, intent: Intent) { when (intent.action) { @@ -203,7 +197,7 @@ class NotificationReceiver : BroadcastReceiver() { val downloadPreferences: DownloadPreferences = Injekt.get() val sourceManager: SourceManager = Injekt.get() - async(scope, Dispatchers.IO) { + launchIO { val toUpdate = chapterUrls.mapNotNull { getChapter.await(it, mangaId) } .map { val chapter = it.copy(read = true) @@ -229,27 +223,13 @@ class NotificationReceiver : BroadcastReceiver() { * @param mangaId id of manga */ private fun downloadChapters(chapterUrls: Array, mangaId: Long) { - async(scope, Dispatchers.IO) { - val manga = getManga.await(mangaId) ?: return@async + launchIO { + val manga = getManga.await(mangaId) ?: return@launchIO val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId) } downloadManager.downloadChapters(manga, chapters) } } - private fun BroadcastReceiver.async( - scope: CoroutineScope, - context: CoroutineContext = EmptyCoroutineContext, - start: CoroutineStart = CoroutineStart.DEFAULT, - block: suspend CoroutineScope.() -> Unit, - ) { - val result = goAsync() - try { - scope.launch(context, start, block) - } finally { - result.finish() - } - } - companion object { private const val NAME = "NotificationReceiver" diff --git a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt index e4e0b92b4..3fab59d97 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/di/AppModule.kt @@ -2,8 +2,6 @@ package eu.kanade.tachiyomi.di import android.app.Application import androidx.core.content.ContextCompat -import androidx.lifecycle.ProcessLifecycleOwner -import androidx.lifecycle.lifecycleScope import androidx.sqlite.driver.bundled.BundledSQLiteDriver import app.cash.sqldelight.db.SqlDriver import com.eygraber.sqldelight.androidx.driver.AndroidxSqliteConfiguration @@ -22,9 +20,6 @@ import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.network.JavaScriptEngine import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.source.AndroidSourceManager -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.plus import kotlinx.serialization.json.Json import kotlinx.serialization.protobuf.ProtoBuf import nl.adaptivity.xmlutil.XmlDeclMode @@ -112,20 +107,18 @@ class AppModule(val app: Application) : InjektModule { ProtoBuf } - addSingletonFactory { ProcessLifecycleOwner.get().lifecycleScope + SupervisorJob() } - addSingletonFactory { ChapterCache(app, get()) } addSingletonFactory { CoverCache(app) } - addSingletonFactory { NetworkHelper(app, get(), get()) } + addSingletonFactory { NetworkHelper(app, get()) } addSingletonFactory { JavaScriptEngine(app) } - addSingletonFactory { AndroidSourceManager(app, get(), get(), get()) } - addSingletonFactory { ExtensionManager(app, get()) } + addSingletonFactory { AndroidSourceManager(app, get(), get()) } + addSingletonFactory { ExtensionManager(app) } addSingletonFactory { DownloadProvider(app) } - addSingletonFactory { DownloadManager(app, get()) } - addSingletonFactory { DownloadCache(app, get()) } + addSingletonFactory { DownloadManager(app) } + addSingletonFactory { DownloadCache(app) } addSingletonFactory { TrackerManager() } addSingletonFactory { DelayedTrackingStore(app) } @@ -135,7 +128,7 @@ class AppModule(val app: Application) : InjektModule { addSingletonFactory { AndroidStorageFolderProvider(app) } addSingletonFactory { LocalSourceFileSystem(get()) } addSingletonFactory { LocalCoverManager(app, get()) } - addSingletonFactory { StorageManager(app, get(), get()) } + addSingletonFactory { StorageManager(app, get()) } // Asynchronously init expensive components for a faster cold start ContextCompat.getMainExecutor(app).execute { diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 042a08d3c..4f9856ccd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.extension.util.ExtensionInstaller import eu.kanade.tachiyomi.extension.util.ExtensionLoader import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted @@ -40,10 +41,12 @@ import java.util.Locale */ class ExtensionManager( private val context: Context, - private val scope: CoroutineScope, private val preferences: SourcePreferences = Injekt.get(), private val trustExtension: TrustExtension = Injekt.get(), ) { + + val scope = CoroutineScope(SupervisorJob()) + private val _isInitialized = MutableStateFlow(false) val isInitialized: StateFlow = _isInitialized.asStateFlow() @@ -55,7 +58,7 @@ class ExtensionManager( /** * The installer which installs, updates and uninstalls the extensions. */ - private val installer by lazy { ExtensionInstaller(context, scope) } + private val installer by lazy { ExtensionInstaller(context) } private val iconMap = mutableMapOf() diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt index 0b3e06fed..5eae3fe47 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt @@ -12,15 +12,16 @@ import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.isPackageInstalled import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.onCompletion +import kotlinx.coroutines.launch import logcat.LogPriority import okhttp3.OkHttpClient import okhttp3.Request -import tachiyomi.core.common.util.lang.launchIO import tachiyomi.core.common.util.system.logcat import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -33,8 +34,9 @@ import java.io.File */ internal class ExtensionInstaller( private val context: Context, - private val scope: CoroutineScope, ) { + + private val scope = CoroutineScope(Dispatchers.IO) private val activeJobs = mutableMapOf() private val activeSteps = mutableMapOf>() private val extensionInstaller = Injekt.get().extensionInstaller @@ -55,7 +57,7 @@ internal class ExtensionInstaller( val step = MutableStateFlow(InstallStep.Pending) activeSteps[downloadId] = step - val job = scope.launchIO { + val job = scope.launch { val tmpFile = File(context.cacheDir, "extension_${extension.pkgName}.apk") try { step.value = InstallStep.Downloading diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt index 0682b8cf3..6fcc999e6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/AndroidSourceManager.kt @@ -5,14 +5,16 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.source.online.HttpSource import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking -import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.source.model.StubSource import tachiyomi.domain.source.repository.StubSourceRepository import tachiyomi.domain.source.service.SourceManager @@ -24,7 +26,6 @@ import java.util.concurrent.ConcurrentHashMap class AndroidSourceManager( private val context: Context, - private val scope: CoroutineScope, private val extensionManager: ExtensionManager, private val sourceRepository: StubSourceRepository, ) : SourceManager { @@ -34,6 +35,8 @@ class AndroidSourceManager( private val downloadManager: DownloadManager by injectLazy() + private val scope = CoroutineScope(Job() + Dispatchers.IO) + private val sourcesMapFlow = MutableStateFlow(ConcurrentHashMap()) private val stubSourcesMap = ConcurrentHashMap() @@ -41,7 +44,7 @@ class AndroidSourceManager( override val sources: Flow> = sourcesMapFlow.map { it.values.toList() } init { - scope.launchIO { + scope.launch { extensionManager.installedExtensionsFlow .collectLatest { extensions -> val mutableMap = ConcurrentHashMap( @@ -64,7 +67,7 @@ class AndroidSourceManager( } } - scope.launchIO { + scope.launch { sourceRepository.subscribeAll() .collectLatest { sources -> val mutableMap = stubSourcesMap.toMutableMap() @@ -95,9 +98,9 @@ class AndroidSourceManager( } private fun registerStubSource(source: StubSource) { - scope.launchIO { + scope.launch { val dbSource = sourceRepository.getStubSource(source.id) - if (dbSource == source) return@launchIO + if (dbSource == source) return@launch sourceRepository.upsertStubSource(source.id, source.lang, source.name) if (dbSource != null) { downloadManager.renameSource(dbSource, source) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 5bbf85878..4c227636a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -286,7 +286,7 @@ class ReaderViewModel @JvmOverloads constructor( val context = Injekt.get() val source = sourceManager.getOrStub(manga.source) - loader = ChapterLoader(context, viewModelScope, downloadManager, downloadProvider, manga, source) + loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source) loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id }) Result.success(true) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt index e091744a9..761c212a1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/ChapterLoader.kt @@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.data.download.DownloadProvider import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter -import kotlinx.coroutines.CoroutineScope import mihon.core.archive.archiveReader import mihon.core.archive.epubReader import tachiyomi.core.common.i18n.stringResource @@ -23,7 +22,6 @@ import tachiyomi.source.local.io.Format */ class ChapterLoader( private val context: Context, - private val scope: CoroutineScope, private val downloadManager: DownloadManager, private val downloadProvider: DownloadProvider, private val manga: Manga, @@ -102,7 +100,7 @@ class ChapterLoader( is Format.Epub -> EpubPageLoader(format.file.epubReader(context)) } } - source is HttpSource -> HttpPageLoader(chapter, source, scope) + source is HttpSource -> HttpPageLoader(chapter, source) source is StubSource -> error(context.stringResource(MR.strings.source_not_installed, source.toString())) else -> error(context.stringResource(MR.strings.loader_not_implemented_error)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt index f89eddd55..81530afa3 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/loader/HttpPageLoader.kt @@ -8,8 +8,9 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runInterruptible @@ -30,10 +31,11 @@ import kotlin.math.min internal class HttpPageLoader( private val chapter: ReaderChapter, private val source: HttpSource, - scope: CoroutineScope, private val chapterCache: ChapterCache = Injekt.get(), ) : PageLoader() { + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO) + /** * A queue used to manage requests one by one while allowing priorities. */ @@ -122,14 +124,14 @@ internal class HttpPageLoader( queue.offer(PriorityPage(page, PriorityPage.RETRY)) } - @OptIn(DelicateCoroutinesApi::class) override fun recycle() { super.recycle() + scope.cancel() queue.clear() // Cache current page list progress for online chapters to allow a faster reopen chapter.pages?.let { pages -> - GlobalScope.launchIO { + launchIO { try { // Convert to pages without reader information val pagesToSave = pages.map { Page(it.index, it.url, it.imageUrl) } diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index b99553b38..24770bbd6 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -5,7 +5,6 @@ import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.IgnoreGzipInterceptor import eu.kanade.tachiyomi.network.interceptor.UncaughtExceptionInterceptor import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor -import kotlinx.coroutines.CoroutineScope import okhttp3.Cache import okhttp3.OkHttpClient import okhttp3.brotli.BrotliInterceptor @@ -16,7 +15,6 @@ import java.util.concurrent.TimeUnit class NetworkHelper( private val context: Context, private val preferences: NetworkPreferences, - scope: CoroutineScope, ) { val cookieJar = AndroidCookieJar() @@ -64,7 +62,7 @@ class NetworkHelper( val client = clientBuilder .addInterceptor( - CloudflareInterceptor(context, cookieJar, scope, ::defaultUserAgentProvider), + CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider), ) .build() diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt index b55209550..517679aca 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/CloudflareInterceptor.kt @@ -11,7 +11,6 @@ import androidx.core.content.ContextCompat import eu.kanade.tachiyomi.network.AndroidCookieJar import eu.kanade.tachiyomi.util.system.isOutdated import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.CoroutineScope import okhttp3.Cookie import okhttp3.HttpUrl.Companion.toHttpUrl import okhttp3.Interceptor @@ -26,9 +25,8 @@ import java.util.concurrent.CountDownLatch class CloudflareInterceptor( private val context: Context, private val cookieManager: AndroidCookieJar, - scope: CoroutineScope, defaultUserAgentProvider: () -> String, -) : WebViewInterceptor(context, scope, defaultUserAgentProvider) { +) : WebViewInterceptor(context, defaultUserAgentProvider) { private val executor = ContextCompat.getMainExecutor(context) diff --git a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt index ef41a1ea2..1223d51a4 100644 --- a/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt +++ b/core/common/src/main/kotlin/eu/kanade/tachiyomi/network/interceptor/WebViewInterceptor.kt @@ -9,12 +9,11 @@ import eu.kanade.tachiyomi.util.system.DeviceUtil import eu.kanade.tachiyomi.util.system.WebViewUtil import eu.kanade.tachiyomi.util.system.setDefaultSettings import eu.kanade.tachiyomi.util.system.toast -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch import okhttp3.Headers import okhttp3.Interceptor import okhttp3.Request import okhttp3.Response +import tachiyomi.core.common.util.lang.launchUI import tachiyomi.i18n.MR import java.util.Locale import java.util.concurrent.CountDownLatch @@ -22,7 +21,6 @@ import java.util.concurrent.TimeUnit abstract class WebViewInterceptor( private val context: Context, - private val scope: CoroutineScope, private val defaultUserAgentProvider: () -> String, ) : Interceptor { @@ -58,7 +56,7 @@ abstract class WebViewInterceptor( } if (!WebViewUtil.supportsWebView(context)) { - scope.launch { + launchUI { context.toast(MR.strings.information_webview_required, Toast.LENGTH_LONG) } return response diff --git a/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt b/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt index 07421fc25..a76bff2cc 100644 --- a/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt +++ b/core/common/src/main/kotlin/tachiyomi/core/common/util/lang/CoroutinesExtensions.kt @@ -1,12 +1,48 @@ package tachiyomi.core.common.util.lang import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +/** + * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. + * + * **Possible replacements** + * - suspend function + * - custom scope like view or presenter scope + */ +@DelicateCoroutinesApi +fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = + GlobalScope.launch(Dispatchers.Main, CoroutineStart.DEFAULT, block) + +/** + * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. + * + * **Possible replacements** + * - suspend function + * - custom scope like view or presenter scope + */ +@DelicateCoroutinesApi +fun launchIO(block: suspend CoroutineScope.() -> Unit): Job = + GlobalScope.launch(Dispatchers.IO, CoroutineStart.DEFAULT, block) + +/** + * Think twice before using this. This is a delicate API. It is easy to accidentally create resource or memory leaks when GlobalScope is used. + * + * **Possible replacements** + * - suspend function + * - custom scope like view or presenter scope + */ +@DelicateCoroutinesApi +fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = + GlobalScope.launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED, block) + fun CoroutineScope.launchUI(block: suspend CoroutineScope.() -> Unit): Job = launch(Dispatchers.Main, block = block) diff --git a/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt b/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt index 7a2624096..002d976fb 100644 --- a/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt +++ b/domain/src/main/java/tachiyomi/domain/storage/service/StorageManager.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow @@ -18,14 +17,15 @@ import kotlinx.coroutines.flow.shareIn class StorageManager( private val context: Context, - scope: CoroutineScope, storagePreferences: StoragePreferences, ) { + + private val scope = CoroutineScope(Dispatchers.IO) + private var baseDir: UniFile? = getBaseDir(storagePreferences.baseStorageDirectory.get()) private val _changes: Channel = Channel(Channel.UNLIMITED) val changes = _changes.receiveAsFlow() - .flowOn(Dispatchers.IO) .shareIn(scope, SharingStarted.Lazily, 1) init { @@ -43,7 +43,6 @@ class StorageManager( } _changes.send(Unit) } - .flowOn(Dispatchers.IO) .launchIn(scope) }