add safe mode that disabled extensions on crash

This commit is contained in:
Cloudburst 2022-09-01 14:13:54 +02:00
parent 461f3d75d8
commit 93b176e3cd
4 changed files with 85 additions and 13 deletions

View file

@ -4,11 +4,13 @@ import android.app.Activity
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context
import android.content.ContextWrapper import android.content.ContextWrapper
import android.content.Intent
import android.widget.Toast import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.auto.service.AutoService import com.google.auto.service.AutoService
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
@ -17,6 +19,7 @@ import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.removeKeys
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.acra.ACRA
import org.acra.ReportField import org.acra.ReportField
import org.acra.config.CoreConfiguration import org.acra.config.CoreConfiguration
import org.acra.data.CrashReportData import org.acra.data.CrashReportData
@ -24,8 +27,14 @@ import org.acra.data.StringFormat
import org.acra.ktx.initAcra import org.acra.ktx.initAcra
import org.acra.sender.ReportSender import org.acra.sender.ReportSender
import org.acra.sender.ReportSenderFactory import org.acra.sender.ReportSenderFactory
import java.io.File
import java.io.FileNotFoundException
import java.io.PrintStream
import java.lang.Exception
import java.lang.ref.WeakReference import java.lang.ref.WeakReference
import kotlin.concurrent.thread import kotlin.concurrent.thread
import kotlin.system.exitProcess
class CustomReportSender : ReportSender { class CustomReportSender : ReportSender {
// Sends all your crashes to google forms // Sends all your crashes to google forms
@ -65,7 +74,33 @@ class CustomSenderFactory : ReportSenderFactory {
} }
} }
class ExceptionHandler(val errorFile: File, val onError: (() -> Unit)): Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, error: Throwable) {
ACRA.errorReporter.handleException(error)
try {
PrintStream(errorFile).use { ps ->
ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
ps.println(String.format("Fatal exception on thread %s (%d)", thread.name, thread.id))
error.printStackTrace(ps)
}
} catch (ignored: FileNotFoundException) { }
try {
onError.invoke()
} catch (ignored: Exception) { }
exitProcess(1)
}
}
class AcraApplication : Application() { class AcraApplication : Application() {
override fun onCreate() {
super.onCreate()
Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler(filesDir.resolve("last_error")){
val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName)
startActivity(Intent.makeRestartActivityTask(intent!!.component))
})
}
override fun attachBaseContext(base: Context?) { override fun attachBaseContext(base: Context?) {
super.attachBaseContext(base) super.attachBaseContext(base)
context = base context = base

View file

@ -10,7 +10,9 @@ import android.view.KeyEvent
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.navigation.NavController import androidx.navigation.NavController
@ -88,6 +90,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.nio.charset.Charset
import kotlin.reflect.KClass import kotlin.reflect.KClass
@ -456,6 +459,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
app.initClient(this) app.initClient(this)
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
val errorFile = filesDir.resolve("last_error")
var lastError: String? = null
if (errorFile.exists() && errorFile.isFile) {
lastError = errorFile.readText(Charset.defaultCharset())
errorFile.delete()
}
val settingsForProvider = SettingsJson() val settingsForProvider = SettingsJson()
settingsForProvider.enableAdult = settingsManager.getBoolean(getString(R.string.enable_nsfw_on_providers_key), false) settingsForProvider.enableAdult = settingsManager.getBoolean(getString(R.string.enable_nsfw_on_providers_key), false)
@ -482,26 +492,44 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
changeStatusBarState(isEmulatorSettings()) changeStatusBarState(isEmulatorSettings())
ioSafe { if (lastError == null) {
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
} ?: run {
mainPluginsLoadedEvent.invoke(false)
}
ioSafe { ioSafe {
if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) { getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity) mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
} else { } ?: run {
PluginManager.loadAllOnlinePlugins(this@MainActivity) mainPluginsLoadedEvent.invoke(false)
}
ioSafe {
if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) {
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
} else {
PluginManager.loadAllOnlinePlugins(this@MainActivity)
}
}
ioSafe {
PluginManager.loadAllLocalPlugins(this@MainActivity)
} }
} }
} else {
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
builder.setTitle(R.string.safe_mode_title)
builder.setMessage(R.string.safe_mode_description)
builder.apply {
setPositiveButton(R.string.safe_mode_crash_info) { _, _ ->
val tbBuilder: AlertDialog.Builder = AlertDialog.Builder(context)
tbBuilder.setTitle(R.string.safe_mode_title)
tbBuilder.setMessage(lastError)
tbBuilder.show()
}
ioSafe { setNegativeButton("Ok") { _, _ -> }
PluginManager.loadAllLocalPlugins(this@MainActivity)
} }
builder.show()
} }
// ioSafe { // ioSafe {
// val plugins = // val plugins =
// RepositoryParser.getRepoPlugins("https://raw.githubusercontent.com/recloudstream/TestPlugin/master/repo.json") // RepositoryParser.getRepoPlugins("https://raw.githubusercontent.com/recloudstream/TestPlugin/master/repo.json")

View file

@ -127,6 +127,8 @@ object PluginManager {
private val LOCAL_PLUGINS_PATH = private val LOCAL_PLUGINS_PATH =
Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins" Environment.getExternalStorageDirectory().absolutePath + "/Cloudstream3/plugins"
public var currentlyLoading: String? = null
// Maps filepath to plugin // Maps filepath to plugin
val plugins: MutableMap<String, Plugin> = val plugins: MutableMap<String, Plugin> =
LinkedHashMap<String, Plugin>() LinkedHashMap<String, Plugin>()
@ -283,6 +285,7 @@ object PluginManager {
private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean { private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean {
val fileName = file.nameWithoutExtension val fileName = file.nameWithoutExtension
val filePath = file.absolutePath val filePath = file.absolutePath
currentlyLoading = fileName
Log.i(TAG, "Loading plugin: $data") Log.i(TAG, "Loading plugin: $data")
return try { return try {
@ -341,6 +344,7 @@ object PluginManager {
} }
pluginInstance.load(activity) pluginInstance.load(activity)
Log.i(TAG, "Loaded plugin ${data.internalName} successfully") Log.i(TAG, "Loaded plugin ${data.internalName} successfully")
currentlyLoading = null
true true
} catch (e: Throwable) { } catch (e: Throwable) {
Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}") Log.e(TAG, "Failed to load $file: ${Log.getStackTraceString(e)}")
@ -349,6 +353,7 @@ object PluginManager {
activity.getString(R.string.plugin_load_fail).format(fileName), activity.getString(R.string.plugin_load_fail).format(fileName),
Toast.LENGTH_LONG Toast.LENGTH_LONG
) )
currentlyLoading = null
false false
} }
} }

View file

@ -613,4 +613,8 @@
<string name="audio_tracks">Audio tracks</string> <string name="audio_tracks">Audio tracks</string>
<string name="video_tracks">Video tracks</string> <string name="video_tracks">Video tracks</string>
<string name="apply_on_restart">Apply on Restart</string> <string name="apply_on_restart">Apply on Restart</string>
<string name="safe_mode_title">Safe Mode enabled</string>
<string name="safe_mode_description">An unrecoverable crash occurred and we\'ve automatically disabled all extensions, so you can find and remove the extension which is causing trouble.</string>
<string name="safe_mode_crash_info">View crash info</string>
</resources> </resources>