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.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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue