Optimized plugin loading

This commit is contained in:
Blatzar 2022-08-23 21:28:42 +02:00
parent 8ff2540f4b
commit 0f8a2df9e5
4 changed files with 108 additions and 69 deletions

View file

@ -84,6 +84,8 @@ import com.lagradost.nicehttp.Requests
import com.lagradost.nicehttp.ResponseParser import com.lagradost.nicehttp.ResponseParser
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_result_swipe.* import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -131,6 +133,10 @@ var app = Requests(responseParser = object : ResponseParser {
class MainActivity : AppCompatActivity(), ColorPickerDialogListener { class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
companion object { companion object {
const val TAG = "MAINACT" 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 afterPluginsLoadedEvent = Event<Boolean>()
val mainPluginsLoadedEvent = val mainPluginsLoadedEvent =
Event<Boolean>() // homepage api, used to speed up time to load for homepage Event<Boolean>() // homepage api, used to speed up time to load for homepage
@ -236,6 +242,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
afterPluginsLoadedEvent += ::onAllPluginsLoaded
try { try {
if (isCastApiAvailable()) { if (isCastApiAvailable()) {
//mCastSession = mSessionManager.currentCastSession //mCastSession = mSessionManager.currentCastSession
@ -325,6 +332,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
broadcastIntent.action = "restart_service" broadcastIntent.action = "restart_service"
broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java) broadcastIntent.setClass(this, VideoDownloadRestartReceiver::class.java)
this.sendBroadcast(broadcastIntent) this.sendBroadcast(broadcastIntent)
afterPluginsLoadedEvent -= ::onAllPluginsLoaded
super.onDestroy() 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?) { override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(this) app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
@ -446,36 +484,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
mainPluginsLoadedEvent.invoke(false) mainPluginsLoadedEvent.invoke(false)
} }
if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { ioSafe {
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) {
} else { PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
PluginManager.loadAllOnlinePlugins(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
})
}
}
} }
apis = allProviders.distinctBy { it }
APIHolder.apiMap = null
} catch (e: Exception) {
logError(e)
} }
afterPluginsLoadedEvent.invoke(true) ioSafe {
PluginManager.loadAllLocalPlugins(this@MainActivity)
}
} }
// ioSafe { // ioSafe {

View file

@ -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.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.APIHolder.removePluginMapping import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES 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.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis import com.lagradost.cloudstream3.utils.extractorApis
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import org.acra.log.debug
import java.io.File import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.* import java.util.*
@ -162,7 +165,7 @@ object PluginManager {
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN 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 { suspend fun loadSinglePlugin(activity: Activity, apiName: String): Boolean {
return (getPluginsOnline().firstOrNull { it.internalName == apiName } return (getPluginsOnline().firstOrNull { it.internalName == apiName }
@ -184,6 +187,19 @@ object PluginManager {
* 4. Else load the plugin normally * 4. Else load the plugin normally
**/ **/
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { 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) val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES ?: emptyArray()) + PREBUILT_REPOSITORIES
@ -198,29 +214,28 @@ object PluginManager {
OnlinePluginData(savedData, onlineData) OnlinePluginData(savedData, onlineData)
} }
}.flatten().distinctBy { it.onlineData.second.url } }.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 { outdatedPlugins.apmap { pluginData ->
if (it.isDisabled) { if (pluginData.isDisabled) {
return@apmap unloadPlugin(pluginData.savedData.filePath)
} else if (it.isOutdated) { } else if (pluginData.isOutdated) {
downloadAndLoadPlugin( downloadAndLoadPlugin(
activity, activity,
it.onlineData.second.url, pluginData.onlineData.second.url,
it.savedData.internalName, pluginData.savedData.internalName,
it.onlineData.first pluginData.onlineData.first
)
} else {
loadPlugin(
activity,
File(it.savedData.filePath),
it.savedData
) )
} }
} }
ioSafe {
afterPluginsLoadedEvent.invoke(true)
}
Log.i(TAG, "Plugin update done!") Log.i(TAG, "Plugin update done!")
} }
@ -256,6 +271,7 @@ object PluginManager {
} }
loadedLocalPlugins = true loadedLocalPlugins = true
afterPluginsLoadedEvent.invoke(true)
} }
/** /**
@ -380,7 +396,9 @@ object PluginManager {
try { try {
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
val fileName = getPluginSanitizedFileName(internalName) 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 // 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) val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName)
return loadPlugin( return loadPlugin(

View file

@ -456,20 +456,17 @@ class HomeFragment : Fragment() {
homeViewModel.loadStoredData(list) homeViewModel.loadStoredData(list)
} }
private var hasBeenConsumed = false
private fun firstLoadHomePage(successful: Boolean = false) { private fun firstLoadHomePage(successful: Boolean = false) {
// dirty hack to make it only load once // dirty hack to make it only load once
if(hasBeenConsumed) return loadHomePage(false)
hasBeenConsumed = true
loadHomePage(successful)
} }
private fun loadHomePage(successful: Boolean = false) { private fun loadHomePage(forceReload: Boolean = true) {
val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API) val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API)
if (homeViewModel.apiName.value != apiName || apiName == null) { if (homeViewModel.apiName.value != apiName || apiName == null) {
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName) homeViewModel.loadAndCancel(apiName, forceReload)
} }
} }

View file

@ -264,25 +264,30 @@ class HomeViewModel : ViewModel() {
} }
} }
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launchSafe { fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) =
val api = getApiFromNameNull(preferredApiName) viewModelScope.launchSafe {
if (preferredApiName == noneApi.name){ // Since plugins are loaded in stages this function can get called multiple times.
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) // This makes the home page reload only if it's a failure or loading
loadAndCancel(noneApi) if (!forceReload && page.value is Resource.Success) {
} return@launchSafe
else if (preferredApiName == randomApi.name || api == null) { }
val validAPIs = context?.filterProviderByPreferredMedia() val api = getApiFromNameNull(preferredApiName)
if (validAPIs.isNullOrEmpty()) { if (preferredApiName == noneApi.name) {
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
loadAndCancel(noneApi) loadAndCancel(noneApi)
} else { } else if (preferredApiName == randomApi.name || api == null) {
val apiRandom = validAPIs.random() val validAPIs = context?.filterProviderByPreferredMedia()
loadAndCancel(apiRandom) if (validAPIs.isNullOrEmpty()) {
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) // 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)
} }
}
} }