mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	batch download plugins
This commit is contained in:
		
							parent
							
								
									4e6bbf3908
								
							
						
					
					
						commit
						32d9aea02c
					
				
					 10 changed files with 150 additions and 29 deletions
				
			
		|  | @ -6,6 +6,7 @@ import android.content.Context | |||
| import android.content.pm.PackageManager | ||||
| import android.content.res.Resources | ||||
| import android.os.Build | ||||
| import android.os.Looper | ||||
| import android.util.Log | ||||
| import android.view.* | ||||
| import android.widget.TextView | ||||
|  | @ -17,11 +18,13 @@ import androidx.preference.PreferenceManager | |||
| import com.google.android.gms.cast.framework.CastSession | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.ui.player.PlayerEventType | ||||
| import com.lagradost.cloudstream3.ui.result.UiText | ||||
| import com.lagradost.cloudstream3.utils.Event | ||||
| import com.lagradost.cloudstream3.utils.UIHelper | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.toPx | ||||
| import kotlinx.coroutines.currentCoroutineContext | ||||
| import org.schabi.newpipe.extractor.NewPipe | ||||
| import java.util.* | ||||
| 
 | ||||
|  | @ -43,6 +46,13 @@ object CommonActivity { | |||
| 
 | ||||
|     var currentToast: Toast? = null | ||||
| 
 | ||||
|     fun showToast(act: Activity?, text: UiText, duration: Int) { | ||||
|         if (act == null) return | ||||
|         text.asStringNull(act)?.let { | ||||
|             showToast(act, it, duration) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun showToast(act: Activity?, @StringRes message: Int, duration: Int) { | ||||
|         if (act == null) return | ||||
|         showToast(act, act.getString(message), duration) | ||||
|  |  | |||
|  | @ -10,7 +10,7 @@ abstract class Plugin { | |||
|      * @param context Context | ||||
|      */ | ||||
|     @Throws(Throwable::class) | ||||
|     open fun load(context: Context?) { | ||||
|     open fun load(context: Context) { | ||||
|     } | ||||
| 
 | ||||
|     class Manifest { | ||||
|  |  | |||
|  | @ -126,10 +126,10 @@ class GeneratorPlayer : FullScreenPlayer() { | |||
|     private fun loadExtractorJob(extractorLink: ExtractorLink?) { | ||||
|         currentVerifyLink?.cancel() | ||||
| 
 | ||||
|         extractorLink?.let { | ||||
|         extractorLink?.let { link -> | ||||
|             currentVerifyLink = ioSafe { | ||||
|                 if (it.extractorData != null) { | ||||
|                     getApiFromNameNull(it.source)?.extractorVerifierJob(it.extractorData) | ||||
|                 if (link.extractorData != null) { | ||||
|                     getApiFromNameNull(link.source)?.extractorVerifierJob(link.extractorData) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  |  | |||
|  | @ -41,6 +41,7 @@ import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast | |||
| import com.lagradost.cloudstream3.utils.CastHelper.startCast | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioWork | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.main | ||||
| import com.lagradost.cloudstream3.utils.DataStore.setKey | ||||
| import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub | ||||
| import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultEpisode | ||||
|  | @ -689,7 +690,7 @@ class ResultViewModel2 : ViewModel() { | |||
|                 }) | ||||
| 
 | ||||
|                 if (currentLinks.isEmpty()) { | ||||
|                     Coroutines.main { | ||||
|                     main { | ||||
|                         showToast( | ||||
|                             activity, | ||||
|                             R.string.no_links_found_toast, | ||||
|  | @ -698,7 +699,7 @@ class ResultViewModel2 : ViewModel() { | |||
|                     } | ||||
|                     return@ioSafe | ||||
|                 } else { | ||||
|                     Coroutines.main { | ||||
|                     main { | ||||
|                         showToast( | ||||
|                             activity, | ||||
|                             R.string.download_started, | ||||
|  |  | |||
|  | @ -1,21 +1,14 @@ | |||
| package com.lagradost.cloudstream3.ui.settings.extensions | ||||
| 
 | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import android.widget.Toast | ||||
| import android.view.* | ||||
| import androidx.appcompat.view.menu.MenuBuilder | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.activityViewModels | ||||
| import com.lagradost.cloudstream3.CommonActivity.showToast | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.mvvm.observe | ||||
| import com.lagradost.cloudstream3.plugins.PluginManager | ||||
| import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.main | ||||
| import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar | ||||
| import kotlinx.android.synthetic.main.fragment_extensions.* | ||||
| import kotlinx.android.synthetic.main.fragment_plugins.* | ||||
| 
 | ||||
| const val PLUGINS_BUNDLE_NAME = "name" | ||||
| const val PLUGINS_BUNDLE_URL = "url" | ||||
|  | @ -26,7 +19,7 @@ class PluginsFragment : Fragment() { | |||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle?, | ||||
|     ): View? { | ||||
|         return inflater.inflate(R.layout.fragment_extensions, container, false) | ||||
|         return inflater.inflate(R.layout.fragment_plugins, container, false) | ||||
|     } | ||||
| 
 | ||||
|     private val pluginViewModel: PluginsViewModel by activityViewModels() | ||||
|  | @ -37,20 +30,30 @@ class PluginsFragment : Fragment() { | |||
|         val name = arguments?.getString(PLUGINS_BUNDLE_NAME) | ||||
|         val url = arguments?.getString(PLUGINS_BUNDLE_URL) | ||||
| 
 | ||||
|         if (url == null) { | ||||
|         if (url == null || name == null) { | ||||
|             activity?.onBackPressed() | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         setUpToolbar(name ?: "Unknown") | ||||
|         setUpToolbar(name) | ||||
| 
 | ||||
|         repo_recycler_view?.adapter = | ||||
|         settings_toolbar?.setOnMenuItemClickListener { menuItem -> | ||||
|             when (menuItem?.itemId) { | ||||
|                 R.id.download_all -> { | ||||
|                     pluginViewModel.downloadAll(activity, url) | ||||
|                 } | ||||
|                 else -> {} | ||||
|             } | ||||
|             return@setOnMenuItemClickListener true | ||||
|         } | ||||
| 
 | ||||
|         plugin_recycler_view?.adapter = | ||||
|             PluginAdapter { | ||||
|                 pluginViewModel.handlePluginAction(activity, url, it) | ||||
|             } | ||||
| 
 | ||||
|         observe(pluginViewModel.plugins) { | ||||
|             (repo_recycler_view?.adapter as? PluginAdapter?)?.updateList(it) | ||||
|             (plugin_recycler_view?.adapter as? PluginAdapter?)?.updateList(it) | ||||
|         } | ||||
| 
 | ||||
|         pluginViewModel.updatePluginList(url) | ||||
|  |  | |||
|  | @ -9,11 +9,14 @@ import androidx.lifecycle.ViewModel | |||
| import androidx.lifecycle.viewModelScope | ||||
| import com.lagradost.cloudstream3.CommonActivity.showToast | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.plugins.PluginData | ||||
| import com.lagradost.cloudstream3.plugins.PluginManager | ||||
| import com.lagradost.cloudstream3.plugins.RepositoryManager | ||||
| import com.lagradost.cloudstream3.plugins.SitePlugin | ||||
| import com.lagradost.cloudstream3.ui.result.txt | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.main | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread | ||||
| import kotlinx.coroutines.launch | ||||
| 
 | ||||
|  | @ -23,9 +26,8 @@ class PluginsViewModel : ViewModel() { | |||
|     private val _plugins = MutableLiveData<List<PluginViewData>>() | ||||
|     val plugins: LiveData<List<PluginViewData>> = _plugins | ||||
| 
 | ||||
|     private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf() | ||||
| 
 | ||||
|     companion object { | ||||
|         private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf() | ||||
|         const val TAG = "PLG" | ||||
|     } | ||||
| 
 | ||||
|  | @ -55,6 +57,51 @@ class PluginsViewModel : ViewModel() { | |||
|         return (data ?: getDownloads()).contains(plugin.second.internalName) | ||||
|     } | ||||
| 
 | ||||
|     fun downloadAll(activity: Activity?, repositoryUrl: String) = ioSafe { | ||||
|         if (activity == null) return@ioSafe | ||||
|         val stored = getDownloads() | ||||
|         val plugins = getPlugins(repositoryUrl) | ||||
| 
 | ||||
|         plugins.filter { plugin -> !isDownloaded(plugin, stored) }.also { list -> | ||||
|             main { | ||||
|                 showToast( | ||||
|                     activity, | ||||
|                     if (list.isEmpty()) { | ||||
|                         txt( | ||||
|                             R.string.batch_download_nothing_to_download_format, | ||||
|                             txt(R.string.plugin) | ||||
|                         ) | ||||
|                     } else { | ||||
|                         txt(R.string.batch_download_start_format, list.size, txt(if(list.size == 1) R.string.plugin_singular else R.string.plugin)) | ||||
|                     }, | ||||
|                     Toast.LENGTH_SHORT | ||||
|                 ) | ||||
|             } | ||||
|         }.apmap { (repo, metadata) -> | ||||
|             PluginManager.downloadAndLoadPlugin( | ||||
|                 activity, | ||||
|                 metadata.url, | ||||
|                 metadata.name, | ||||
|                 repo | ||||
|             ) | ||||
|         }.main { list -> | ||||
|             if (list.any { it }) { | ||||
|                 showToast( | ||||
|                     activity, | ||||
|                     txt( | ||||
|                         R.string.batch_download_finish_format, | ||||
|                         list.count { it }, | ||||
|                         txt(if(list.size == 1) R.string.plugin_singular else R.string.plugin) | ||||
|                     ), | ||||
|                     Toast.LENGTH_SHORT | ||||
|                 ) | ||||
|                 updatePluginListPrivate(repositoryUrl) | ||||
|             } else if (list.isNotEmpty()) { | ||||
|                 showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun handlePluginAction(activity: Activity?, repositoryUrl: String, plugin: Plugin) = ioSafe { | ||||
|         Log.i(TAG, "handlePluginAction = $repositoryUrl, $plugin") | ||||
| 
 | ||||
|  |  | |||
|  | @ -3,28 +3,34 @@ package com.lagradost.cloudstream3.utils | |||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
| import com.lagradost.cloudstream3.mvvm.logError | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.ioSafe | ||||
| import com.lagradost.cloudstream3.utils.Coroutines.main | ||||
| import kotlinx.coroutines.* | ||||
| 
 | ||||
| object Coroutines { | ||||
|     fun main(work: suspend (() -> Unit)): Job { | ||||
|     fun <T> T.main(work: suspend ((T) -> Unit)): Job { | ||||
|         val value = this | ||||
|         return CoroutineScope(Dispatchers.Main).launch { | ||||
|             work() | ||||
|             work(value) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun ioSafe(work: suspend (CoroutineScope.() -> Unit)): Job { | ||||
|     fun <T> T.ioSafe(work: suspend (CoroutineScope.(T) -> Unit)): Job { | ||||
|         val value = this | ||||
| 
 | ||||
|         return CoroutineScope(Dispatchers.IO).launch { | ||||
|             try { | ||||
|                 work() | ||||
|                 work(value) | ||||
|             } catch (e: Exception) { | ||||
|                 logError(e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     suspend fun <T> ioWork(work: suspend (CoroutineScope.() -> T)): T { | ||||
|     suspend fun <T, V> V.ioWork(work: suspend (CoroutineScope.(V) -> T)): T { | ||||
|         val value = this | ||||
|         return withContext(Dispatchers.IO) { | ||||
|             work() | ||||
|             work(value) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										39
									
								
								app/src/main/res/layout/fragment_plugins.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/src/main/res/layout/fragment_plugins.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,39 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| 
 | ||||
| <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|         xmlns:tools="http://schemas.android.com/tools" | ||||
|         android:id="@+id/extensions_root" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" | ||||
|         android:orientation="vertical"> | ||||
| 
 | ||||
|     <com.google.android.material.appbar.AppBarLayout | ||||
|             android:background="@android:color/transparent" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="wrap_content"> | ||||
| 
 | ||||
|         <com.google.android.material.appbar.MaterialToolbar | ||||
|                 app:menu="@menu/repository" | ||||
|                 android:id="@+id/settings_toolbar" | ||||
|                 android:paddingTop="@dimen/navbar_height" | ||||
|                 tools:title="Overlord" | ||||
|                 android:background="?attr/primaryGrayBackground" | ||||
|                 app:navigationIconTint="?attr/iconColor" | ||||
|                 app:titleTextColor="?attr/textColor" | ||||
|                 app:layout_scrollFlags="scroll|enterAlways" | ||||
|                 android:layout_width="match_parent" | ||||
|                 android:layout_height="wrap_content" /> | ||||
|     </com.google.android.material.appbar.AppBarLayout> | ||||
| 
 | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|             app:layout_behavior="@string/appbar_scrolling_view_behavior" | ||||
|             tools:listitem="@layout/repository_item" | ||||
|             android:id="@+id/plugin_recycler_view" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> | ||||
| 
 | ||||
| 
 | ||||
| </androidx.coordinatorlayout.widget.CoordinatorLayout> | ||||
| 
 | ||||
							
								
								
									
										9
									
								
								app/src/main/res/menu/repository.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								app/src/main/res/menu/repository.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,9 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <menu xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         xmlns:app="http://schemas.android.com/apk/res-auto"> | ||||
|     <item | ||||
|             app:showAsAction="always" | ||||
|             android:id="@+id/download_all" | ||||
|             android:icon="@drawable/netflix_download" | ||||
|             android:title="@string/batch_download" /> | ||||
| </menu> | ||||
|  | @ -577,4 +577,10 @@ | |||
|     <string name="plugin_deleted">Plugin Deleted</string> | ||||
|     <string name="plugin_load_fail" formatted="true">Failed to load %s</string> | ||||
|     <string name="is_adult">18+</string> | ||||
|     <string name="batch_download_start_format" formatted="true">Started downloading %d %s</string> | ||||
|     <string name="batch_download_finish_format" formatted="true">Downloaded %d %s successfully</string> | ||||
|     <string name="batch_download_nothing_to_download_format" formatted="true">All %s already downloaded</string> | ||||
|     <string name="batch_download">Batch download</string> | ||||
|     <string name="plugin_singular" formatted="true">plugin</string> | ||||
|     <string name="plugin" formatted="true">plugins</string> | ||||
| </resources> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue