From 0d67d49fc9cee25f805571618ae6666ea3863ead Mon Sep 17 00:00:00 2001 From: Jays2Kings Date: Sun, 22 Aug 2021 23:30:27 -0400 Subject: [PATCH] Minor updates to extension installs downloadsStateFlow now uses pkgName instead of downloadId Suppress downloadId warnings ExtensionInstallJob now sets all pending extensions into "Pending" State On load, extensions list now gets the current status of updating/installing extensions Emit a finish to all pending extension on job cancel --- .../extension/ExtensionInstallService.kt | 12 ++++ .../tachiyomi/extension/ExtensionManager.kt | 48 ++++++++++++++- .../extension/util/ExtensionInstaller.kt | 58 ++++++++++++------- .../ui/extension/ExtensionBottomPresenter.kt | 26 ++++++++- 4 files changed, 119 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionInstallService.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionInstallService.kt index 03074dfbc3..b0f1921c90 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionInstallService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionInstallService.kt @@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.extension.ExtensionManager.ExtensionInfo import eu.kanade.tachiyomi.extension.model.Extension +import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.toast import kotlinx.coroutines.CoroutineScope @@ -49,6 +50,8 @@ class ExtensionInstallService( private val preferences: PreferencesHelper = Injekt.get() + private var activeInstalls = listOf() + /** * This method needs to be implemented, but it's not used/needed. */ @@ -85,6 +88,11 @@ class ExtensionInstallService( ) < it.versionCode } ?: return START_NOT_STICKY + + activeInstalls = list.map { it.pkgName } + serviceScope.launch { + list.forEach { extensionManager.setPending(it.pkgName) } + } var installed = 0 val installedExtensions = mutableListOf() job = serviceScope.launch { @@ -143,6 +151,8 @@ class ExtensionInstallService( override fun onDestroy() { job?.cancel() serviceScope.cancel() + activeInstalls.forEach { extensionManager.cleanUpInstallation(it) } + extensionManager.downloadRelay.tryEmit("Finished" to (InstallStep.Installed to null)) if (instance == this) { instance = null } @@ -166,6 +176,8 @@ class ExtensionInstallService( context.stopService(Intent(context, ExtensionUpdateJob::class.java)) } + fun activeInstalls(): List? = instance?.activeInstalls + /** * Returns the status of the service. * 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 4ed3ed4b27..e89fd9def4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi import eu.kanade.tachiyomi.extension.model.Extension +import eu.kanade.tachiyomi.extension.model.InstallStep import eu.kanade.tachiyomi.extension.model.LoadResult import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver import eu.kanade.tachiyomi.extension.util.ExtensionInstaller @@ -54,7 +55,7 @@ class ExtensionManager( val downloadRelay get() = installer.downloadsStateFlow - fun getExtension(downloadId: Long): String? { + private fun getExtension(downloadId: Long): String? { return installer.activeDownloads.entries.find { downloadId == it.value }?.key } @@ -266,7 +267,16 @@ class ExtensionManager( * @param result Whether the extension was installed or not. */ fun setInstallationResult(downloadId: Long, result: Boolean) { - installer.setInstallationResult(downloadId, result) + val pkgName = getExtension(downloadId) ?: return + setInstallationResult(pkgName, result) + } + + fun cleanUpInstallation(pkgName: String) { + installer.cleanUpInstallation(pkgName) + } + + fun setInstallationResult(pkgName: String, result: Boolean) { + installer.setInstallationResult(pkgName, result) } /** Sets the result of the installation of an extension. @@ -284,7 +294,39 @@ class ExtensionManager( * @param downloadId The id of the download. */ fun setInstalling(downloadId: Long, sessionId: Int) { - installer.setInstalling(downloadId, sessionId) + val pkgName = getExtension(downloadId) ?: return + setInstalling(pkgName, sessionId) + } + + fun setInstalling(pkgName: String, sessionId: Int) { + installer.setInstalling(pkgName, sessionId) + } + + suspend fun setPending(pkgName: String) { + installer.setPending(pkgName) + } + + fun getInstallInfo(pkgName: String): ExtensionIntallInfo? { + val installStep = when { + installer.downloadInstallerMap[pkgName] != null && + context.packageManager.packageInstaller + .getSessionInfo(installer.downloadInstallerMap[pkgName] ?: 0) != null -> { + InstallStep.Installing + } + installer.activeDownloads[pkgName] != null -> InstallStep.Downloading + ExtensionInstallService.activeInstalls() + ?.contains(pkgName) == true -> InstallStep.Pending + else -> return null + } + val sessionInfo = run { + val sessionId = installer.downloadInstallerMap[pkgName] + if (sessionId != null) { + context.packageManager.packageInstaller.getSessionInfo(sessionId) + } else { + null + } + } + return ExtensionIntallInfo(installStep, sessionInfo) } /** 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 09f9c49948..afbd6d92c4 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 @@ -1,5 +1,6 @@ package eu.kanade.tachiyomi.extension.util +import android.annotation.SuppressLint import android.app.DownloadManager import android.content.BroadcastReceiver import android.content.Context @@ -63,10 +64,10 @@ internal class ExtensionInstaller(private val context: Context) { /** * StateFlow used to notify the installation step of every download. */ - val downloadsStateFlow = MutableStateFlow(0L to ExtensionIntallInfo(InstallStep.Pending, null)) + val downloadsStateFlow = MutableStateFlow("" to ExtensionIntallInfo(InstallStep.Pending, null)) /** Map of download id to installer session id */ - val downloadInstallerMap = hashMapOf() + val downloadInstallerMap = hashMapOf() /** * Adds the given extension to the downloads queue and returns a flow containing its @@ -103,7 +104,7 @@ internal class ExtensionInstaller(private val context: Context) { scope.launch { flowOf( pollStatus(id), - pollInstallStatus(id) + pollInstallStatus(pkgName) ).flattenMerge() .transformWhile { emit(it) @@ -118,11 +119,11 @@ internal class ExtensionInstaller(private val context: Context) { deleteDownload(pkgName) } .collect { - downloadsStateFlow.emit(id to it) + downloadsStateFlow.emit(extension.pkgName to it) } } - return downloadsStateFlow.filter { it.first == id }.map { it.second } + return downloadsStateFlow.filter { it.first == extension.pkgName }.map { it.second } .flowOn(Dispatchers.IO) .transformWhile { emit(it) @@ -139,6 +140,7 @@ internal class ExtensionInstaller(private val context: Context) { * * @param id The id of the download to poll. */ + @SuppressLint("Range") private fun pollStatus(id: Long): Flow { val query = DownloadManager.Query().setFilterById(id) @@ -176,12 +178,12 @@ internal class ExtensionInstaller(private val context: Context) { * Returns a flow that polls the given installer session for its status every half second, as the * manager doesn't have any notification system. This will only stop once * - * @param id The id of the download mapped to the session to poll. + * @param pkgName The pkgName of the download mapped to the session to poll. */ - private fun pollInstallStatus(id: Long): Flow { + private fun pollInstallStatus(pkgName: String): Flow { return flow { while (true) { - val sessionId = downloadInstallerMap[id] + val sessionId = downloadInstallerMap[pkgName] if (sessionId != null) { val session = context.packageManager.packageInstaller.getSessionInfo(sessionId) @@ -191,7 +193,7 @@ internal class ExtensionInstaller(private val context: Context) { } } .takeWhile { info -> - val sessionId = downloadInstallerMap[id] + val sessionId = downloadInstallerMap[pkgName] if (sessionId != null) { info.second != null } else { @@ -250,15 +252,29 @@ internal class ExtensionInstaller(private val context: Context) { * * @param downloadId The id of the download. */ - fun setInstalling(downloadId: Long, sessionId: Int) { - downloadsStateFlow.tryEmit(downloadId to ExtensionIntallInfo(InstallStep.Installing, null)) - downloadInstallerMap[downloadId] = sessionId + fun setInstalling(pkgName: String, sessionId: Int) { + downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(InstallStep.Installing, null)) + downloadInstallerMap[pkgName] = sessionId + } + + suspend fun setPending(pkgName: String) { + downloadsStateFlow.emit(pkgName to ExtensionIntallInfo(InstallStep.Pending, null)) } fun cancelInstallation(sessionId: Int) { val downloadId = downloadInstallerMap.entries.find { it.value == sessionId }?.key ?: return setInstallationResult(downloadId, false) - context.packageManager.packageInstaller.abandonSession(sessionId) + try { + context.packageManager.packageInstaller.abandonSession(sessionId) + } catch (_: Exception) { } + } + + fun cleanUpInstallation(pkgName: String) { + val sessionId = downloadInstallerMap[pkgName] ?: return + downloadInstallerMap.remove(pkgName) + try { + context.packageManager.packageInstaller.abandonSession(sessionId) + } catch (_: Exception) { } } /** @@ -267,10 +283,10 @@ internal class ExtensionInstaller(private val context: Context) { * @param downloadId The id of the download. * @param result Whether the extension was installed or not. */ - fun setInstallationResult(downloadId: Long, result: Boolean) { + fun setInstallationResult(pkgName: String, result: Boolean) { val step = if (result) InstallStep.Installed else InstallStep.Error - downloadInstallerMap.remove(downloadId) - downloadsStateFlow.tryEmit(downloadId to ExtensionIntallInfo(step, null)) + downloadInstallerMap.remove(pkgName) + downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(step, null)) } fun softDeleteDownload(downloadId: Long) { @@ -327,6 +343,7 @@ internal class ExtensionInstaller(private val context: Context) { * Called when a download event is received. It looks for the download in the current active * downloads and notifies its installation step. */ + @SuppressLint("Range") override fun onReceive(context: Context, intent: Intent?) { val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) ?: return @@ -335,12 +352,13 @@ internal class ExtensionInstaller(private val context: Context) { val uri = downloadManager.getUriForDownloadedFile(id) + val pkgName = activeDownloads.entries.find { id == it.value }?.key // Set next installation step - if (uri != null) { - downloadsStateFlow.tryEmit(id to ExtensionIntallInfo(InstallStep.Loading, null)) - } else { + if (uri != null && pkgName != null) { + downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(InstallStep.Loading, null)) + } else if (pkgName != null) { Timber.e("Couldn't locate downloaded APK") - downloadsStateFlow.tryEmit(id to ExtensionIntallInfo(InstallStep.Error, null)) + downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(InstallStep.Error, null)) return } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt index 07b26dfd7c..9c1671eacc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionBottomPresenter.kt @@ -59,6 +59,7 @@ class ExtensionBottomPresenter( private val sourceManager: SourceManager = Injekt.get() private var selectedSource: Long? = null + private var firstLoad = true private val db: DatabaseHelper = Injekt.get() override fun onCreate() { @@ -100,9 +101,21 @@ class ExtensionBottomPresenter( presenterScope.launch { extensionManager.downloadRelay .collect { - val extPageName = extensionManager.getExtension(it.first) + if (it.first == "Finished") { + firstLoad = true + currentDownloads.clear() + extensions = toItems( + Triple( + extensionManager.installedExtensions, + extensionManager.untrustedExtensions, + extensionManager.availableExtensions + ) + ) + withUIContext { bottomSheet.setExtensions(extensions) } + return@collect + } val extension = extensions.find { item -> - extPageName == item.extension.pkgName + it.first == item.extension.pkgName } ?: return@collect when (it.second.first) { InstallStep.Installed, InstallStep.Error -> { @@ -183,6 +196,15 @@ class ExtensionBottomPresenter( val items = mutableListOf() + if (firstLoad) { + val listOfExtensions = installed + untrusted + available + listOfExtensions.forEach { + val installInfo = extensionManager.getInstallInfo(it.pkgName) ?: return@forEach + currentDownloads[it.pkgName] = installInfo + } + firstLoad = false + } + val updatesSorted = installed.filter { it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedBy { it.name } val sortOrder = InstalledExtensionsOrder.fromPreference(preferences) val installedSorted = installed