mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Optimized plugin loading
This commit is contained in:
		
							parent
							
								
									8ff2540f4b
								
							
						
					
					
						commit
						0f8a2df9e5
					
				
					 4 changed files with 108 additions and 69 deletions
				
			
		|  | @ -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<Boolean>() | ||||
|         val mainPluginsLoadedEvent = | ||||
|             Event<Boolean>() // 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<Array<SettingsGeneral.CustomSite>>(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<Array<SettingsGeneral.CustomSite>>(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 { | ||||
|  |  | |||
|  | @ -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<OnlinePluginData> = emptySet() | ||||
|     // var allCurrentOutDatedPlugins: Set<OnlinePluginData> = 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<Array<RepositoryData>>(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( | ||||
|  |  | |||
|  | @ -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<String>(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) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue