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
This commit is contained in:
Jays2Kings 2021-08-22 23:30:27 -04:00
parent b3d05d50fa
commit 0d67d49fc9
4 changed files with 119 additions and 25 deletions

View File

@ -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<String>()
/**
* 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<ExtensionInfo>()
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<String>? = instance?.activeInstalls
/**
* Returns the status of the service.
*

View File

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

View File

@ -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<Long, Int>()
val downloadInstallerMap = hashMapOf<String, Int>()
/**
* 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<ExtensionIntallInfo> {
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<ExtensionIntallInfo> {
private fun pollInstallStatus(pkgName: String): Flow<ExtensionIntallInfo> {
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
}

View File

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