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.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.google.auto.service.AutoService
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
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.setKey
import kotlinx.coroutines.runBlocking
import org.acra.ACRA
import org.acra.ReportField
import org.acra.config.CoreConfiguration
import org.acra.data.CrashReportData
@ -24,8 +27,14 @@ import org.acra.data.StringFormat
import org.acra.ktx.initAcra
import org.acra.sender.ReportSender
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 kotlin.concurrent.thread
import kotlin.system.exitProcess
class CustomReportSender : ReportSender {
// 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() {
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?) {
super.attachBaseContext(base)
context = base

View File

@ -10,7 +10,9 @@ import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.view.WindowManager
import android.widget.Toast
import androidx.annotation.IdRes
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.navigation.NavController
@ -88,6 +90,7 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.io.File
import java.net.URI
import java.nio.charset.Charset
import kotlin.reflect.KClass
@ -455,6 +458,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onCreate(savedInstanceState: Bundle?) {
app.initClient(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()
settingsForProvider.enableAdult = settingsManager.getBoolean(getString(R.string.enable_nsfw_on_providers_key), false)
@ -482,26 +492,44 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
changeStatusBarState(isEmulatorSettings())
ioSafe {
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
} ?: run {
mainPluginsLoadedEvent.invoke(false)
}
if (lastError == null) {
ioSafe {
if (settingsManager.getBoolean(getString(R.string.auto_update_plugins_key), true)) {
PluginManager.updateAllOnlinePluginsAndLoadThem(this@MainActivity)
} else {
PluginManager.loadAllOnlinePlugins(this@MainActivity)
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
} ?: run {
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 {
PluginManager.loadAllLocalPlugins(this@MainActivity)
setNegativeButton("Ok") { _, _ -> }
}
builder.show()
}
// ioSafe {
// val plugins =
// RepositoryParser.getRepoPlugins("https://raw.githubusercontent.com/recloudstream/TestPlugin/master/repo.json")

View File

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

View File

@ -613,4 +613,8 @@
<string name="audio_tracks">Audio tracks</string>
<string name="video_tracks">Video tracks</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>