mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Add local file management
This commit is contained in:
		
							parent
							
								
									0dc8077296
								
							
						
					
					
						commit
						815838f17b
					
				
					 6 changed files with 156 additions and 68 deletions
				
			
		|  | @ -9,22 +9,18 @@ import android.widget.Toast | |||
| import android.app.Activity | ||||
| import android.util.Log | ||||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.* | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.getKey | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.setKey | ||||
| import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDER | ||||
| import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile | ||||
| import com.lagradost.cloudstream3.CommonActivity.showToast | ||||
| import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN | ||||
| import com.lagradost.cloudstream3.R | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins | ||||
| import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY | ||||
| import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData | ||||
| import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename | ||||
| import com.lagradost.cloudstream3.APIHolder | ||||
| import com.lagradost.cloudstream3.APIHolder.removePluginMapping | ||||
| import com.lagradost.cloudstream3.MainAPI | ||||
| import com.lagradost.cloudstream3.utils.ExtractorApi | ||||
| import com.lagradost.cloudstream3.utils.extractorApis | ||||
| import kotlinx.coroutines.sync.Mutex | ||||
|  | @ -45,7 +41,24 @@ data class PluginData( | |||
|     @JsonProperty("isOnline") val isOnline: Boolean, | ||||
|     @JsonProperty("filePath") val filePath: String, | ||||
|     @JsonProperty("version") val version: Int, | ||||
| ) | ||||
| ) { | ||||
|     fun toSitePlugin(): SitePlugin { | ||||
|         return SitePlugin( | ||||
|             this.filePath, | ||||
|             PROVIDER_STATUS_OK, | ||||
|             maxOf(1, version), | ||||
|             1, | ||||
|             internalName, | ||||
|             internalName, | ||||
|             emptyList(), | ||||
|             File(this.filePath).name, | ||||
|             null, | ||||
|             null, | ||||
|             null, | ||||
|             null | ||||
|         ) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // This is used as a placeholder / not set version | ||||
| const val PLUGIN_VERSION_NOT_SET = Int.MIN_VALUE | ||||
|  | @ -74,18 +87,28 @@ object PluginManager { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private suspend fun deletePluginData(data: PluginData) { | ||||
|     private suspend fun deletePluginData(data: PluginData?) { | ||||
|         if (data == null) return | ||||
|         lock.withLock { | ||||
|             if (data.isOnline) { | ||||
|                 val plugins = getPluginsOnline().filter { it.url != data.url } | ||||
|                 setKey(PLUGINS_KEY, plugins) | ||||
|             } else { | ||||
|                 val plugins = getPluginsLocal().filter { it.filePath != data.filePath } | ||||
|                 setKey(PLUGINS_KEY_LOCAL, plugins + data) | ||||
|                 setKey(PLUGINS_KEY_LOCAL, plugins) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     suspend fun deleteRepositoryData(repositoryPath: String) { | ||||
|         lock.withLock { | ||||
|             val plugins = getPluginsOnline().filter { | ||||
|                 !it.filePath.contains(repositoryPath) | ||||
|             } | ||||
|             setKey(PLUGINS_KEY, plugins) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun getPluginsOnline(): Array<PluginData> { | ||||
|         return getKey(PLUGINS_KEY) ?: emptyArray() | ||||
|     } | ||||
|  | @ -346,10 +369,14 @@ object PluginManager { | |||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     suspend fun deletePlugin(pluginUrl: String): Boolean { | ||||
|         val data = getPluginsOnline() | ||||
|             .firstOrNull { it.url == pluginUrl } | ||||
|             ?: return false | ||||
|     /** | ||||
|      * @param isFilePath will treat the pluginUrl as as the filepath instead of url | ||||
|      * */ | ||||
|     suspend fun deletePlugin(pluginIdentifier: String, isFilePath: Boolean): Boolean { | ||||
|         val data = | ||||
|             (if (isFilePath) getPluginsLocal().firstOrNull { it.filePath == pluginIdentifier } | ||||
|             else getPluginsOnline().firstOrNull { it.url == pluginIdentifier }) ?: return false | ||||
| 
 | ||||
|         return try { | ||||
|             if (File(data.filePath).delete()) { | ||||
|                 unloadPlugin(data.filePath) | ||||
|  |  | |||
|  | @ -4,10 +4,12 @@ import android.content.Context | |||
| import com.fasterxml.jackson.annotation.JsonProperty | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.getKey | ||||
| import com.lagradost.cloudstream3.AcraApplication.Companion.setKey | ||||
| import com.lagradost.cloudstream3.apmap | ||||
| import com.lagradost.cloudstream3.apmapIndexed | ||||
| import com.lagradost.cloudstream3.app | ||||
| import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall | ||||
| import com.lagradost.cloudstream3.plugins.PluginManager.getPluginSanitizedFileName | ||||
| import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsLocal | ||||
| import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY | ||||
| import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData | ||||
| import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson | ||||
|  | @ -93,9 +95,9 @@ object RepositoryManager { | |||
|      * */ | ||||
|     suspend fun getRepoPlugins(repositoryUrl: String): List<Pair<String, SitePlugin>>? { | ||||
|         val repo = parseRepository(repositoryUrl) ?: return null | ||||
|         return repo.pluginLists.apmapIndexed { index, url -> | ||||
|         return repo.pluginLists.apmap { url -> | ||||
|             parsePlugins(url).map { | ||||
|                 repo.pluginLists[index] to it | ||||
|                 repositoryUrl to it | ||||
|             } | ||||
|         }.flatten() | ||||
|     } | ||||
|  | @ -150,10 +152,13 @@ object RepositoryManager { | |||
|             setKey(REPOSITORIES_KEY, newRepos) | ||||
|         } | ||||
| 
 | ||||
|         File( | ||||
|         val file = File( | ||||
|             extensionsDir, | ||||
|             getPluginSanitizedFileName(repository.url) | ||||
|         ).delete() | ||||
|         ) | ||||
|         PluginManager.deleteRepositoryData(file.absolutePath) | ||||
| 
 | ||||
|         file.delete() | ||||
|     } | ||||
| 
 | ||||
|     private fun write(stream: InputStream, output: OutputStream) { | ||||
|  |  | |||
|  | @ -59,10 +59,12 @@ class ExtensionsFragment : Fragment() { | |||
|         repo_recycler_view?.adapter = RepoAdapter(false, { | ||||
|             findNavController().navigate( | ||||
|                 R.id.navigation_settings_extensions_to_navigation_settings_plugins, | ||||
|                 Bundle().apply { | ||||
|                     putString(PLUGINS_BUNDLE_NAME, it.name) | ||||
|                     putString(PLUGINS_BUNDLE_URL, it.url) | ||||
|                 }) | ||||
|                 PluginsFragment.newInstance( | ||||
|                     it.name, | ||||
|                     it.url, | ||||
|                     false | ||||
|                 ) | ||||
|             ) | ||||
|         }, { repo -> | ||||
|             // Prompt user before deleting repo | ||||
|             main { | ||||
|  | @ -120,6 +122,17 @@ class ExtensionsFragment : Fragment() { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         plugin_storage_appbar?.setOnClickListener { | ||||
|             findNavController().navigate( | ||||
|                 R.id.navigation_settings_extensions_to_navigation_settings_plugins, | ||||
|                 PluginsFragment.newInstance( | ||||
|                     getString(R.string.extensions), | ||||
|                     "", | ||||
|                     true | ||||
|                 ) | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         add_repo_button?.setOnClickListener { | ||||
|             val builder = | ||||
|                 AlertDialog.Builder(context ?: return@setOnClickListener, R.style.AlertDialogCustom) | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ import kotlinx.android.synthetic.main.fragment_plugins.* | |||
| 
 | ||||
| const val PLUGINS_BUNDLE_NAME = "name" | ||||
| const val PLUGINS_BUNDLE_URL = "url" | ||||
| const val PLUGINS_BUNDLE_LOCAL = "isLocal" | ||||
| 
 | ||||
| class PluginsFragment : Fragment() { | ||||
|     override fun onCreateView( | ||||
|  | @ -29,6 +30,7 @@ class PluginsFragment : Fragment() { | |||
| 
 | ||||
|         val name = arguments?.getString(PLUGINS_BUNDLE_NAME) | ||||
|         val url = arguments?.getString(PLUGINS_BUNDLE_URL) | ||||
|         val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true | ||||
| 
 | ||||
|         if (url == null || name == null) { | ||||
|             activity?.onBackPressed() | ||||
|  | @ -49,21 +51,28 @@ class PluginsFragment : Fragment() { | |||
| 
 | ||||
|         plugin_recycler_view?.adapter = | ||||
|             PluginAdapter { | ||||
|                 pluginViewModel.handlePluginAction(activity, url, it) | ||||
|                 pluginViewModel.handlePluginAction(activity, url, it, isLocal) | ||||
|             } | ||||
| 
 | ||||
|         observe(pluginViewModel.plugins) { | ||||
|             (plugin_recycler_view?.adapter as? PluginAdapter?)?.updateList(it) | ||||
|         } | ||||
| 
 | ||||
|         pluginViewModel.updatePluginList(url) | ||||
|         if (isLocal) { | ||||
|             // No download button | ||||
|             settings_toolbar?.menu?.clear() | ||||
|             pluginViewModel.updatePluginListLocal() | ||||
|         } else { | ||||
|             pluginViewModel.updatePluginList(url) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     companion object { | ||||
|         fun newInstance(name: String, url: String): Bundle { | ||||
|         fun newInstance(name: String, url: String, isLocal: Boolean): Bundle { | ||||
|             return Bundle().apply { | ||||
|                 putString(PLUGINS_BUNDLE_NAME, name) | ||||
|                 putString(PLUGINS_BUNDLE_URL, url) | ||||
|                 putBoolean(PLUGINS_BUNDLE_LOCAL, isLocal) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  |  | |||
|  | @ -59,65 +59,76 @@ class PluginsViewModel : ViewModel() { | |||
|         /** | ||||
|          * @param viewModel optional, updates the plugins livedata for that viewModel if included | ||||
|          * */ | ||||
|         fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) = ioSafe { | ||||
|             if (activity == null) return@ioSafe | ||||
|             val stored = getDownloads() | ||||
|             val plugins = getPlugins(repositoryUrl) | ||||
|         fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) = | ||||
|             ioSafe { | ||||
|                 if (activity == null) return@ioSafe | ||||
|                 val stored = getDownloads() | ||||
|                 val plugins = getPlugins(repositoryUrl) | ||||
| 
 | ||||
|             plugins.filter { plugin -> !isDownloaded(plugin, stored) }.also { list -> | ||||
|                 main { | ||||
|                     showToast( | ||||
|                 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, | ||||
|                         if (list.isEmpty()) { | ||||
|                         metadata.url, | ||||
|                         metadata.name, | ||||
|                         repo | ||||
|                     ) | ||||
|                 }.main { list -> | ||||
|                     if (list.any { it }) { | ||||
|                         showToast( | ||||
|                             activity, | ||||
|                             txt( | ||||
|                                 R.string.batch_download_nothing_to_download_format, | ||||
|                                 txt(R.string.plugin) | ||||
|                             ) | ||||
|                         } else { | ||||
|                             txt( | ||||
|                                 R.string.batch_download_start_format, | ||||
|                                 list.size, | ||||
|                                 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 | ||||
|                     ) | ||||
|                 } | ||||
|             }.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 | ||||
|                     ) | ||||
|                     viewModel?.updatePluginListPrivate(repositoryUrl) | ||||
|                 } else if (list.isNotEmpty()) { | ||||
|                     showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT) | ||||
|                             ), | ||||
|                             Toast.LENGTH_SHORT | ||||
|                         ) | ||||
|                         viewModel?.updatePluginListPrivate(repositoryUrl) | ||||
|                     } else if (list.isNotEmpty()) { | ||||
|                         showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     fun handlePluginAction(activity: Activity?, repositoryUrl: String, plugin: Plugin) = ioSafe { | ||||
|     /** | ||||
|      * @param isLocal defines if the plugin data is from local data instead of repo | ||||
|      * Will only allow removal of plugins. Used for the local file management. | ||||
|      * */ | ||||
|     fun handlePluginAction( | ||||
|         activity: Activity?, | ||||
|         repositoryUrl: String, | ||||
|         plugin: Plugin, | ||||
|         isLocal: Boolean | ||||
|     ) = ioSafe { | ||||
|         Log.i(TAG, "handlePluginAction = $repositoryUrl, $plugin") | ||||
| 
 | ||||
|         if (activity == null) return@ioSafe | ||||
|         val (repo, metadata) = plugin | ||||
| 
 | ||||
|         val (success, message) = if (isDownloaded(plugin)) { | ||||
|         val (success, message) = if (isDownloaded(plugin) || isLocal) { | ||||
|             PluginManager.deletePlugin( | ||||
|                 metadata.url, | ||||
|                 isLocal | ||||
|             ) to R.string.plugin_deleted | ||||
|         } else { | ||||
|             PluginManager.downloadAndLoadPlugin( | ||||
|  | @ -136,7 +147,10 @@ class PluginsViewModel : ViewModel() { | |||
|         } | ||||
| 
 | ||||
|         if (success) | ||||
|             updatePluginListPrivate(repositoryUrl) | ||||
|             if (isLocal) | ||||
|                 updatePluginListLocal() | ||||
|             else | ||||
|                 updatePluginListPrivate(repositoryUrl) | ||||
|     } | ||||
| 
 | ||||
|     private suspend fun updatePluginListPrivate(repositoryUrl: String) { | ||||
|  | @ -153,4 +167,20 @@ class PluginsViewModel : ViewModel() { | |||
|         Log.i(TAG, "updatePluginList = $repositoryUrl") | ||||
|         updatePluginListPrivate(repositoryUrl) | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     /** | ||||
|      * Update the list but only with the local data. Used for file management. | ||||
|      * */ | ||||
|     fun updatePluginListLocal() = viewModelScope.launch { | ||||
|         Log.i(TAG, "updatePluginList = local") | ||||
| 
 | ||||
|         val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal()) | ||||
|             .distinctBy { it.filePath } | ||||
|             .map { | ||||
|                 PluginViewData("" to it.toSitePlugin(), true) | ||||
|             } | ||||
| 
 | ||||
|         _plugins.postValue(downloadedPlugins) | ||||
|     } | ||||
| } | ||||
|  | @ -177,6 +177,10 @@ | |||
|                 android:name="url" | ||||
|                 android:defaultValue="@null" | ||||
|                 app:argType="string" /> | ||||
|             <argument | ||||
|                 android:name="isLocal" | ||||
|                 android:defaultValue="false" | ||||
|                 app:argType="boolean" /> | ||||
|         </action> | ||||
|     </fragment> | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue