forked from recloudstream/cloudstream
Added hot reloading for plugins when using deployWithAdb
This commit is contained in:
parent
7362ac9f64
commit
3fdf41869e
7 changed files with 89 additions and 35 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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()) {
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue