Files
mihon-bookoasis/app/src/main/java/eu/kanade/tachiyomi/util/CrashLogUtil.kt
T

120 lines
4.9 KiB
Kotlin

package eu.kanade.tachiyomi.util
import android.content.Context
import android.os.Build
import dev.zacsweers.metro.Inject
import eu.kanade.domain.base.BasePreferences
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.network.NetworkPreferences
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.WebViewUtil
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.datetime.TimeZone
import kotlinx.datetime.offsetAt
import kotlinx.datetime.toLocalDateTime
import tachiyomi.core.common.util.lang.withNonCancellableContext
import tachiyomi.core.common.util.lang.withUIContext
import kotlin.time.Clock
@Inject
class CrashLogUtil(
private val context: Context,
private val extensionManager: ExtensionManager,
private val preferences: BasePreferences,
private val networkPreferences: NetworkPreferences,
) {
suspend fun dumpLogs(exception: Throwable? = null) = withNonCancellableContext {
try {
val file = context.createFileInCacheDir("mihon_crash_logs.txt")
file.appendText(getDebugInfo() + "\n\n")
getExtensionsInfo()?.let { file.appendText("$it\n\n") }
exception?.let { file.appendText("$it\n\n") }
val logPriority = if (networkPreferences.verboseLogging.get()) "V" else "E"
Runtime.getRuntime().exec("logcat *:$logPriority -d -v year -v zone -f ${file.absolutePath}").waitFor()
val uri = file.getUriCompat(context)
context.startActivity(uri.toShareIntent(context, "text/plain"))
} catch (_: Throwable) {
withUIContext { context.toast("Failed to get logs") }
}
}
fun getDebugInfo(): String {
val now = Clock.System.now()
val tz = TimeZone.currentSystemDefault()
return """
App ID: ${BuildConfig.APPLICATION_ID}
App version: ${BuildConfig.VERSION_NAME} (${BuildConfig.COMMIT_SHA}, ${BuildConfig.VERSION_CODE}, ${BuildConfig.BUILD_TIME})
Installation ID: ${preferences.installationId.get()}
Android version: ${Build.VERSION.RELEASE} (SDK ${Build.VERSION.SDK_INT}; build ${Build.DISPLAY})
Device brand: ${Build.BRAND}
Device manufacturer: ${Build.MANUFACTURER}
Device name: ${Build.DEVICE} (${Build.PRODUCT})
Device model: ${Build.MODEL}
WebView: ${WebViewUtil.getVersion(context)}
Current time: ${now.toLocalDateTime(tz)}${tz.offsetAt(now)}
""".trimIndent()
}
private suspend fun getExtensionsInfo(): String? {
val availableExtensions = extensionManager.availableExtensionsFlow.value.associateBy { it.pkgName }
val outdatedInfoList = extensionManager.getLoadedExtensions()
.sortedBy { it.name }
.mapNotNull {
val availableExtension = availableExtensions[it.pkgName]
val hasUpdate = (availableExtension?.versionCode ?: 0) > it.versionCode
if (!hasUpdate && !it.isObsolete) return@mapNotNull null
"""
- ${it.name}
Installed: ${it.versionName} / Available: ${availableExtension?.versionName ?: "?"}
Orphaned: ${it.isObsolete}
""".trimIndent()
}
val notLoadedInfoList = extensionManager.getNotLoadedExtensions()
.sortedBy { it.name }
.map { extension ->
buildString {
appendLine("- ${extension.name}")
appendLine(" Installed: ${extension.versionName} (lib ${extension.libVersion ?: "?"})")
append(" Not loaded: ${extension.reason.description}")
val reason = extension.reason
if (reason is Extension.NotLoaded.Reason.Failed) {
appendLine()
append(reason.stackTrace.trimEnd().prependIndent(" "))
}
}
}
val extensionInfoList = outdatedInfoList + notLoadedInfoList
return if (extensionInfoList.isNotEmpty()) {
(listOf("Problematic extensions:") + extensionInfoList)
.joinToString("\n")
} else {
null
}
}
}
private val Extension.NotLoaded.Reason.description: String
get() = when (this) {
is Extension.NotLoaded.Reason.Untrusted -> "Untrusted"
Extension.NotLoaded.Reason.Filtered -> "Filtered by content warning"
Extension.NotLoaded.Reason.Unsigned -> "Unsigned"
Extension.NotLoaded.Reason.UnsupportedLibVersion -> "Unsupported lib version"
Extension.NotLoaded.Reason.Malformed -> "Malformed"
is Extension.NotLoaded.Reason.Failed -> "Failed ($message)"
}