Added hot reloading for plugins when using deployWithAdb

This commit is contained in:
Blatzar 2022-12-13 23:28:31 +01:00
parent 7362ac9f64
commit 3fdf41869e
7 changed files with 89 additions and 35 deletions

View File

@ -232,6 +232,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
/**
* Fires every time a new batch of plugins have been loaded, no guarantee about how often this is run and on which thread
* Boolean signifies if stuff should be force reloaded (true if force reload, false if reload when necessary).
*
* The force reloading are used for plugin development to instantly reload the page on deployWithAdb
* */
val afterPluginsLoadedEvent = Event<Boolean>()
val mainPluginsLoadedEvent =
@ -286,6 +289,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
return true
}
}
// This specific intent is used for the gradle deployWithAdb
// https://github.com/recloudstream/gradle/blob/master/src/main/kotlin/com/lagradost/cloudstream3/gradle/tasks/DeployWithAdbTask.kt#L46
if (str == "$appString:") {
PluginManager.hotReloadAllLocalPlugins(activity)
}
} else if (safeURI(str)?.scheme == appStringRepo) {
val url = str.replaceFirst(appStringRepo, "https")
loadRepository(url)
@ -650,7 +658,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
ioSafe {
PluginManager.loadAllLocalPlugins(this@MainActivity)
PluginManager.loadAllLocalPlugins(this@MainActivity, false)
}
}
} else {

View File

@ -10,6 +10,7 @@ import android.util.Log
import android.widget.Toast
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty
import com.google.gson.Gson
import com.lagradost.cloudstream3.*
@ -223,7 +224,7 @@ object PluginManager {
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
// Load all plugins as fast as possible!
loadAllOnlinePlugins(activity)
afterPluginsLoadedEvent.invoke(true)
afterPluginsLoadedEvent.invoke(false)
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES
@ -272,7 +273,7 @@ object PluginManager {
}
// ioSafe {
afterPluginsLoadedEvent.invoke(true)
afterPluginsLoadedEvent.invoke(false)
// }
Log.i(TAG, "Plugin update done!")
@ -299,8 +300,12 @@ object PluginManager {
val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData ->
val sitePlugin = onlineData.second
//Don't include empty urls
if (sitePlugin.url.isBlank()) { return@mapNotNull null }
if (sitePlugin.repositoryUrl.isNullOrBlank()) { return@mapNotNull null }
if (sitePlugin.url.isBlank()) {
return@mapNotNull null
}
if (sitePlugin.repositoryUrl.isNullOrBlank()) {
return@mapNotNull null
}
//Omit already existing plugins
if (getPluginPath(activity, sitePlugin.internalName, onlineData.first).exists()) {
@ -353,7 +358,7 @@ object PluginManager {
}
// ioSafe {
afterPluginsLoadedEvent.invoke(true)
afterPluginsLoadedEvent.invoke(false)
// }
Log.i(TAG, "Plugin download done!")
@ -373,7 +378,23 @@ object PluginManager {
}
}
fun loadAllLocalPlugins(activity: Activity) {
/**
* Reloads all local plugins and forces a page update, used for hot reloading with deployWithAdb
**/
fun hotReloadAllLocalPlugins(activity: FragmentActivity?) {
Log.d(TAG, "Reloading all local plugins!")
if (activity == null) return
getPluginsLocal().forEach {
unloadPlugin(it.filePath)
}
loadAllLocalPlugins(activity, true)
}
/**
* @param forceReload see afterPluginsLoadedEvent, basically a way to load all local plugins
* and reload all pages even if they are previously valid
**/
fun loadAllLocalPlugins(activity: Activity, forceReload: Boolean) {
val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL)
@ -395,7 +416,7 @@ object PluginManager {
}
loadedLocalPlugins = true
afterPluginsLoadedEvent.invoke(true)
afterPluginsLoadedEvent.invoke(forceReload)
}
/**
@ -576,7 +597,8 @@ object PluginManager {
}
suspend fun deletePlugin(file: File): Boolean {
val list = (getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
val list =
(getPluginsLocal() + getPluginsOnline()).filter { it.filePath == file.absolutePath }
return try {
if (File(file.absolutePath).delete()) {

View File

@ -3,9 +3,11 @@ package com.lagradost.cloudstream3.ui
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
@ -38,13 +40,23 @@ class APIRepository(val api: MainAPI) {
val hash: Pair<String, String>
)
// This really does not need to be Nullable but it can crash otherwise, probably caused by replacing items while looping over them.
// "Attempt to invoke .... getHash() on a null object reference"
private val cache: ArrayList<SavedLoadResponse?> = arrayListOf()
private val cache = threadSafeListOf<SavedLoadResponse>()
private var cacheIndex: Int = 0
const val cacheSize = 20
}
private fun afterPluginsLoaded(forceReload: Boolean) {
if (forceReload) {
synchronized(cache) {
cache.clear()
}
}
}
init {
afterPluginsLoadedEvent += ::afterPluginsLoaded
}
val hasMainPage = api.hasMainPage
val providerType = api.providerType
val name = api.name
@ -59,20 +71,25 @@ class APIRepository(val api: MainAPI) {
val fixedUrl = api.fixUrl(url)
val lookingForHash = Pair(api.name, fixedUrl)
for (item in cache) {
// 10 min save
if (item?.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) {
return@safeApiCall item.response
synchronized(cache) {
for (item in cache) {
// 10 min save
if (item.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) {
return@safeApiCall item.response
}
}
}
api.load(fixedUrl)?.also { response ->
val add = SavedLoadResponse(unixTime, response, lookingForHash)
if (cache.size > cacheSize) {
cache[cacheIndex] = add // rolling cache
cacheIndex = (cacheIndex + 1) % cacheSize
} else {
cache.add(add)
synchronized(cache) {
if (cache.size > cacheSize) {
cache[cacheIndex] = add // rolling cache
cacheIndex = (cacheIndex + 1) % cacheSize
} else {
cache.add(add)
}
}
} ?: throw ErrorLoadingException()
}

View File

@ -79,7 +79,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.getStatusBarHeight
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
@ -475,13 +474,13 @@ class HomeFragment : Fragment() {
override fun onResume() {
super.onResume()
reloadStored()
afterPluginsLoadedEvent += ::firstLoadHomePage
mainPluginsLoadedEvent += ::firstLoadHomePage
afterPluginsLoadedEvent += ::afterPluginsLoaded
mainPluginsLoadedEvent += ::afterMainPluginsLoaded
}
override fun onStop() {
afterPluginsLoadedEvent -= ::firstLoadHomePage
mainPluginsLoadedEvent -= ::firstLoadHomePage
afterPluginsLoadedEvent -= ::afterPluginsLoaded
mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
super.onStop()
}
@ -494,15 +493,18 @@ class HomeFragment : Fragment() {
homeViewModel.loadStoredData(list)
}
private fun firstLoadHomePage(successful: Boolean = false) {
// dirty hack to make it only load once
private fun afterMainPluginsLoaded(unused: Boolean = false) {
loadHomePage(false)
}
private fun loadHomePage(forceReload: Boolean = true) {
private fun afterPluginsLoaded(forceReload: Boolean) {
loadHomePage(forceReload)
}
private fun loadHomePage(forceReload: Boolean) {
val apiName = context?.getKey<String>(USER_SELECTED_HOMEPAGE_API)
if (homeViewModel.apiName.value != apiName || apiName == null) {
if (homeViewModel.apiName.value != apiName || apiName == null || forceReload) {
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName, forceReload)
}
@ -1120,7 +1122,7 @@ class HomeFragment : Fragment() {
} // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
reloadStored()
loadHomePage()
loadHomePage(false)
home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { v, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY

View File

@ -58,6 +58,7 @@ const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
const val ACTION_PLAY_EPISODE_IN_MPV = 17
const val ACTION_MARK_AS_WATCHED = 18
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)

View File

@ -491,11 +491,10 @@ open class ResultFragment : ResultTrailerPlayer() {
return StoredData(url, apiName, showFillers, dubStatus, start, playerAction)
}
private fun reloadViewModel(success: Boolean = false) {
if (!viewModel.hasLoaded()) {
private fun reloadViewModel(forceReload: Boolean) {
if (!viewModel.hasLoaded() || forceReload) {
val storedData = getStoredData(activity ?: context ?: return) ?: return
//viewModel.clear()
viewModel.load(
activity,
storedData.url ?: return,

View File

@ -1146,6 +1146,7 @@ class ResultViewModel2 : ViewModel() {
txt(R.string.episode_action_download_mirror) to ACTION_DOWNLOAD_MIRROR,
txt(R.string.episode_action_download_subtitle) to ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR,
txt(R.string.episode_action_reload_links) to ACTION_RELOAD_EPISODE,
// txt(R.string.action_mark_as_watched) to ACTION_MARK_AS_WATCHED,
)
)
@ -1365,7 +1366,7 @@ class ResultViewModel2 : ViewModel() {
R.id.global_to_navigation_player,
GeneratorPlayer.newInstance(
generator?.also {
it.getAll() // I know kinda shit to itterate all, but it is 100% sure to work
it.getAll() // I know kinda shit to iterate all, but it is 100% sure to work
?.indexOfFirst { value -> value is ResultEpisode && value.id == click.data.id }
?.let { index ->
if (index >= 0)
@ -1376,6 +1377,10 @@ class ResultViewModel2 : ViewModel() {
)
)
}
ACTION_MARK_AS_WATCHED -> {
// TODO FIX
// DataStoreHelper.setViewPos(click.data.id, 1, 1)
}
}
}