forked from recloudstream/cloudstream
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…
Reference in a new issue