Fix internal plugin updater

This commit is contained in:
Blatzar 2022-09-23 14:20:53 +02:00
parent 7a640b58cb
commit c4295f55ae
3 changed files with 69 additions and 20 deletions

View file

@ -15,6 +15,7 @@ import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!" const val DEBUG_EXCEPTION = "THIS IS A DEBUG EXCEPTION!"
const val DEBUG_PRINT = "DEBUG PRINT"
class DebugException(message: String) : Exception("$DEBUG_EXCEPTION\n$message") class DebugException(message: String) : Exception("$DEBUG_EXCEPTION\n$message")
@ -24,6 +25,12 @@ inline fun debugException(message: () -> String) {
} }
} }
inline fun debugPrint(tag: String = DEBUG_PRINT, message: () -> String) {
if (BuildConfig.DEBUG) {
Log.d(tag, message.invoke())
}
}
inline fun debugWarning(message: () -> String) { inline fun debugWarning(message: () -> String) {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
logError(DebugException(message.invoke())) logError(DebugException(message.invoke()))

View file

@ -8,12 +8,10 @@ import android.content.res.Resources
import android.os.Environment import android.os.Environment
import android.widget.Toast import android.widget.Toast
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
import androidx.core.net.toUri
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
@ -28,6 +26,7 @@ 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.removePluginMapping import com.lagradost.cloudstream3.APIHolder.removePluginMapping
import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent
import com.lagradost.cloudstream3.mvvm.debugPrint
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -37,7 +36,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.extractorApis 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 org.acra.log.debug
import java.io.File import java.io.File
import java.io.InputStreamReader import java.io.InputStreamReader
import java.util.* import java.util.*
@ -223,13 +221,14 @@ object PluginManager {
// Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated // Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated
val outdatedPlugins = getPluginsOnline().map { savedData -> val outdatedPlugins = getPluginsOnline().map { savedData ->
onlinePlugins.filter { onlineData -> savedData.internalName == onlineData.second.internalName } onlinePlugins
.filter { onlineData -> savedData.internalName == onlineData.second.internalName }
.map { onlineData -> .map { onlineData ->
OnlinePluginData(savedData, onlineData) OnlinePluginData(savedData, onlineData)
} }
}.flatten().distinctBy { it.onlineData.second.url } }.flatten().distinctBy { it.onlineData.second.url }
debug { debugPrint {
"Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}" "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}"
} }
@ -244,7 +243,7 @@ object PluginManager {
activity, activity,
pluginData.onlineData.second.url, pluginData.onlineData.second.url,
pluginData.savedData.internalName, pluginData.savedData.internalName,
pluginData.onlineData.first File(pluginData.savedData.filePath)
).let { success -> ).let { success ->
if (success) if (success)
updatedPlugins.add(pluginData.onlineData.second.name) updatedPlugins.add(pluginData.onlineData.second.name)
@ -417,24 +416,45 @@ object PluginManager {
) + "." + name.hashCode() ) + "." + name.hashCode()
} }
/**
* Used for fresh installs
* */
suspend fun downloadAndLoadPlugin( suspend fun downloadAndLoadPlugin(
activity: Activity, activity: Activity,
pluginUrl: String, pluginUrl: String,
internalName: String, internalName: String,
repositoryUrl: String repositoryUrl: String
): Boolean { ): Boolean {
try { val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique
val folderName = getPluginSanitizedFileName(repositoryUrl) // Guaranteed unique val fileName = getPluginSanitizedFileName(internalName)
val fileName = getPluginSanitizedFileName(internalName) val file = File("${activity.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3")
unloadPlugin("${activity.filesDir}/${ONLINE_PLUGINS_FOLDER}/${folderName}/$fileName.cs3") downloadAndLoadPlugin(activity, pluginUrl, internalName, file)
return true
}
Log.d(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") /**
* Used for updates.
*
* Uses a file instead of repository url, as extensions can get moved it is better to directly
* update the files instead of getting the filepath from repo url.
* */
private suspend fun downloadAndLoadPlugin(
activity: Activity,
pluginUrl: String,
internalName: String,
file: File,
): Boolean {
try {
unloadPlugin(file.absolutePath)
Log.d(TAG, "Downloading plugin: $pluginUrl to ${file.absolutePath}")
// The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names // The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names
val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName) val newFile = downloadPluginToFile(pluginUrl, file)
return loadPlugin( return loadPlugin(
activity, activity,
file ?: return false, newFile ?: return false,
PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET) PluginData(internalName, pluginUrl, true, newFile.absolutePath, PLUGIN_VERSION_NOT_SET)
) )
} catch (e: Exception) { } catch (e: Exception) {
logError(e) logError(e)
@ -468,7 +488,8 @@ object PluginManager {
// the NotificationChannel class is new and not in the support library // the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = EXTENSIONS_CHANNEL_NAME //getString(R.string.channel_name) val name = EXTENSIONS_CHANNEL_NAME //getString(R.string.channel_name)
val descriptionText = EXTENSIONS_CHANNEL_DESCRIPT//getString(R.string.channel_description) val descriptionText =
EXTENSIONS_CHANNEL_DESCRIPT//getString(R.string.channel_description)
val importance = NotificationManager.IMPORTANCE_LOW val importance = NotificationManager.IMPORTANCE_LOW
val channel = NotificationChannel(EXTENSIONS_CHANNEL_ID, name, importance).apply { val channel = NotificationChannel(EXTENSIONS_CHANNEL_ID, name, importance).apply {
description = descriptionText description = descriptionText
@ -479,10 +500,11 @@ object PluginManager {
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
} }
private fun createNotification( private fun createNotification(
context: Context, context: Context,
extensionNames: List<String> extensionNames: List<String>
): Notification? { ): Notification? {
try { try {
if (extensionNames.isEmpty()) return null if (extensionNames.isEmpty()) return null
@ -497,8 +519,10 @@ object PluginManager {
.setColor(context.colorFromAttribute(R.attr.colorPrimary)) .setColor(context.colorFromAttribute(R.attr.colorPrimary))
.setContentTitle(context.getString(R.string.plugins_updated, extensionNames.size)) .setContentTitle(context.getString(R.string.plugins_updated, extensionNames.size))
.setSmallIcon(R.drawable.ic_baseline_extension_24) .setSmallIcon(R.drawable.ic_baseline_extension_24)
.setStyle(NotificationCompat.BigTextStyle() .setStyle(
.bigText(content)) NotificationCompat.BigTextStyle()
.bigText(content)
)
.setContentText(content) .setContentText(content)
if (!hasCreatedNotChanel) { if (!hasCreatedNotChanel) {
@ -508,7 +532,7 @@ object PluginManager {
val notification = builder.build() val notification = builder.build()
with(NotificationManagerCompat.from(context)) { with(NotificationManagerCompat.from(context)) {
// notificationId is a unique int for each notification that you must define // notificationId is a unique int for each notification that you must define
notify((System.currentTimeMillis()/1000).toInt(), notification) notify((System.currentTimeMillis() / 1000).toInt(), notification)
} }
return notification return notification
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -84,7 +84,7 @@ object RepositoryManager {
// Normal parsed function not working? // Normal parsed function not working?
// return response.parsedSafe() // return response.parsedSafe()
tryParseJson<Array<SitePlugin>>(response.text)?.toList() ?: emptyList() tryParseJson<Array<SitePlugin>>(response.text)?.toList() ?: emptyList()
} catch (t : Throwable) { } catch (t: Throwable) {
logError(t) logError(t)
emptyList() emptyList()
} }
@ -102,9 +102,27 @@ object RepositoryManager {
}.flatten() }.flatten()
} }
suspend fun downloadPluginToFile(
pluginUrl: String,
file: File
): File? {
return suspendSafeApiCall {
file.mkdirs()
// Overwrite if exists
if (file.exists()) { file.delete() }
file.createNewFile()
val body = app.get(pluginUrl).okhttpResponse.body
write(body.byteStream(), file.outputStream())
file
}
}
suspend fun downloadPluginToFile( suspend fun downloadPluginToFile(
context: Context, context: Context,
pluginUrl: String, pluginUrl: String,
/** Filename without .cs3 */
fileName: String, fileName: String,
folder: String folder: String
): File? { ): File? {