mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	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 | ||||
|          * 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 { | ||||
|  |  | |||
|  | @ -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()) { | ||||
|  |  | |||
|  | @ -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() | ||||
|         } | ||||
|  |  | |||
|  | @ -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 | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
|  | @ -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) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue