diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index 71aa6d6f..02154677 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -19,6 +19,7 @@ import com.futo.platformplayer.stores.PluginIconStorage import com.futo.platformplayer.stores.PluginScriptsDirectory import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kotlinx.serialization.Serializable @@ -47,6 +48,8 @@ class StatePlugins { private var _updatesAvailableMap: HashSet = hashSetOf(); + private val _isUpdating: HashSet = hashSetOf(); + fun getPluginIconOrNull(id: String): ImageVariable? { if(iconsDir.hasIcon(id)) return iconsDir.getIconBinary(id); @@ -58,6 +61,38 @@ class StatePlugins { .load(); } + fun isUpdating(id: String): Boolean{ + synchronized(_isUpdating){ + return _isUpdating.contains(id); + } + } + fun setIsUpdating(id: String, value: Boolean){ + synchronized(_isUpdating){ + if(value && !_isUpdating.contains(id)) { + Logger.i(TAG, "PLUGIN [${id}] UPDATING"); + _isUpdating.add(id); + } + if(!value && _isUpdating.contains(id)) { + Logger.i(TAG, "PLUGIN [${id}] NOT UPDATING"); + _isUpdating.remove(id); + } + } + } + suspend fun whileUpdating(id: String, handle: suspend ()->Unit){ + try { + setIsUpdating(id, true); + handle(); + } + finally { + setIsUpdating(id, false); + } + } + fun clearUpdating(){ + synchronized(_isUpdating) { + _isUpdating.clear(); + } + } + suspend fun checkForUpdates(): List> = withContext(Dispatchers.IO) { var configs = mutableListOf>() @@ -430,42 +465,49 @@ class StatePlugins { fun installPluginBackground(context: Context, scope: CoroutineScope, config: SourcePluginConfig, script: String, onProgress: (text: String, progress: Double)->Unit, onConcluded: (ex: Throwable?)->Unit) { scope.launch(Dispatchers.IO) { - val client = ManagedHttpClient(); - try { + whileUpdating(config.id) { withContext(Dispatchers.Main) { - onProgress.invoke("Validating script", 0.25); + onProgress.invoke("Waiting for plugins to finish", 0.1); } + delay(500); - val tempDescriptor = SourcePluginDescriptor(config); - val plugin = JSClient(context, tempDescriptor, null, script); - plugin.validate(); - - withContext(Dispatchers.Main) { - onProgress.invoke("Downloading Icon", 0.5); - } - - val icon = config.absoluteIconUrl?.let { absIconUrl -> + val client = ManagedHttpClient(); + try { withContext(Dispatchers.Main) { - onProgress.invoke("Saving plugin", 0.75); + onProgress.invoke("Validating script", 0.25); } - val iconResp = client.get(absIconUrl); - if(iconResp.isOk) - return@let iconResp.body?.byteStream()?.use { it.readBytes() }; - return@let null; - } - val installEx = StatePlugins.instance.createPlugin(config, script, icon, true); - if(installEx != null) - throw installEx; - StatePlatform.instance.updateAvailableClients(context); - withContext(Dispatchers.Main) { - onProgress.invoke("Finished", 1.0) - onConcluded.invoke(null); - } - } catch (ex: Exception) { - Logger.e(TAG, ex.message ?: "null", ex); - withContext(Dispatchers.Main) { - onConcluded.invoke(ex); + val tempDescriptor = SourcePluginDescriptor(config); + val plugin = JSClient(context, tempDescriptor, null, script); + plugin.validate(); + + withContext(Dispatchers.Main) { + onProgress.invoke("Downloading Icon", 0.5); + } + + val icon = config.absoluteIconUrl?.let { absIconUrl -> + withContext(Dispatchers.Main) { + onProgress.invoke("Saving plugin", 0.75); + } + val iconResp = client.get(absIconUrl); + if (iconResp.isOk) + return@let iconResp.body?.byteStream()?.use { it.readBytes() }; + return@let null; + } + val installEx = StatePlugins.instance.createPlugin(config, script, icon, true); + if (installEx != null) + throw installEx; + StatePlatform.instance.updateAvailableClients(context); + + withContext(Dispatchers.Main) { + onProgress.invoke("Finished", 1.0) + onConcluded.invoke(null); + } + } catch (ex: Exception) { + Logger.e(TAG, ex.message ?: "null", ex); + withContext(Dispatchers.Main) { + onConcluded.invoke(ex); + } } } } diff --git a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt index 978500e0..eff83030 100644 --- a/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt +++ b/app/src/main/java/com/futo/platformplayer/subscription/SubscriptionsTaskFetchAlgorithm.kt @@ -22,6 +22,7 @@ import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateCache import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.states.StateSubscriptions import kotlinx.coroutines.CoroutineScope import java.time.OffsetDateTime @@ -138,6 +139,18 @@ abstract class SubscriptionsTaskFetchAlgorithm( for(task in tasks) { val forkTask = threadPool.submit { + if(StatePlugins.instance.isUpdating(task.client.id)){ + val isUpdatingException = ScriptCriticalException(task.client.config, "Plugin is updating"); + synchronized(failedPlugins) { + //Fail all subscription calls to plugin if it has a critical issue + if(isUpdatingException.config is SourcePluginConfig && !failedPlugins.contains(isUpdatingException.config.id)) { + Logger.w(StateSubscriptions.TAG, "Subscriptions ignoring plugin [${isUpdatingException.config.name}] due to critical exception:\n" + isUpdatingException.message); + failedPlugins.add(isUpdatingException.config.id); + } + } + return@submit SubscriptionTaskResult(task, StateCache.instance.getChannelCachePager(task.sub.channel.url), isUpdatingException); + } + if(task.fromPeek) { try {