forked from recloudstream/cloudstream
add safe mode that disabled extensions on crash
This commit is contained in:
parent
461f3d75d8
commit
93b176e3cd
4 changed files with 85 additions and 13 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
@ -456,6 +459,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
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,6 +492,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
|
||||
changeStatusBarState(isEmulatorSettings())
|
||||
|
||||
if (lastError == null) {
|
||||
ioSafe {
|
||||
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi ->
|
||||
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
|
||||
|
@ -501,6 +512,23 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
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()
|
||||
}
|
||||
|
||||
setNegativeButton("Ok") { _, _ -> }
|
||||
}
|
||||
builder.show()
|
||||
}
|
||||
|
||||
|
||||
// ioSafe {
|
||||
// val plugins =
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in a new issue