add plugin unloading

This commit is contained in:
C10udburst 2022-08-09 10:33:16 +02:00
parent 08d7f3444f
commit bceeec849d
4 changed files with 70 additions and 4 deletions

View File

@ -342,6 +342,9 @@ abstract class MainAPI {
/**Used for testing and can be used to disable the providers if WebView is not available*/ /**Used for testing and can be used to disable the providers if WebView is not available*/
open val usesWebView = false open val usesWebView = false
/** Determines which plugin a given provider is from */
var sourcePlugin: String? = null
open val hasMainPage = false open val hasMainPage = false
open val hasQuickSearch = false open val hasQuickSearch = false

View File

@ -3,6 +3,13 @@ package com.lagradost.cloudstream3.plugins
import android.content.Context import android.content.Context
import android.content.res.Resources import android.content.res.Resources
import kotlin.Throws import kotlin.Throws
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
import android.util.Log
const val PLUGIN_TAG = "PluginInstance"
abstract class Plugin { abstract class Plugin {
/** /**
@ -13,6 +20,33 @@ abstract class Plugin {
open fun load(context: Context) { open fun load(context: Context) {
} }
/**
* Called when your Plugin is being unloaded
*/
@Throws(Throwable::class)
open fun beforeUnload() {
}
/**
* Used to register providers instances of MainAPI
* @param element MainAPI provider you want to register
*/
fun registerMainAPI(element: MainAPI) {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
element.sourcePlugin = this.`__filename`
APIHolder.allProviders.add(element)
}
/**
* Used to register extractor instances of ExtractorApi
* @param element ExtractorApi provider you want to register
*/
fun registerExtractorAPI(element: ExtractorApi) {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) ExtractorApi")
element.sourcePlugin = this.`__filename`
extractorApis.add(element)
}
class Manifest { class Manifest {
var name: String? = null var name: String? = null
var pluginClassName: String? = null var pluginClassName: String? = null

View File

@ -23,6 +23,10 @@ import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins
import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY
import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData
import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename import com.lagradost.cloudstream3.utils.VideoDownloadManager.sanitizeFilename
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.utils.ExtractorApi
import com.lagradost.cloudstream3.utils.extractorApis
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.io.File import java.io.File
@ -99,8 +103,7 @@ object PluginManager {
private val classLoaders: MutableMap<PathClassLoader, Plugin> = private val classLoaders: MutableMap<PathClassLoader, Plugin> =
HashMap<PathClassLoader, Plugin>() HashMap<PathClassLoader, Plugin>()
private val failedToLoad: MutableMap<File, Any> = LinkedHashMap()
private var loadedLocalPlugins = false private var loadedLocalPlugins = false
private val gson = Gson() private val gson = Gson()
@ -112,6 +115,8 @@ object PluginManager {
file, file,
PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET) PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET)
) )
} else {
Log.i(TAG, "Skipping invalid plugin file: $file")
} }
} }
@ -197,6 +202,8 @@ object PluginManager {
val sortedPlugins = dir.listFiles() val sortedPlugins = dir.listFiles()
// Always sort plugins alphabetically for reproducible results // Always sort plugins alphabetically for reproducible results
Log.d(TAG, "Files in '${LOCAL_PLUGINS_PATH}' folder: ${sortedPlugins}")
sortedPlugins?.sortedBy { it.name }?.apmap { file -> sortedPlugins?.sortedBy { it.name }?.apmap { file ->
maybeLoadPlugin(activity, file) maybeLoadPlugin(activity, file)
} }
@ -217,7 +224,6 @@ object PluginManager {
var manifest: Plugin.Manifest var manifest: Plugin.Manifest
loader.getResourceAsStream("manifest.json").use { stream -> loader.getResourceAsStream("manifest.json").use { stream ->
if (stream == null) { if (stream == null) {
failedToLoad[file] = "No manifest found"
Log.e(TAG, "Failed to load plugin $fileName: No manifest found") Log.e(TAG, "Failed to load plugin $fileName: No manifest found")
return false return false
} }
@ -263,7 +269,6 @@ object PluginManager {
Log.i(TAG, "Loaded plugin ${data.internalName} successfully") Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
true true
} catch (e: Throwable) { } catch (e: Throwable) {
failedToLoad[file] = e
Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}")
showToast( showToast(
activity, activity,
@ -274,6 +279,26 @@ object PluginManager {
} }
} }
private suspend fun unloadPlugin(absolutePath: String) {
var plugin = plugins.get(absolutePath)
if (plugin == null) {
Log.w(TAG, "Couldn't find plugin $absolutePath")
return
}
try {
plugin.beforeUnload()
} catch (e: Throwable) {
Log.e(TAG, "Failed to run beforeUnload $absolutePath: ${Log.getStackTraceString(e)}")
}
// remove all registered apis
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.`__filename` }
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.`__filename` }
plugins.remove(absolutePath)
}
/** /**
* Spits out a unique and safe filename based on name. * Spits out a unique and safe filename based on name.
* Used for repo folders (using repo url) and plugin file names (using internalName) * Used for repo folders (using repo url) and plugin file names (using internalName)
@ -309,6 +334,7 @@ object PluginManager {
?: return false ?: return false
return try { return try {
if (File(data.filePath).delete()) { if (File(data.filePath).delete()) {
unloadPlugin(data.filePath)
deletePluginData(data) deletePluginData(data)
return true return true
} }

View File

@ -247,6 +247,9 @@ abstract class ExtractorApi {
abstract val mainUrl: String abstract val mainUrl: String
abstract val requiresReferer: Boolean abstract val requiresReferer: Boolean
/** Determines which plugin a given extractor is from */
var sourcePlugin: String? = null
//suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? { //suspend fun getSafeUrl(url: String, referer: String? = null): List<ExtractorLink>? {
// return suspendSafeApiCall { getUrl(url, referer) } // return suspendSafeApiCall { getUrl(url, referer) }
//} //}