forked from eden-emu/eden
style: remove unused DriverResolver and refactor driver handling in CustomSettingsHandler
- Deleted `DriverResolver` - Moved and streamlined driver path extraction logic into `CustomSettingsHandler`. - Improved string resource usage and ensured consistent formatting across dialogs.
This commit is contained in:
parent
3e3e35f558
commit
e99b129cc2
3 changed files with 122 additions and 409 deletions
|
@ -34,6 +34,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
|
|||
import org.yuzu.yuzu_emu.adapters.HomeSettingAdapter
|
||||
import org.yuzu.yuzu_emu.databinding.FragmentHomeSettingsBinding
|
||||
import org.yuzu.yuzu_emu.features.DocumentProvider
|
||||
import org.yuzu.yuzu_emu.features.fetcher.SpacingItemDecoration
|
||||
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.HomeSetting
|
||||
|
@ -251,6 +252,8 @@ class HomeSettingsFragment : Fragment() {
|
|||
viewLifecycleOwner,
|
||||
optionsList
|
||||
)
|
||||
val spacing = resources.getDimensionPixelSize(R.dimen.spacing_small)
|
||||
addItemDecoration(SpacingItemDecoration(spacing))
|
||||
}
|
||||
|
||||
setInsets()
|
||||
|
|
|
@ -10,11 +10,14 @@ import android.content.Context
|
|||
import android.widget.Toast
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import org.yuzu.yuzu_emu.model.Game
|
||||
import java.io.File
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
import android.net.Uri
|
||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||
|
||||
object CustomSettingsHandler {
|
||||
const val CUSTOM_CONFIG_ACTION = "dev.eden.eden_emulator.LAUNCH_WITH_CUSTOM_CONFIG"
|
||||
|
@ -39,21 +42,21 @@ object CustomSettingsHandler {
|
|||
}
|
||||
|
||||
// Check if config already exists - this should be handled by the caller
|
||||
val configFile = getConfigFile(titleId)
|
||||
val configFile = getConfigFile(game)
|
||||
if (configFile.exists()) {
|
||||
Log.warning("[CustomSettingsHandler] Config file already exists for title ID: $titleId")
|
||||
// The caller should have already asked the user about overwriting
|
||||
Log.warning("[CustomSettingsHandler] Config file already exists for game: ${game.title}")
|
||||
}
|
||||
|
||||
// Write the config file
|
||||
if (!writeConfigFile(titleId, customSettings)) {
|
||||
if (!writeConfigFile(game, customSettings)) {
|
||||
Log.error("[CustomSettingsHandler] Failed to write config file")
|
||||
return null
|
||||
}
|
||||
|
||||
// Initialize per-game config
|
||||
try {
|
||||
NativeConfig.initializePerGameConfig(game.programId, configFile.nameWithoutExtension)
|
||||
val fileName = FileUtil.getFilename(Uri.parse(game.path))
|
||||
NativeConfig.initializePerGameConfig(game.programId, fileName)
|
||||
Log.info("[CustomSettingsHandler] Successfully applied custom settings")
|
||||
return game
|
||||
} catch (e: Exception) {
|
||||
|
@ -88,50 +91,104 @@ object CustomSettingsHandler {
|
|||
}
|
||||
|
||||
// Check if config already exists
|
||||
val configFile = getConfigFile(titleId)
|
||||
val configFile = getConfigFile(game)
|
||||
if (configFile.exists() && activity != null) {
|
||||
Log.info("[CustomSettingsHandler] Config file already exists, asking user for confirmation")
|
||||
Toast.makeText(activity, "Config exists, asking to overwrite", Toast.LENGTH_SHORT).show()
|
||||
Log.info(
|
||||
"[CustomSettingsHandler] Config file already exists, asking user for confirmation"
|
||||
)
|
||||
Toast.makeText(
|
||||
activity,
|
||||
activity.getString(R.string.config_exists_prompt),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
val shouldOverwrite = askUserToOverwriteConfig(activity, game.title)
|
||||
if (!shouldOverwrite) {
|
||||
Log.info("[CustomSettingsHandler] User chose not to overwrite existing config")
|
||||
Toast.makeText(activity, "Overwrite cancelled", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(
|
||||
activity,
|
||||
activity.getString(R.string.overwrite_cancelled),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
// Check for driver requirements if activity and driverViewModel are provided
|
||||
if (activity != null && driverViewModel != null) {
|
||||
val driverPath = DriverResolver.extractDriverPath(customSettings)
|
||||
val driverPath = extractDriverPath(customSettings)
|
||||
if (driverPath != null) {
|
||||
Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath")
|
||||
Toast.makeText(activity, "Checking driver: ${driverPath.split("/").lastOrNull()?.take(20) ?: "driver"}", Toast.LENGTH_SHORT).show()
|
||||
val driverExists = DriverResolver.ensureDriverExists(driverPath, activity, driverViewModel)
|
||||
if (!driverExists) {
|
||||
Log.error("[CustomSettingsHandler] Required driver not available: $driverPath")
|
||||
Toast.makeText(activity, "Driver unavailable", Toast.LENGTH_SHORT).show()
|
||||
// Don't write config if driver installation failed
|
||||
// Check if driver exists in the driver storage
|
||||
val driverFile = File(driverPath)
|
||||
if (!driverFile.exists()) {
|
||||
Log.error("[CustomSettingsHandler] Required driver not found: $driverPath")
|
||||
Toast.makeText(
|
||||
activity,
|
||||
activity.getString(
|
||||
R.string.custom_settings_failed_message,
|
||||
game.title,
|
||||
activity.getString(R.string.driver_not_found, driverFile.name)
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
// Don't write config if driver is missing
|
||||
return null
|
||||
}
|
||||
|
||||
// Verify it's a valid driver
|
||||
val metadata = GpuDriverHelper.getMetadataFromZip(driverFile)
|
||||
if (metadata.name == null) {
|
||||
Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath")
|
||||
Toast.makeText(
|
||||
activity,
|
||||
activity.getString(
|
||||
R.string.custom_settings_failed_message,
|
||||
game.title,
|
||||
activity.getString(R.string.invalid_driver_file, driverFile.name)
|
||||
),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
return null
|
||||
}
|
||||
|
||||
Log.info("[CustomSettingsHandler] Driver verified: ${metadata.name}")
|
||||
}
|
||||
}
|
||||
|
||||
// Only write the config file after all checks pass
|
||||
if (!writeConfigFile(titleId, customSettings)) {
|
||||
if (!writeConfigFile(game, customSettings)) {
|
||||
Log.error("[CustomSettingsHandler] Failed to write config file")
|
||||
Toast.makeText(activity, "Config write failed", Toast.LENGTH_SHORT).show()
|
||||
activity?.let {
|
||||
Toast.makeText(
|
||||
it,
|
||||
it.getString(R.string.config_write_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// Initialize per-game config
|
||||
try {
|
||||
NativeConfig.initializePerGameConfig(game.programId, configFile.nameWithoutExtension)
|
||||
SettingsFile.loadCustomConfig(game)
|
||||
Log.info("[CustomSettingsHandler] Successfully applied custom settings")
|
||||
Toast.makeText(activity, "Custom settings applied", Toast.LENGTH_SHORT).show()
|
||||
activity?.let {
|
||||
Toast.makeText(
|
||||
it,
|
||||
it.getString(R.string.custom_settings_applied),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return game
|
||||
} catch (e: Exception) {
|
||||
Log.error("[CustomSettingsHandler] Failed to apply custom settings: ${e.message}")
|
||||
Toast.makeText(activity, "Config apply failed", Toast.LENGTH_SHORT).show()
|
||||
activity?.let {
|
||||
Toast.makeText(
|
||||
it,
|
||||
it.getString(R.string.config_apply_failed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +211,7 @@ object CustomSettingsHandler {
|
|||
// First check cached games for fast lookup
|
||||
GameHelper.cachedGameList.find { game ->
|
||||
game.programId == programIdDecimal ||
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
}?.let { foundGame ->
|
||||
Log.info("[CustomSettingsHandler] Found game in cache: ${foundGame.title}")
|
||||
return foundGame
|
||||
|
@ -164,22 +221,27 @@ object CustomSettingsHandler {
|
|||
val allGames = GameHelper.getGames()
|
||||
val foundGame = allGames.find { game ->
|
||||
game.programId == programIdDecimal ||
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
game.programIdHex.equals(expectedHex, ignoreCase = true)
|
||||
}
|
||||
if (foundGame != null) {
|
||||
Log.info("[CustomSettingsHandler] Found game: ${foundGame.title} at ${foundGame.path}")
|
||||
Toast.makeText(context, "Found: ${foundGame.title}", Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Log.warning("[CustomSettingsHandler] No game found for title ID: $titleId")
|
||||
Toast.makeText(context, "Game not found: $titleId", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
return foundGame
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config file path for a title ID
|
||||
* Get the config file path for a game
|
||||
*/
|
||||
private fun getConfigFile(titleId: String): File {
|
||||
private fun getConfigFile(game: Game): File {
|
||||
return SettingsFile.getCustomSettingsFile(game)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the title ID config file path
|
||||
*/
|
||||
private fun getTitleIdConfigFile(titleId: String): File {
|
||||
val configDir = File(DirectoryInitialization.userDirectory, "config/custom")
|
||||
return File(configDir, "$titleId.ini")
|
||||
}
|
||||
|
@ -187,14 +249,14 @@ object CustomSettingsHandler {
|
|||
/**
|
||||
* Write the config file with the custom settings
|
||||
*/
|
||||
private fun writeConfigFile(titleId: String, customSettings: String): Boolean {
|
||||
private fun writeConfigFile(game: Game, customSettings: String): Boolean {
|
||||
return try {
|
||||
val configDir = File(DirectoryInitialization.userDirectory, "config/custom")
|
||||
if (!configDir.exists()) {
|
||||
val configFile = getConfigFile(game)
|
||||
val configDir = configFile.parentFile
|
||||
if (configDir != null && !configDir.exists()) {
|
||||
configDir.mkdirs()
|
||||
}
|
||||
|
||||
val configFile = File(configDir, "$titleId.ini")
|
||||
configFile.writeText(customSettings)
|
||||
|
||||
Log.info("[CustomSettingsHandler] Wrote config file: ${configFile.absolutePath}")
|
||||
|
@ -212,16 +274,14 @@ object CustomSettingsHandler {
|
|||
return suspendCoroutine { continuation ->
|
||||
activity.runOnUiThread {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle("Configuration Already Exists")
|
||||
.setTitle(activity.getString(R.string.config_already_exists_title))
|
||||
.setMessage(
|
||||
"Custom settings already exist for '$gameTitle'.\n\n" +
|
||||
"Do you want to overwrite the existing configuration?\n\n" +
|
||||
"This action cannot be undone."
|
||||
activity.getString(R.string.config_already_exists_message, gameTitle)
|
||||
)
|
||||
.setPositiveButton("Overwrite") { _, _ ->
|
||||
.setPositiveButton(activity.getString(R.string.overwrite)) { _, _ ->
|
||||
continuation.resume(true)
|
||||
}
|
||||
.setNegativeButton("Cancel") { _, _ ->
|
||||
.setNegativeButton(activity.getString(R.string.cancel)) { _, _ ->
|
||||
continuation.resume(false)
|
||||
}
|
||||
.setCancelable(false)
|
||||
|
@ -229,4 +289,26 @@ object CustomSettingsHandler {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract driver path from custom settings INI content
|
||||
*/
|
||||
private fun extractDriverPath(customSettings: String): String? {
|
||||
val lines = customSettings.lines()
|
||||
var inGpuDriverSection = false
|
||||
|
||||
for (line in lines) {
|
||||
val trimmed = line.trim()
|
||||
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
||||
inGpuDriverSection = trimmed == "[GpuDriver]"
|
||||
continue
|
||||
}
|
||||
|
||||
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
|
||||
return trimmed.substringAfter("driver_path=")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,372 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.fragments.DriverFetcherFragment
|
||||
import org.yuzu.yuzu_emu.fragments.DriverFetcherFragment.SortMode
|
||||
import org.yuzu.yuzu_emu.model.DriverViewModel
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
||||
object DriverResolver {
|
||||
private val client = OkHttpClient()
|
||||
|
||||
// Mirror of the repositories from DriverFetcherFragment
|
||||
private val repoList = listOf(
|
||||
DriverRepo("Mr. Purple Turnip", "MrPurple666/purple-turnip", 0),
|
||||
DriverRepo("GameHub Adreno 8xx", "crueter/GameHub-8Elite-Drivers", 1),
|
||||
DriverRepo("KIMCHI Turnip", "K11MCH1/AdrenoToolsDrivers", 2, true),
|
||||
DriverRepo("Weab-Chan Freedreno", "Weab-chan/freedreno_turnip-CI", 3)
|
||||
)
|
||||
|
||||
private data class DriverRepo(
|
||||
val name: String,
|
||||
val path: String,
|
||||
val sort: Int,
|
||||
val useTagName: Boolean = false
|
||||
)
|
||||
|
||||
/**
|
||||
* Extract driver path from custom settings INI content
|
||||
*/
|
||||
fun extractDriverPath(customSettings: String): String? {
|
||||
val lines = customSettings.lines()
|
||||
var inGpuDriverSection = false
|
||||
|
||||
for (line in lines) {
|
||||
val trimmed = line.trim()
|
||||
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
||||
inGpuDriverSection = trimmed == "[GpuDriver]"
|
||||
continue
|
||||
}
|
||||
|
||||
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
|
||||
return trimmed.substringAfter("driver_path=")
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a driver exists and handle missing drivers
|
||||
*/
|
||||
suspend fun ensureDriverExists(
|
||||
driverPath: String,
|
||||
activity: FragmentActivity,
|
||||
driverViewModel: DriverViewModel
|
||||
): Boolean {
|
||||
Log.info("[DriverResolver] Checking driver path: $driverPath")
|
||||
|
||||
val driverFile = File(driverPath)
|
||||
if (driverFile.exists()) {
|
||||
Log.info("[DriverResolver] Driver exists at: $driverPath")
|
||||
return true
|
||||
}
|
||||
|
||||
Log.warning("[DriverResolver] Driver not found: $driverPath")
|
||||
|
||||
// Extract driver name from path
|
||||
val driverName = extractDriverNameFromPath(driverPath)
|
||||
if (driverName == null) {
|
||||
Log.error("[DriverResolver] Could not extract driver name from path")
|
||||
return false
|
||||
}
|
||||
|
||||
Log.info("[DriverResolver] Searching for downloadable driver: $driverName")
|
||||
|
||||
// Check if driver exists locally with different path
|
||||
val localDriver = findLocalDriver(driverName)
|
||||
if (localDriver != null) {
|
||||
Log.info("[DriverResolver] Found local driver: ${localDriver.first}")
|
||||
// The game can use this local driver, no need to download
|
||||
return true
|
||||
}
|
||||
|
||||
// Search for downloadable driver
|
||||
val downloadableDriver = findDownloadableDriver(driverName)
|
||||
if (downloadableDriver != null) {
|
||||
Log.info("[DriverResolver] Found downloadable driver: ${downloadableDriver.name}")
|
||||
|
||||
val shouldInstall = askUserToInstallDriver(activity, downloadableDriver.name)
|
||||
if (shouldInstall) {
|
||||
return downloadAndInstallDriver(activity, downloadableDriver, driverViewModel)
|
||||
}
|
||||
} else {
|
||||
Log.warning("[DriverResolver] No downloadable driver found for: $driverName")
|
||||
showDriverNotFoundDialog(activity, driverName)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract driver name from full path
|
||||
*/
|
||||
private fun extractDriverNameFromPath(driverPath: String): String? {
|
||||
val file = File(driverPath)
|
||||
val fileName = file.name
|
||||
|
||||
// Remove .zip extension and extract meaningful name
|
||||
if (fileName.endsWith(".zip")) {
|
||||
return fileName.substring(0, fileName.length - 4)
|
||||
}
|
||||
|
||||
return fileName
|
||||
}
|
||||
|
||||
/**
|
||||
* Find driver in local storage by name matching
|
||||
*/
|
||||
private fun findLocalDriver(driverName: String): Pair<String, GpuDriverMetadata>? {
|
||||
val availableDrivers = GpuDriverHelper.getDrivers()
|
||||
|
||||
// Try exact match first
|
||||
availableDrivers.find { (_, metadata) ->
|
||||
metadata.name?.contains(driverName, ignoreCase = true) == true
|
||||
}?.let { return it }
|
||||
|
||||
// Try partial match
|
||||
availableDrivers.find { (path, metadata) ->
|
||||
path.contains(driverName, ignoreCase = true) ||
|
||||
metadata.name?.contains(
|
||||
extractKeywords(driverName).first(),
|
||||
ignoreCase = true
|
||||
) == true
|
||||
}?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract keywords from driver name for matching
|
||||
*/
|
||||
private fun extractKeywords(driverName: String): List<String> {
|
||||
val keywords = mutableListOf<String>()
|
||||
|
||||
// Common driver patterns
|
||||
when {
|
||||
driverName.contains("turnip", ignoreCase = true) -> keywords.add("turnip")
|
||||
driverName.contains("purple", ignoreCase = true) -> keywords.add("purple")
|
||||
driverName.contains("kimchi", ignoreCase = true) -> keywords.add("kimchi")
|
||||
driverName.contains("freedreno", ignoreCase = true) -> keywords.add("freedreno")
|
||||
driverName.contains("gamehub", ignoreCase = true) -> keywords.add("gamehub")
|
||||
}
|
||||
|
||||
// Version patterns
|
||||
Regex("v?\\d+\\.\\d+\\.\\d+").find(driverName)?.value?.let { keywords.add(it) }
|
||||
|
||||
if (keywords.isEmpty()) {
|
||||
keywords.add(driverName)
|
||||
}
|
||||
|
||||
return keywords
|
||||
}
|
||||
|
||||
/**
|
||||
* Find downloadable driver that matches the required driver
|
||||
*/
|
||||
private suspend fun findDownloadableDriver(driverName: String): DriverFetcherFragment.Artifact? {
|
||||
val keywords = extractKeywords(driverName)
|
||||
|
||||
for (repo in repoList) {
|
||||
// Check if this repo is relevant based on driver name
|
||||
val isRelevant = keywords.any { keyword ->
|
||||
repo.name.contains(keyword, ignoreCase = true) ||
|
||||
keyword.contains(repo.name.split(" ").first(), ignoreCase = true)
|
||||
}
|
||||
|
||||
if (!isRelevant) continue
|
||||
|
||||
try {
|
||||
val releases = fetchReleases(repo)
|
||||
val latestRelease = releases.firstOrNull { !it.prerelease }
|
||||
|
||||
latestRelease?.artifacts?.forEach { artifact ->
|
||||
if (matchesDriverName(artifact.name, driverName, keywords)) {
|
||||
return artifact
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.error(
|
||||
"[DriverResolver] Failed to fetch releases for ${repo.name}: ${e.message}"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if artifact name matches the required driver
|
||||
*/
|
||||
private fun matchesDriverName(
|
||||
artifactName: String,
|
||||
requiredName: String,
|
||||
keywords: List<String>
|
||||
): Boolean {
|
||||
// Exact match
|
||||
if (artifactName.equals(requiredName, ignoreCase = true)) return true
|
||||
|
||||
// Keyword matching
|
||||
return keywords.any { keyword ->
|
||||
artifactName.contains(keyword, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch releases from GitHub repo
|
||||
*/
|
||||
private suspend fun fetchReleases(repo: DriverRepo): List<DriverFetcherFragment.Release> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url("https://api.github.com/repos/${repo.path}/releases")
|
||||
.build()
|
||||
|
||||
client.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Failed to fetch releases: ${response.code}")
|
||||
}
|
||||
|
||||
val body = response.body?.string() ?: throw IOException("Empty response")
|
||||
DriverFetcherFragment.Release.fromJsonArray(body, repo.useTagName, SortMode.Default)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask user if they want to install the missing driver
|
||||
*/
|
||||
private suspend fun askUserToInstallDriver(
|
||||
activity: FragmentActivity,
|
||||
driverName: String
|
||||
): Boolean {
|
||||
return suspendCoroutine { continuation ->
|
||||
activity.runOnUiThread {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(activity.getString(R.string.missing_gpu_driver_title))
|
||||
.setMessage(activity.getString(R.string.missing_gpu_driver_message, driverName))
|
||||
.setPositiveButton(activity.getString(R.string.install)) { _, _ ->
|
||||
continuation.resume(true)
|
||||
}
|
||||
.setNegativeButton(activity.getString(R.string.cancel)) { _, _ ->
|
||||
continuation.resume(false)
|
||||
}
|
||||
.setCancelable(false)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Download and install driver automatically
|
||||
*/
|
||||
private suspend fun downloadAndInstallDriver(
|
||||
activity: FragmentActivity,
|
||||
artifact: DriverFetcherFragment.Artifact,
|
||||
driverViewModel: DriverViewModel
|
||||
): Boolean {
|
||||
return try {
|
||||
Log.info("[DriverResolver] Downloading driver: ${artifact.name}")
|
||||
Toast.makeText(
|
||||
activity,
|
||||
activity.getString(R.string.downloading_driver),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
|
||||
val cacheDir =
|
||||
activity.externalCacheDir ?: throw IOException("Cache directory not available")
|
||||
cacheDir.mkdirs()
|
||||
|
||||
val file = File(cacheDir, artifact.name)
|
||||
|
||||
// Download the driver
|
||||
withContext(Dispatchers.IO) {
|
||||
val request = Request.Builder()
|
||||
.url(artifact.url)
|
||||
.header("Accept", "application/octet-stream")
|
||||
.build()
|
||||
|
||||
client.newBuilder()
|
||||
.followRedirects(true)
|
||||
.followSslRedirects(true)
|
||||
.build()
|
||||
.newCall(request).execute().use { response ->
|
||||
if (!response.isSuccessful) {
|
||||
throw IOException("Download failed: ${response.code}")
|
||||
}
|
||||
|
||||
response.body?.byteStream()?.use { input ->
|
||||
FileOutputStream(file).use { output ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
} ?: throw IOException("Empty response body")
|
||||
}
|
||||
}
|
||||
|
||||
if (file.length() == 0L) {
|
||||
throw IOException("Downloaded file is empty")
|
||||
}
|
||||
|
||||
// Install the driver on main thread
|
||||
withContext(Dispatchers.Main) {
|
||||
val driverData = GpuDriverHelper.getMetadataFromZip(file)
|
||||
val driverPath = "${GpuDriverHelper.driverStoragePath}${file.name}"
|
||||
|
||||
if (GpuDriverHelper.copyDriverToInternalStorage(file.toUri())) {
|
||||
driverViewModel.onDriverAdded(Pair(driverPath, driverData))
|
||||
Log.info("[DriverResolver] Successfully installed driver: ${driverData.name}")
|
||||
Toast.makeText(
|
||||
activity,
|
||||
activity.getString(R.string.driver_installed),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
true
|
||||
} else {
|
||||
throw IOException("Failed to install driver")
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.error("[DriverResolver] Failed to download/install driver: ${e.message}")
|
||||
withContext(Dispatchers.Main) {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(activity.getString(R.string.driver_installation_failed_title))
|
||||
.setMessage(
|
||||
activity.getString(R.string.driver_installation_failed_message, e.message)
|
||||
)
|
||||
.setPositiveButton(activity.getString(R.string.ok)) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show dialog when driver cannot be found
|
||||
*/
|
||||
private fun showDriverNotFoundDialog(activity: FragmentActivity, driverName: String) {
|
||||
activity.runOnUiThread {
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setTitle(activity.getString(R.string.driver_not_available_title))
|
||||
.setMessage(activity.getString(R.string.driver_not_available_message, driverName))
|
||||
.setPositiveButton(activity.getString(R.string.ok)) { dialog, _ -> dialog.dismiss() }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue