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 * 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 afterPluginsLoadedEvent = Event<Boolean>()
val mainPluginsLoadedEvent = val mainPluginsLoadedEvent =
@ -286,6 +289,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
return true 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) { } else if (safeURI(str)?.scheme == appStringRepo) {
val url = str.replaceFirst(appStringRepo, "https") val url = str.replaceFirst(appStringRepo, "https")
loadRepository(url) loadRepository(url)
@ -650,7 +658,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
ioSafe { ioSafe {
PluginManager.loadAllLocalPlugins(this@MainActivity) PluginManager.loadAllLocalPlugins(this@MainActivity, false)
} }
} }
} else { } else {

View file

@ -10,6 +10,7 @@ import android.util.Log
import android.widget.Toast import android.widget.Toast
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.fragment.app.FragmentActivity
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.google.gson.Gson import com.google.gson.Gson
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
@ -223,7 +224,7 @@ object PluginManager {
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
// Load all plugins as fast as possible! // Load all plugins as fast as possible!
loadAllOnlinePlugins(activity) loadAllOnlinePlugins(activity)
afterPluginsLoadedEvent.invoke(true) afterPluginsLoadedEvent.invoke(false)
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY) val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
?: emptyArray()) + PREBUILT_REPOSITORIES ?: emptyArray()) + PREBUILT_REPOSITORIES
@ -272,7 +273,7 @@ object PluginManager {
} }
// ioSafe { // ioSafe {
afterPluginsLoadedEvent.invoke(true) afterPluginsLoadedEvent.invoke(false)
// } // }
Log.i(TAG, "Plugin update done!") Log.i(TAG, "Plugin update done!")
@ -299,8 +300,12 @@ object PluginManager {
val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData -> val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData ->
val sitePlugin = onlineData.second val sitePlugin = onlineData.second
//Don't include empty urls //Don't include empty urls
if (sitePlugin.url.isBlank()) { return@mapNotNull null } if (sitePlugin.url.isBlank()) {
if (sitePlugin.repositoryUrl.isNullOrBlank()) { return@mapNotNull null } return@mapNotNull null
}
if (sitePlugin.repositoryUrl.isNullOrBlank()) {
return@mapNotNull null
}
//Omit already existing plugins //Omit already existing plugins
if (getPluginPath(activity, sitePlugin.internalName, onlineData.first).exists()) { if (getPluginPath(activity, sitePlugin.internalName, onlineData.first).exists()) {
@ -353,7 +358,7 @@ object PluginManager {
} }
// ioSafe { // ioSafe {
afterPluginsLoadedEvent.invoke(true) afterPluginsLoadedEvent.invoke(false)
// } // }
Log.i(TAG, "Plugin download done!") 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) val dir = File(LOCAL_PLUGINS_PATH)
removeKey(PLUGINS_KEY_LOCAL) removeKey(PLUGINS_KEY_LOCAL)
@ -395,7 +416,7 @@ object PluginManager {
} }
loadedLocalPlugins = true loadedLocalPlugins = true
afterPluginsLoadedEvent.invoke(true) afterPluginsLoadedEvent.invoke(forceReload)
} }
/** /**
@ -576,7 +597,8 @@ object PluginManager {
} }
suspend fun deletePlugin(file: File): Boolean { 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 { return try {
if (File(file.absolutePath).delete()) { if (File(file.absolutePath).delete()) {

View file

@ -3,9 +3,11 @@ package com.lagradost.cloudstream3.ui
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTime
import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.APIHolder.unixTimeMS
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf
import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLink
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
@ -38,13 +40,23 @@ class APIRepository(val api: MainAPI) {
val hash: Pair<String, String> 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. private val cache = threadSafeListOf<SavedLoadResponse>()
// "Attempt to invoke .... getHash() on a null object reference"
private val cache: ArrayList<SavedLoadResponse?> = arrayListOf()
private var cacheIndex: Int = 0 private var cacheIndex: Int = 0
const val cacheSize = 20 const val cacheSize = 20
} }
private fun afterPluginsLoaded(forceReload: Boolean) {
if (forceReload) {
synchronized(cache) {
cache.clear()
}
}
}
init {
afterPluginsLoadedEvent += ::afterPluginsLoaded
}
val hasMainPage = api.hasMainPage val hasMainPage = api.hasMainPage
val providerType = api.providerType val providerType = api.providerType
val name = api.name val name = api.name
@ -59,21 +71,26 @@ class APIRepository(val api: MainAPI) {
val fixedUrl = api.fixUrl(url) val fixedUrl = api.fixUrl(url)
val lookingForHash = Pair(api.name, fixedUrl) val lookingForHash = Pair(api.name, fixedUrl)
synchronized(cache) {
for (item in cache) { for (item in cache) {
// 10 min save // 10 min save
if (item?.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) { if (item.hash == lookingForHash && (unixTime - item.unixTime) < 60 * 10) {
return@safeApiCall item.response return@safeApiCall item.response
} }
} }
}
api.load(fixedUrl)?.also { response -> api.load(fixedUrl)?.also { response ->
val add = SavedLoadResponse(unixTime, response, lookingForHash) val add = SavedLoadResponse(unixTime, response, lookingForHash)
synchronized(cache) {
if (cache.size > cacheSize) { if (cache.size > cacheSize) {
cache[cacheIndex] = add // rolling cache cache[cacheIndex] = add // rolling cache
cacheIndex = (cacheIndex + 1) % cacheSize cacheIndex = (cacheIndex + 1) % cacheSize
} else { } else {
cache.add(add) cache.add(add)
} }
}
} ?: throw ErrorLoadingException() } ?: 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.fixPaddingStatusbarView
import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount 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.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.setImage
import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur import com.lagradost.cloudstream3.utils.UIHelper.setImageBlur
@ -475,13 +474,13 @@ class HomeFragment : Fragment() {
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
reloadStored() reloadStored()
afterPluginsLoadedEvent += ::firstLoadHomePage afterPluginsLoadedEvent += ::afterPluginsLoaded
mainPluginsLoadedEvent += ::firstLoadHomePage mainPluginsLoadedEvent += ::afterMainPluginsLoaded
} }
override fun onStop() { override fun onStop() {
afterPluginsLoadedEvent -= ::firstLoadHomePage afterPluginsLoadedEvent -= ::afterPluginsLoaded
mainPluginsLoadedEvent -= ::firstLoadHomePage mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
super.onStop() super.onStop()
} }
@ -494,15 +493,18 @@ class HomeFragment : Fragment() {
homeViewModel.loadStoredData(list) homeViewModel.loadStoredData(list)
} }
private fun firstLoadHomePage(successful: Boolean = false) { private fun afterMainPluginsLoaded(unused: Boolean = false) {
// dirty hack to make it only load once
loadHomePage(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) 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) //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName, forceReload) homeViewModel.loadAndCancel(apiName, forceReload)
} }
@ -1120,7 +1122,7 @@ class HomeFragment : Fragment() {
} // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() } } // GridLayoutManager(context, 1).also { it.supportsPredictiveItemAnimations() }
reloadStored() reloadStored()
loadHomePage() loadHomePage(false)
home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { v, _, scrollY, _, oldScrollY -> home_loaded.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { v, _, scrollY, _, oldScrollY ->
val dy = 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_WEB_VIDEO = 16
const val ACTION_PLAY_EPISODE_IN_MPV = 17 const val ACTION_PLAY_EPISODE_IN_MPV = 17
const val ACTION_MARK_AS_WATCHED = 18
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) 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) return StoredData(url, apiName, showFillers, dubStatus, start, playerAction)
} }
private fun reloadViewModel(success: Boolean = false) { private fun reloadViewModel(forceReload: Boolean) {
if (!viewModel.hasLoaded()) { if (!viewModel.hasLoaded() || forceReload) {
val storedData = getStoredData(activity ?: context ?: return) ?: return val storedData = getStoredData(activity ?: context ?: return) ?: return
//viewModel.clear()
viewModel.load( viewModel.load(
activity, activity,
storedData.url ?: return, 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_mirror) to ACTION_DOWNLOAD_MIRROR,
txt(R.string.episode_action_download_subtitle) to ACTION_DOWNLOAD_EPISODE_SUBTITLE_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.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, R.id.global_to_navigation_player,
GeneratorPlayer.newInstance( GeneratorPlayer.newInstance(
generator?.also { 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 } ?.indexOfFirst { value -> value is ResultEpisode && value.id == click.data.id }
?.let { index -> ?.let { index ->
if (index >= 0) if (index >= 0)
@ -1376,6 +1377,10 @@ class ResultViewModel2 : ViewModel() {
) )
) )
} }
ACTION_MARK_AS_WATCHED -> {
// TODO FIX
// DataStoreHelper.setViewPos(click.data.id, 1, 1)
}
} }
} }