From 3fdf41869ef42e3fe1ee7da21725f42178345a76 Mon Sep 17 00:00:00 2001 From: Blatzar <46196380+Blatzar@users.noreply.github.com> Date: Tue, 13 Dec 2022 23:28:31 +0100 Subject: [PATCH] Added hot reloading for plugins when using deployWithAdb --- .../lagradost/cloudstream3/MainActivity.kt | 10 ++++- .../cloudstream3/plugins/PluginManager.kt | 38 +++++++++++++---- .../cloudstream3/ui/APIRepository.kt | 41 +++++++++++++------ .../cloudstream3/ui/home/HomeFragment.kt | 22 +++++----- .../cloudstream3/ui/result/EpisodeAdapter.kt | 1 + .../cloudstream3/ui/result/ResultFragment.kt | 5 +-- .../ui/result/ResultViewModel2.kt | 7 +++- 7 files changed, 89 insertions(+), 35 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 08695ce7..d7963c43 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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() 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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index f2dbb02f..c0bc4f1f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -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>(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()) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 227c2f25..645cd573 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -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 ) - // 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 = arrayListOf() + private val cache = threadSafeListOf() 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() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index b7803e6c..8e2ca6af 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -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(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 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index e9fbd5f9..e5b839a8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt index 26dbb03e..30ea889e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragment.kt @@ -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, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 18e54d14..f5aae7fc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -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) + } } }