forked from recloudstream/cloudstream
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…
Reference in a new issue