mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
refactor(extension): Installer abstraction
This commit is contained in:
parent
0565fc2665
commit
6a680facd5
5 changed files with 146 additions and 115 deletions
|
@ -7,6 +7,7 @@ import android.os.Parcelable
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionApi
|
||||||
|
import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
|
||||||
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.InstallStep
|
||||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||||
|
@ -17,6 +18,9 @@ import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
|
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
|
||||||
import eu.kanade.tachiyomi.util.system.launchNow
|
import eu.kanade.tachiyomi.util.system.launchNow
|
||||||
import eu.kanade.tachiyomi.util.system.withIOContext
|
import eu.kanade.tachiyomi.util.system.withIOContext
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -27,8 +31,6 @@ import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import yokai.domain.base.BasePreferences
|
import yokai.domain.base.BasePreferences
|
||||||
import yokai.domain.extension.interactor.TrustExtension
|
import yokai.domain.extension.interactor.TrustExtension
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The manager of extensions installed as another apk which extend the available sources. It handles
|
* The manager of extensions installed as another apk which extend the available sources. It handles
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.installer
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.annotation.CallSuper
|
||||||
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
|
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
|
||||||
|
import java.util.Collections
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
|
abstract class Installer(
|
||||||
|
internal val context: Context,
|
||||||
|
// TODO: Remove finishedQueue
|
||||||
|
internal val finishedQueue: (Installer) -> Unit,
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val extensionManager: ExtensionManager by injectLazy()
|
||||||
|
|
||||||
|
private var waitingInstall = AtomicReference<Entry>(null)
|
||||||
|
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
||||||
|
|
||||||
|
private val cancelReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return
|
||||||
|
cancelQueue(downloadId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract var ready: Boolean
|
||||||
|
|
||||||
|
fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add an item to install queue.
|
||||||
|
*
|
||||||
|
* @param downloadId Download ID as known by [ExtensionManager]
|
||||||
|
* @param uri Uri of APK to install
|
||||||
|
*/
|
||||||
|
fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) {
|
||||||
|
queue.add(Entry(downloadId, pkgName, uri))
|
||||||
|
checkQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun processEntry(entry: Entry) {
|
||||||
|
extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
open fun cancelEntry(entry: Entry): Boolean {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tells the queue to continue processing the next entry and updates the install step
|
||||||
|
* of the completed entry ([waitingInstall]) to [ExtensionManager].
|
||||||
|
*
|
||||||
|
* @param resultStep new install step for the processed entry.
|
||||||
|
* @see waitingInstall
|
||||||
|
*/
|
||||||
|
fun continueQueue(succeeded: Boolean) {
|
||||||
|
val completedEntry = waitingInstall.getAndSet(null)
|
||||||
|
if (completedEntry != null) {
|
||||||
|
extensionManager.setInstallationResult(completedEntry.downloadId, succeeded)
|
||||||
|
checkQueue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun checkQueue() {
|
||||||
|
if (!ready) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (queue.isEmpty()) {
|
||||||
|
finishedQueue(this)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val nextEntry = queue.first()
|
||||||
|
if (waitingInstall.compareAndSet(null, nextEntry)) {
|
||||||
|
queue.removeAt(0)
|
||||||
|
processEntry(nextEntry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
open fun onDestroy() {
|
||||||
|
LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver)
|
||||||
|
queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) }
|
||||||
|
queue.clear()
|
||||||
|
waitingInstall.set(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected fun getActiveEntry(): Entry? = waitingInstall.get()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels queue for the provided download ID if exists.
|
||||||
|
*
|
||||||
|
* @param downloadId Download ID as known by [ExtensionManager]
|
||||||
|
*/
|
||||||
|
fun cancelQueue(downloadId: Long) {
|
||||||
|
val waitingInstall = this.waitingInstall.get()
|
||||||
|
val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
|
||||||
|
if (cancelEntry(toCancel)) {
|
||||||
|
queue.remove(toCancel)
|
||||||
|
if (waitingInstall == toCancel) {
|
||||||
|
// Currently processing removed entry, continue queue
|
||||||
|
this.waitingInstall.set(null)
|
||||||
|
checkQueue()
|
||||||
|
}
|
||||||
|
queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) }
|
||||||
|
// extensionManager.up(downloadId, InstallStep.Idle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Entry(
|
||||||
|
val downloadId: Long,
|
||||||
|
val pkgName: String,
|
||||||
|
val uri: Uri,
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,21 +1,16 @@
|
||||||
package eu.kanade.tachiyomi.extension
|
package eu.kanade.tachiyomi.extension.installer
|
||||||
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Process
|
import android.os.Process
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import yokai.i18n.MR
|
|
||||||
import yokai.util.lang.getString
|
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller.Companion.EXTRA_DOWNLOAD_ID
|
|
||||||
import eu.kanade.tachiyomi.util.system.getUriSize
|
import eu.kanade.tachiyomi.util.system.getUriSize
|
||||||
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
import eu.kanade.tachiyomi.util.system.isShizukuInstalled
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.lang.reflect.Method
|
||||||
|
import java.util.*
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
@ -23,37 +18,21 @@ import kotlinx.coroutines.cancel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import rikka.shizuku.Shizuku
|
import rikka.shizuku.Shizuku
|
||||||
import rikka.shizuku.ShizukuRemoteProcess
|
import rikka.shizuku.ShizukuRemoteProcess
|
||||||
import uy.kohesive.injekt.injectLazy
|
import yokai.i18n.MR
|
||||||
import java.io.BufferedReader
|
import yokai.util.lang.getString
|
||||||
import java.io.InputStream
|
|
||||||
import java.lang.reflect.Method
|
|
||||||
import java.util.*
|
|
||||||
import java.util.concurrent.atomic.AtomicReference
|
|
||||||
|
|
||||||
class ShizukuInstaller(private val context: Context, val finishedQueue: (ShizukuInstaller) -> Unit) {
|
class ShizukuInstaller(
|
||||||
|
context: Context,
|
||||||
|
finishedQueue: (Installer) -> Unit,
|
||||||
|
) : Installer(context, finishedQueue) {
|
||||||
|
|
||||||
private val extensionManager: ExtensionManager by injectLazy()
|
|
||||||
|
|
||||||
private var waitingInstall = AtomicReference<Entry>(null)
|
|
||||||
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
|
|
||||||
private val cancelReceiver = object : BroadcastReceiver() {
|
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
|
||||||
val downloadId = intent.getLongExtra(EXTRA_DOWNLOAD_ID, -1).takeIf { it >= 0 } ?: return
|
|
||||||
cancelQueue(downloadId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Entry(val downloadId: Long, val pkgName: String, val uri: Uri)
|
|
||||||
private val queue = Collections.synchronizedList(mutableListOf<Entry>())
|
|
||||||
|
|
||||||
private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
|
private val shizukuDeadListener = Shizuku.OnBinderDeadListener {
|
||||||
Logger.d { "Shizuku was killed prematurely" }
|
Logger.d { "Shizuku was killed prematurely" }
|
||||||
finishedQueue(this)
|
finishedQueue(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun isInQueue(pkgName: String) = queue.any { it.pkgName == pkgName }
|
|
||||||
|
|
||||||
private val shizukuPermissionListener = object : Shizuku.OnRequestPermissionResultListener {
|
private val shizukuPermissionListener = object : Shizuku.OnRequestPermissionResultListener {
|
||||||
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
|
override fun onRequestPermissionResult(requestCode: Int, grantResult: Int) {
|
||||||
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
if (requestCode == SHIZUKU_PERMISSION_REQUEST_CODE) {
|
||||||
|
@ -68,7 +47,7 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ready = false
|
override var ready = false
|
||||||
|
|
||||||
private val newProcess: Method
|
private val newProcess: Method
|
||||||
|
|
||||||
|
@ -90,9 +69,8 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku
|
||||||
newProcess.isAccessible = true
|
newProcess.isAccessible = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
override fun processEntry(entry: Entry) {
|
||||||
fun processEntry(entry: Entry) {
|
super.processEntry(entry)
|
||||||
extensionManager.setInstalling(entry.downloadId, entry.uri.hashCode())
|
|
||||||
ioScope.launch {
|
ioScope.launch {
|
||||||
var sessionId: String? = null
|
var sessionId: String? = null
|
||||||
try {
|
try {
|
||||||
|
@ -130,85 +108,14 @@ class ShizukuInstaller(private val context: Context, val finishedQueue: (Shizuku
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks the queue. The provided service will be stopped if the queue is empty.
|
|
||||||
* Will not be run when not ready.
|
|
||||||
*
|
|
||||||
* @see ready
|
|
||||||
*/
|
|
||||||
fun checkQueue() {
|
|
||||||
if (!ready) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (queue.isEmpty()) {
|
|
||||||
finishedQueue(this)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val nextEntry = queue.first()
|
|
||||||
if (waitingInstall.compareAndSet(null, nextEntry)) {
|
|
||||||
queue.removeAt(0)
|
|
||||||
processEntry(nextEntry)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tells the queue to continue processing the next entry and updates the install step
|
|
||||||
* of the completed entry ([waitingInstall]) to [ExtensionManager].
|
|
||||||
*
|
|
||||||
* @param resultStep new install step for the processed entry.
|
|
||||||
* @see waitingInstall
|
|
||||||
*/
|
|
||||||
fun continueQueue(succeeded: Boolean) {
|
|
||||||
val completedEntry = waitingInstall.getAndSet(null)
|
|
||||||
if (completedEntry != null) {
|
|
||||||
extensionManager.setInstallationResult(completedEntry.downloadId, succeeded)
|
|
||||||
checkQueue()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add an item to install queue.
|
|
||||||
*
|
|
||||||
* @param downloadId Download ID as known by [ExtensionManager]
|
|
||||||
* @param uri Uri of APK to install
|
|
||||||
*/
|
|
||||||
fun addToQueue(downloadId: Long, pkgName: String, uri: Uri) {
|
|
||||||
queue.add(Entry(downloadId, pkgName, uri))
|
|
||||||
checkQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels queue for the provided download ID if exists.
|
|
||||||
*
|
|
||||||
* @param downloadId Download ID as known by [ExtensionManager]
|
|
||||||
*/
|
|
||||||
private fun cancelQueue(downloadId: Long) {
|
|
||||||
val waitingInstall = this.waitingInstall.get()
|
|
||||||
val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return
|
|
||||||
if (cancelEntry(toCancel)) {
|
|
||||||
queue.remove(toCancel)
|
|
||||||
if (waitingInstall == toCancel) {
|
|
||||||
// Currently processing removed entry, continue queue
|
|
||||||
this.waitingInstall.set(null)
|
|
||||||
checkQueue()
|
|
||||||
}
|
|
||||||
queue.forEach { extensionManager.setInstallationResult(it.downloadId, false) }
|
|
||||||
// extensionManager.up(downloadId, InstallStep.Idle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't cancel if entry is already started installing
|
// Don't cancel if entry is already started installing
|
||||||
fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry
|
override fun cancelEntry(entry: Entry): Boolean = getActiveEntry() != entry
|
||||||
fun getActiveEntry(): Entry? = waitingInstall.get()
|
|
||||||
|
|
||||||
fun onDestroy() {
|
override fun onDestroy() {
|
||||||
Shizuku.removeBinderDeadListener(shizukuDeadListener)
|
Shizuku.removeBinderDeadListener(shizukuDeadListener)
|
||||||
Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener)
|
Shizuku.removeRequestPermissionResultListener(shizukuPermissionListener)
|
||||||
ioScope.cancel()
|
ioScope.cancel()
|
||||||
LocalBroadcastManager.getInstance(context).unregisterReceiver(cancelReceiver)
|
super.onDestroy()
|
||||||
queue.forEach { extensionManager.setInstallationResult(it.pkgName, false) }
|
|
||||||
queue.clear()
|
|
||||||
waitingInstall.set(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
private fun exec(command: String, stdin: InputStream? = null): ShellResult {
|
|
@ -14,7 +14,7 @@ import androidx.core.net.toUri
|
||||||
import co.touchlab.kermit.Logger
|
import co.touchlab.kermit.Logger
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionInstallerJob
|
import eu.kanade.tachiyomi.extension.ExtensionInstallerJob
|
||||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||||
import eu.kanade.tachiyomi.extension.ShizukuInstaller
|
import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
|
import eu.kanade.tachiyomi.ui.extension.ExtensionIntallInfo
|
||||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
|
@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.util.system.e
|
||||||
import eu.kanade.tachiyomi.util.system.isPackageInstalled
|
import eu.kanade.tachiyomi.util.system.isPackageInstalled
|
||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
|
import java.io.File
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
@ -47,7 +48,6 @@ import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import yokai.domain.base.BasePreferences
|
import yokai.domain.base.BasePreferences
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The installer which installs, updates and uninstalls the extensions.
|
* The installer which installs, updates and uninstalls the extensions.
|
||||||
|
|
|
@ -24,7 +24,7 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateJob
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob.Target
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateJob.Target
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
|
||||||
import eu.kanade.tachiyomi.data.preference.changesIn
|
import eu.kanade.tachiyomi.data.preference.changesIn
|
||||||
import eu.kanade.tachiyomi.extension.ShizukuInstaller
|
import eu.kanade.tachiyomi.extension.installer.ShizukuInstaller
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.network.NetworkPreferences
|
import eu.kanade.tachiyomi.network.NetworkPreferences
|
||||||
import eu.kanade.tachiyomi.network.PREF_DOH_360
|
import eu.kanade.tachiyomi.network.PREF_DOH_360
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue