From 0f8a2df9e5046c5f1822f19e85dd2248e6b1737b Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Tue, 23 Aug 2022 21:28:42 +0200 Subject: [PATCH] Optimized plugin loading --- .../lagradost/cloudstream3/MainActivity.kt | 73 ++++++++++++------- .../cloudstream3/plugins/PluginManager.kt | 52 ++++++++----- .../cloudstream3/ui/home/HomeFragment.kt | 9 +-- .../cloudstream3/ui/home/HomeViewModel.kt | 43 ++++++----- 4 files changed, 108 insertions(+), 69 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 49864e65..629406aa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -84,6 +84,8 @@ import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ResponseParser import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.fragment_result_swipe.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.io.File import java.net.URI import kotlin.reflect.KClass @@ -131,6 +133,10 @@ var app = Requests(responseParser = object : ResponseParser { class MainActivity : AppCompatActivity(), ColorPickerDialogListener { companion object { const val TAG = "MAINACT" + + /** + * Fires every time a new batch of plugins have been loaded, no guarantee about how often this is run and on which thread + * */ val afterPluginsLoadedEvent = Event() val mainPluginsLoadedEvent = Event() // homepage api, used to speed up time to load for homepage @@ -236,6 +242,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { override fun onResume() { super.onResume() + afterPluginsLoadedEvent += ::onAllPluginsLoaded try { if (isCastApiAvailable()) { //mCastSession = mSessionManager.currentCastSession @@ -325,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { broadcastIntent.action = "restart_service" broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java) this.sendBroadcast(broadcastIntent) + afterPluginsLoadedEvent -= ::onAllPluginsLoaded super.onDestroy() } @@ -414,6 +422,36 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } } + private val pluginsLock = Mutex() + private fun onAllPluginsLoaded(success: Boolean = false) { + ioSafe { + pluginsLock.withLock { + // Load cloned sites after plugins have been loaded since clones depend on plugins. + try { + getKey>(USER_PROVIDER_API)?.let { list -> + list.forEach { custom -> + allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } + ?.let { + allProviders.add(it.javaClass.newInstance().apply { + name = custom.name + lang = custom.lang + mainUrl = custom.url.trimEnd('/') + canBeOverridden = false + }) + } + } + } + // it.hashCode() is not enough to make sure they are distinct + apis = allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name } + APIHolder.apiMap = null + } catch (e: Exception) { + logError(e) + } + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) @@ -446,36 +484,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { mainPluginsLoadedEvent.invoke(false) } - if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { - PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) - } else { - PluginManager.loadAllOnlinePlugins(this@MainActivity) - } - - PluginManager.loadAllLocalPlugins(this@MainActivity) - - // Load cloned sites after plugins have been loaded since clones depend on plugins. - try { - getKey>(USER_PROVIDER_API)?.let { list -> - list.forEach { custom -> - allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass } - ?.let { - allProviders.add(it.javaClass.newInstance().apply { - name = custom.name - lang = custom.lang - mainUrl = custom.url.trimEnd('/') - canBeOverridden = false - }) - } - } + ioSafe { + if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { + PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) + } else { + PluginManager.loadAllOnlinePlugins(this@MainActivity) } - apis = allProviders.distinctBy { it } - APIHolder.apiMap = null - } catch (e: Exception) { - logError(e) } - afterPluginsLoadedEvent.invoke(true) + ioSafe { + PluginManager.loadAllLocalPlugins(this@MainActivity) + } } // ioSafe { diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 96a2222e..ef7dbf35 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -21,12 +21,15 @@ import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.APIHolder.removePluginMapping +import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.extractorApis import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock +import org.acra.log.debug import java.io.File import java.io.InputStreamReader import java.util.* @@ -162,7 +165,7 @@ object PluginManager { val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN } - var allCurrentOutDatedPlugins: Set = emptySet() + // var allCurrentOutDatedPlugins: Set = emptySet() suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean { return (getPluginsOnline().firstOrNull { it.internalName == apiName } @@ -184,6 +187,19 @@ object PluginManager { * 4. Else load the plugin normally **/ fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { + // Load all plugins as fast as possible! + (getPluginsOnline()).toList().apmap { pluginData -> + loadPlugin( + activity, + File(pluginData.filePath), + pluginData + ) + } + + ioSafe { + afterPluginsLoadedEvent.invoke(true) + } + val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES @@ -198,29 +214,28 @@ object PluginManager { OnlinePluginData(savedData, onlineData) } }.flatten().distinctBy { it.onlineData.second.url } - allCurrentOutDatedPlugins = outdatedPlugins.toSet() - Log.i(TAG, "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}") + debug { + "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}" + } - outdatedPlugins.apmap { - if (it.isDisabled) { - return@apmap - } else if (it.isOutdated) { + outdatedPlugins.apmap { pluginData -> + if (pluginData.isDisabled) { + unloadPlugin(pluginData.savedData.filePath) + } else if (pluginData.isOutdated) { downloadAndLoadPlugin( activity, - it.onlineData.second.url, - it.savedData.internalName, - it.onlineData.first - ) - } else { - loadPlugin( - activity, - File(it.savedData.filePath), - it.savedData + pluginData.onlineData.second.url, + pluginData.savedData.internalName, + pluginData.onlineData.first ) } } + ioSafe { + afterPluginsLoadedEvent.invoke(true) + } + Log.i(TAG, "Plugin update done!") } @@ -256,6 +271,7 @@ object PluginManager { } loadedLocalPlugins = true + afterPluginsLoadedEvent.invoke(true) } /** @@ -380,7 +396,9 @@ object PluginManager { try { val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique val fileName = getPluginSanitizedFileName(internalName) - Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") + unloadPlugin("${activity.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3") + + Log.d(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") // The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName) return loadPlugin( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 975545ee..539ce843 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -456,20 +456,17 @@ class HomeFragment : Fragment() { homeViewModel.loadStoredData(list) } - private var hasBeenConsumed = false private fun firstLoadHomePage(successful: Boolean = false) { // dirty hack to make it only load once - if(hasBeenConsumed) return - hasBeenConsumed = true - loadHomePage(successful) + loadHomePage(false) } - private fun loadHomePage(successful: Boolean = false) { + private fun loadHomePage(forceReload: Boolean = true) { val apiName = context?.getKey(USER_SELECTED_HOMEPAGE_API) if (homeViewModel.apiName.value != apiName || apiName == null) { //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) - homeViewModel.loadAndCancel(apiName) + homeViewModel.loadAndCancel(apiName, forceReload) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index 7319c5b2..daff5e4a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -264,25 +264,30 @@ class HomeViewModel : ViewModel() { } } - fun loadAndCancel(preferredApiName: String?) = viewModelScope.launchSafe { - val api = getApiFromNameNull(preferredApiName) - if (preferredApiName == noneApi.name){ - setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) - loadAndCancel(noneApi) - } - else if (preferredApiName == randomApi.name || api == null) { - val validAPIs = context?.filterProviderByPreferredMedia() - if (validAPIs.isNullOrEmpty()) { - // Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded - loadAndCancel(noneApi) - } else { - val apiRandom = validAPIs.random() - loadAndCancel(apiRandom) - setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) + fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) = + viewModelScope.launchSafe { + // Since plugins are loaded in stages this function can get called multiple times. + // This makes the home page reload only if it's a failure or loading + if (!forceReload && page.value is Resource.Success) { + return@launchSafe + } + val api = getApiFromNameNull(preferredApiName) + if (preferredApiName == noneApi.name) { + setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) + loadAndCancel(noneApi) + } else if (preferredApiName == randomApi.name || api == null) { + val validAPIs = context?.filterProviderByPreferredMedia() + if (validAPIs.isNullOrEmpty()) { + // Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded + loadAndCancel(noneApi) + } else { + val apiRandom = validAPIs.random() + loadAndCancel(apiRandom) + setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) + } + } else { + setKey(USER_SELECTED_HOMEPAGE_API, api.name) + loadAndCancel(api) } - } else { - setKey(USER_SELECTED_HOMEPAGE_API, api.name) - loadAndCancel(api) } - } } \ No newline at end of file