mirror of
https://github.com/null2264/yokai.git
synced 2025-06-21 10:44:42 +00:00
* fix(deps): "String index out of range" compile error
REF: https://github.com/icerockdev/moko-resources/issues/732
* refactor(i18n): Simplify gradle
* refactor: Migrate i18n files to moko-resources
Also split plurals to their own files
* fix: Only use firebase on releases
* revert: Revert "fix: Only use firebase on releases"
This reverts commit e4f40b450a
.
* ci: Just build standard release
* refactor: Fully migrate to Moko Resources for i18n
204 lines
8.3 KiB
Kotlin
204 lines
8.3 KiB
Kotlin
package eu.kanade.tachiyomi.extension
|
|
|
|
import android.content.Context
|
|
import android.content.pm.ServiceInfo
|
|
import android.os.Build
|
|
import androidx.work.CoroutineWorker
|
|
import androidx.work.ExistingWorkPolicy
|
|
import androidx.work.ForegroundInfo
|
|
import androidx.work.NetworkType
|
|
import androidx.work.OneTimeWorkRequestBuilder
|
|
import androidx.work.OutOfQuotaPolicy
|
|
import androidx.work.WorkInfo
|
|
import androidx.work.WorkManager
|
|
import androidx.work.WorkerParameters
|
|
import androidx.work.workDataOf
|
|
import co.touchlab.kermit.Logger
|
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
|
import eu.kanade.tachiyomi.extension.model.Extension
|
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
|
import eu.kanade.tachiyomi.util.system.jobIsRunning
|
|
import eu.kanade.tachiyomi.util.system.launchIO
|
|
import eu.kanade.tachiyomi.util.system.localeContext
|
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
|
import eu.kanade.tachiyomi.util.system.toast
|
|
import eu.kanade.tachiyomi.util.system.tryToSetForeground
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.Job
|
|
import kotlinx.coroutines.async
|
|
import kotlinx.coroutines.awaitAll
|
|
import kotlinx.coroutines.cancel
|
|
import kotlinx.coroutines.coroutineScope
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.sync.Semaphore
|
|
import kotlinx.coroutines.sync.withPermit
|
|
import kotlinx.coroutines.withContext
|
|
import kotlinx.serialization.encodeToString
|
|
import kotlinx.serialization.json.Json
|
|
import uy.kohesive.injekt.Injekt
|
|
import uy.kohesive.injekt.api.get
|
|
import yokai.i18n.MR
|
|
import java.lang.ref.WeakReference
|
|
import kotlin.math.max
|
|
|
|
class ExtensionInstallerJob(val context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) {
|
|
|
|
private val notifier = ExtensionInstallNotifier(context.localeContext)
|
|
|
|
private val preferences: PreferencesHelper = Injekt.get()
|
|
|
|
private var activeInstalls = mutableListOf<String>()
|
|
|
|
val extensionManager: ExtensionManager = Injekt.get()
|
|
|
|
private var emitScope = CoroutineScope(Job() + Dispatchers.Default)
|
|
|
|
private var job: Job? = null
|
|
|
|
override suspend fun getForegroundInfo(): ForegroundInfo {
|
|
val notification = notifier.progressNotificationBuilder.build()
|
|
val id = Notifications.ID_EXTENSION_PROGRESS
|
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
ForegroundInfo(id, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
|
} else {
|
|
ForegroundInfo(id, notification)
|
|
}
|
|
}
|
|
|
|
override suspend fun doWork(): Result {
|
|
tryToSetForeground()
|
|
|
|
instance = WeakReference(this)
|
|
|
|
context.notificationManager.cancel(Notifications.ID_UPDATES_TO_EXTS)
|
|
val showUpdated = inputData.getInt(KEY_SHOW_UPDATED, -1)
|
|
val showUpdatedNotification = showUpdated > -1
|
|
val reRunUpdateCheck = showUpdated > 0
|
|
|
|
if (!showUpdatedNotification && !preferences.hasPromptedBeforeUpdateAll().get()) {
|
|
context.toast(MR.strings.some_extensions_may_prompt)
|
|
preferences.hasPromptedBeforeUpdateAll().set(true)
|
|
}
|
|
|
|
val json = inputData.getString(KEY_EXTENSION) ?: return Result.failure()
|
|
|
|
val infos = try {
|
|
Json.decodeFromString<Array<ExtensionManager.ExtensionInfo>>(json)
|
|
} catch (e: Exception) {
|
|
Logger.e(e) { "Cannot decode string" }
|
|
null
|
|
} ?: return Result.failure()
|
|
val list = infos.filter {
|
|
val installedExt = extensionManager.installedExtensionsFlow.value.find { installed ->
|
|
installed.pkgName == it.pkgName
|
|
} ?: return@filter false
|
|
installedExt.versionCode < it.versionCode || installedExt.libVersion < it.libVersion
|
|
}
|
|
|
|
activeInstalls = list.map { it.pkgName }.toMutableList()
|
|
emitScope.launch { list.forEach { extensionManager.setPending(it.pkgName) } }
|
|
var installed = 0
|
|
val installedExtensions = mutableListOf<ExtensionManager.ExtensionInfo>()
|
|
val requestSemaphore = Semaphore(3)
|
|
coroutineScope {
|
|
job = launchIO {
|
|
list.map { extension ->
|
|
async {
|
|
requestSemaphore.withPermit {
|
|
extensionManager.installExtension(extension, this)
|
|
.collect {
|
|
if (it.first.isCompleted()) {
|
|
activeInstalls.remove(extension.pkgName)
|
|
installedExtensions.add(extension)
|
|
installed++
|
|
val prefCount = preferences.extensionUpdatesCount().get()
|
|
preferences.extensionUpdatesCount()
|
|
.set(max(prefCount - 1, 0))
|
|
}
|
|
notifier.showProgressNotification(installed, list.size)
|
|
if (activeInstalls.isEmpty() || isStopped) {
|
|
cancel()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}.awaitAll()
|
|
}
|
|
}
|
|
|
|
if (showUpdatedNotification && installedExtensions.size > 0) {
|
|
notifier.showUpdatedNotification(installedExtensions, preferences.hideNotificationContent().get())
|
|
}
|
|
if (reRunUpdateCheck || installedExtensions.size != list.size) {
|
|
ExtensionUpdateJob.runJobAgain(context, NetworkType.CONNECTED, false)
|
|
}
|
|
|
|
activeInstalls.forEach { extensionManager.cleanUpInstallation(it) }
|
|
activeInstalls.clear()
|
|
val hasChain = withContext(Dispatchers.IO) {
|
|
WorkManager.getInstance(context).getWorkInfosByTag(TAG).get().any {
|
|
it.state == WorkInfo.State.BLOCKED
|
|
}
|
|
}
|
|
if (!hasChain) {
|
|
extensionManager.emitToInstaller("Finished", (InstallStep.Installed to null))
|
|
}
|
|
if (instance?.get() == this) {
|
|
instance = null
|
|
}
|
|
if (!hasChain) {
|
|
context.notificationManager.cancel(Notifications.ID_EXTENSION_PROGRESS)
|
|
}
|
|
return Result.success()
|
|
}
|
|
|
|
companion object {
|
|
private const val TAG = "ExtensionInstaller"
|
|
|
|
/**
|
|
* Key that defines what should be updated.
|
|
*/
|
|
const val KEY_EXTENSION = "extension"
|
|
const val KEY_SHOW_UPDATED = "show_updated"
|
|
|
|
private var instance: WeakReference<ExtensionInstallerJob>? = null
|
|
|
|
fun start(context: Context, extensions: List<Extension.Available>, showUpdatedExtension: Int = -1) {
|
|
startJob(context, extensions.map(ExtensionManager::ExtensionInfo), showUpdatedExtension)
|
|
}
|
|
|
|
fun startJob(context: Context, info: List<ExtensionManager.ExtensionInfo>, showUpdatedExtension: Int = -1) {
|
|
// chunked to satisfy input limits
|
|
val requests = info.chunked(32).map {
|
|
OneTimeWorkRequestBuilder<ExtensionInstallerJob>()
|
|
.addTag(TAG)
|
|
.setInputData(
|
|
workDataOf(
|
|
KEY_EXTENSION to Json.encodeToString(it.toTypedArray()),
|
|
KEY_SHOW_UPDATED to showUpdatedExtension,
|
|
),
|
|
)
|
|
.setExpedited(OutOfQuotaPolicy.RUN_AS_NON_EXPEDITED_WORK_REQUEST)
|
|
.build()
|
|
}
|
|
var workContinuation = WorkManager.getInstance(context)
|
|
.beginUniqueWork(TAG, ExistingWorkPolicy.REPLACE, requests.first())
|
|
for (i in 1 until requests.size) {
|
|
workContinuation = workContinuation.then(requests[i])
|
|
}
|
|
workContinuation.enqueue()
|
|
}
|
|
|
|
fun activeInstalls(): List<String>? = instance?.get()?.activeInstalls
|
|
fun removeActiveInstall(pkgName: String) = instance?.get()?.activeInstalls?.remove(pkgName)
|
|
|
|
fun stop(context: Context) {
|
|
instance?.get()?.job?.cancel()
|
|
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
|
|
}
|
|
|
|
fun isRunning(context: Context) = WorkManager.getInstance(context).jobIsRunning(TAG)
|
|
}
|
|
}
|