mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-20 03:39:19 +01:00
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:
parent
b3d05d50fa
commit
0d67d49fc9
@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager.ExtensionInfo
|
import eu.kanade.tachiyomi.extension.ExtensionManager.ExtensionInfo
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
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.notificationManager
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@ -49,6 +50,8 @@ class ExtensionInstallService(
|
|||||||
|
|
||||||
private val preferences: PreferencesHelper = Injekt.get()
|
private val preferences: PreferencesHelper = Injekt.get()
|
||||||
|
|
||||||
|
private var activeInstalls = listOf<String>()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method needs to be implemented, but it's not used/needed.
|
* This method needs to be implemented, but it's not used/needed.
|
||||||
*/
|
*/
|
||||||
@ -85,6 +88,11 @@ class ExtensionInstallService(
|
|||||||
) < it.versionCode
|
) < it.versionCode
|
||||||
}
|
}
|
||||||
?: return START_NOT_STICKY
|
?: return START_NOT_STICKY
|
||||||
|
|
||||||
|
activeInstalls = list.map { it.pkgName }
|
||||||
|
serviceScope.launch {
|
||||||
|
list.forEach { extensionManager.setPending(it.pkgName) }
|
||||||
|
}
|
||||||
var installed = 0
|
var installed = 0
|
||||||
val installedExtensions = mutableListOf<ExtensionInfo>()
|
val installedExtensions = mutableListOf<ExtensionInfo>()
|
||||||
job = serviceScope.launch {
|
job = serviceScope.launch {
|
||||||
@ -143,6 +151,8 @@ class ExtensionInstallService(
|
|||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
serviceScope.cancel()
|
serviceScope.cancel()
|
||||||
|
activeInstalls.forEach { extensionManager.cleanUpInstallation(it) }
|
||||||
|
extensionManager.downloadRelay.tryEmit("Finished" to (InstallStep.Installed to null))
|
||||||
if (instance == this) {
|
if (instance == this) {
|
||||||
instance = null
|
instance = null
|
||||||
}
|
}
|
||||||
@ -166,6 +176,8 @@ class ExtensionInstallService(
|
|||||||
context.stopService(Intent(context, ExtensionUpdateJob::class.java))
|
context.stopService(Intent(context, ExtensionUpdateJob::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun activeInstalls(): List<String>? = instance?.activeInstalls
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the status of the service.
|
* Returns the status of the service.
|
||||||
*
|
*
|
||||||
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
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.model.LoadResult
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
|
||||||
@ -54,7 +55,7 @@ class ExtensionManager(
|
|||||||
val downloadRelay
|
val downloadRelay
|
||||||
get() = installer.downloadsStateFlow
|
get() = installer.downloadsStateFlow
|
||||||
|
|
||||||
fun getExtension(downloadId: Long): String? {
|
private fun getExtension(downloadId: Long): String? {
|
||||||
return installer.activeDownloads.entries.find { downloadId == it.value }?.key
|
return installer.activeDownloads.entries.find { downloadId == it.value }?.key
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -266,7 +267,16 @@ class ExtensionManager(
|
|||||||
* @param result Whether the extension was installed or not.
|
* @param result Whether the extension was installed or not.
|
||||||
*/
|
*/
|
||||||
fun setInstallationResult(downloadId: Long, result: Boolean) {
|
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.
|
/** Sets the result of the installation of an extension.
|
||||||
@ -284,7 +294,39 @@ class ExtensionManager(
|
|||||||
* @param downloadId The id of the download.
|
* @param downloadId The id of the download.
|
||||||
*/
|
*/
|
||||||
fun setInstalling(downloadId: Long, sessionId: Int) {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.extension.util
|
package eu.kanade.tachiyomi.extension.util
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.app.DownloadManager
|
import android.app.DownloadManager
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
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.
|
* 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 */
|
/** 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
|
* 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 {
|
scope.launch {
|
||||||
flowOf(
|
flowOf(
|
||||||
pollStatus(id),
|
pollStatus(id),
|
||||||
pollInstallStatus(id)
|
pollInstallStatus(pkgName)
|
||||||
).flattenMerge()
|
).flattenMerge()
|
||||||
.transformWhile {
|
.transformWhile {
|
||||||
emit(it)
|
emit(it)
|
||||||
@ -118,11 +119,11 @@ internal class ExtensionInstaller(private val context: Context) {
|
|||||||
deleteDownload(pkgName)
|
deleteDownload(pkgName)
|
||||||
}
|
}
|
||||||
.collect {
|
.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)
|
.flowOn(Dispatchers.IO)
|
||||||
.transformWhile {
|
.transformWhile {
|
||||||
emit(it)
|
emit(it)
|
||||||
@ -139,6 +140,7 @@ internal class ExtensionInstaller(private val context: Context) {
|
|||||||
*
|
*
|
||||||
* @param id The id of the download to poll.
|
* @param id The id of the download to poll.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("Range")
|
||||||
private fun pollStatus(id: Long): Flow<ExtensionIntallInfo> {
|
private fun pollStatus(id: Long): Flow<ExtensionIntallInfo> {
|
||||||
val query = DownloadManager.Query().setFilterById(id)
|
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
|
* 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
|
* 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 {
|
return flow {
|
||||||
while (true) {
|
while (true) {
|
||||||
val sessionId = downloadInstallerMap[id]
|
val sessionId = downloadInstallerMap[pkgName]
|
||||||
if (sessionId != null) {
|
if (sessionId != null) {
|
||||||
val session =
|
val session =
|
||||||
context.packageManager.packageInstaller.getSessionInfo(sessionId)
|
context.packageManager.packageInstaller.getSessionInfo(sessionId)
|
||||||
@ -191,7 +193,7 @@ internal class ExtensionInstaller(private val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.takeWhile { info ->
|
.takeWhile { info ->
|
||||||
val sessionId = downloadInstallerMap[id]
|
val sessionId = downloadInstallerMap[pkgName]
|
||||||
if (sessionId != null) {
|
if (sessionId != null) {
|
||||||
info.second != null
|
info.second != null
|
||||||
} else {
|
} else {
|
||||||
@ -250,15 +252,29 @@ internal class ExtensionInstaller(private val context: Context) {
|
|||||||
*
|
*
|
||||||
* @param downloadId The id of the download.
|
* @param downloadId The id of the download.
|
||||||
*/
|
*/
|
||||||
fun setInstalling(downloadId: Long, sessionId: Int) {
|
fun setInstalling(pkgName: String, sessionId: Int) {
|
||||||
downloadsStateFlow.tryEmit(downloadId to ExtensionIntallInfo(InstallStep.Installing, null))
|
downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(InstallStep.Installing, null))
|
||||||
downloadInstallerMap[downloadId] = sessionId
|
downloadInstallerMap[pkgName] = sessionId
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun setPending(pkgName: String) {
|
||||||
|
downloadsStateFlow.emit(pkgName to ExtensionIntallInfo(InstallStep.Pending, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelInstallation(sessionId: Int) {
|
fun cancelInstallation(sessionId: Int) {
|
||||||
val downloadId = downloadInstallerMap.entries.find { it.value == sessionId }?.key ?: return
|
val downloadId = downloadInstallerMap.entries.find { it.value == sessionId }?.key ?: return
|
||||||
setInstallationResult(downloadId, false)
|
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 downloadId The id of the download.
|
||||||
* @param result Whether the extension was installed or not.
|
* @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
|
val step = if (result) InstallStep.Installed else InstallStep.Error
|
||||||
downloadInstallerMap.remove(downloadId)
|
downloadInstallerMap.remove(pkgName)
|
||||||
downloadsStateFlow.tryEmit(downloadId to ExtensionIntallInfo(step, null))
|
downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(step, null))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun softDeleteDownload(downloadId: Long) {
|
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
|
* Called when a download event is received. It looks for the download in the current active
|
||||||
* downloads and notifies its installation step.
|
* downloads and notifies its installation step.
|
||||||
*/
|
*/
|
||||||
|
@SuppressLint("Range")
|
||||||
override fun onReceive(context: Context, intent: Intent?) {
|
override fun onReceive(context: Context, intent: Intent?) {
|
||||||
val id = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) ?: return
|
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 uri = downloadManager.getUriForDownloadedFile(id)
|
||||||
|
|
||||||
|
val pkgName = activeDownloads.entries.find { id == it.value }?.key
|
||||||
// Set next installation step
|
// Set next installation step
|
||||||
if (uri != null) {
|
if (uri != null && pkgName != null) {
|
||||||
downloadsStateFlow.tryEmit(id to ExtensionIntallInfo(InstallStep.Loading, null))
|
downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(InstallStep.Loading, null))
|
||||||
} else {
|
} else if (pkgName != null) {
|
||||||
Timber.e("Couldn't locate downloaded APK")
|
Timber.e("Couldn't locate downloaded APK")
|
||||||
downloadsStateFlow.tryEmit(id to ExtensionIntallInfo(InstallStep.Error, null))
|
downloadsStateFlow.tryEmit(pkgName to ExtensionIntallInfo(InstallStep.Error, null))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,6 +59,7 @@ class ExtensionBottomPresenter(
|
|||||||
private val sourceManager: SourceManager = Injekt.get()
|
private val sourceManager: SourceManager = Injekt.get()
|
||||||
|
|
||||||
private var selectedSource: Long? = null
|
private var selectedSource: Long? = null
|
||||||
|
private var firstLoad = true
|
||||||
private val db: DatabaseHelper = Injekt.get()
|
private val db: DatabaseHelper = Injekt.get()
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@ -100,9 +101,21 @@ class ExtensionBottomPresenter(
|
|||||||
presenterScope.launch {
|
presenterScope.launch {
|
||||||
extensionManager.downloadRelay
|
extensionManager.downloadRelay
|
||||||
.collect {
|
.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 ->
|
val extension = extensions.find { item ->
|
||||||
extPageName == item.extension.pkgName
|
it.first == item.extension.pkgName
|
||||||
} ?: return@collect
|
} ?: return@collect
|
||||||
when (it.second.first) {
|
when (it.second.first) {
|
||||||
InstallStep.Installed, InstallStep.Error -> {
|
InstallStep.Installed, InstallStep.Error -> {
|
||||||
@ -183,6 +196,15 @@ class ExtensionBottomPresenter(
|
|||||||
|
|
||||||
val items = mutableListOf<ExtensionItem>()
|
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 updatesSorted = installed.filter { it.hasUpdate && (showNsfwExtensions || !it.isNsfw) }.sortedBy { it.name }
|
||||||
val sortOrder = InstalledExtensionsOrder.fromPreference(preferences)
|
val sortOrder = InstalledExtensionsOrder.fromPreference(preferences)
|
||||||
val installedSorted = installed
|
val installedSorted = installed
|
||||||
|
Loading…
Reference in New Issue
Block a user