[android] fix intent-auto-driver-install #369

Merged
crueter merged 2 commits from fix/intent-auto-driver-install into master 2025-08-31 03:33:55 +02:00
2 changed files with 102 additions and 19 deletions

View file

@ -124,11 +124,16 @@ object CustomSettingsHandler {
// Check for driver requirements if activity and driverViewModel are provided
if (activity != null && driverViewModel != null) {
val driverPath = extractDriverPath(customSettings)
if (driverPath != null) {
Log.info("[CustomSettingsHandler] Custom settings specify driver: $driverPath")
val rawDriverPath = extractDriverPath(customSettings)
if (rawDriverPath != null) {
// Normalize to local storage path (we only store drivers under driverStoragePath)
val driverFilename = rawDriverPath.substringAfterLast('/')
.substringAfterLast('\\')
val localDriverPath = "${GpuDriverHelper.driverStoragePath}$driverFilename"
Log.info("[CustomSettingsHandler] Custom settings specify driver: $rawDriverPath (normalized: $localDriverPath)")
// Check if driver exists in the driver storage
val driverFile = File(driverPath)
val driverFile = File(localDriverPath)
if (!driverFile.exists()) {
Log.info("[CustomSettingsHandler] Driver not found locally: ${driverFile.name}")
@ -182,7 +187,7 @@ object CustomSettingsHandler {
}
// Attempt to download and install the driver
val driverUri = DriverResolver.ensureDriverAvailable(driverPath, activity) { progress ->
val driverUri = DriverResolver.ensureDriverAvailable(driverFilename, activity) { progress ->
progressChannel.trySend(progress.toInt())
}
@ -209,12 +214,12 @@ object CustomSettingsHandler {
return null
}
// Verify the downloaded driver
val installedFile = File(driverPath)
// Verify the downloaded driver (from normalized local path)
val installedFile = File(localDriverPath)
val metadata = GpuDriverHelper.getMetadataFromZip(installedFile)
if (metadata.name == null) {
Log.error(
"[CustomSettingsHandler] Downloaded driver is invalid: $driverPath"
"[CustomSettingsHandler] Downloaded driver is invalid: $localDriverPath"
)
Toast.makeText(
activity,
@ -232,7 +237,7 @@ object CustomSettingsHandler {
}
// Add to driver list
driverViewModel.onDriverAdded(Pair(driverPath, metadata))
driverViewModel.onDriverAdded(Pair(localDriverPath, metadata))
Log.info(
"[CustomSettingsHandler] Successfully downloaded and installed driver: ${metadata.name}"
)
@ -268,7 +273,7 @@ object CustomSettingsHandler {
// Driver exists, verify it's valid
val metadata = GpuDriverHelper.getMetadataFromZip(driverFile)
if (metadata.name == null) {
Log.error("[CustomSettingsHandler] Invalid driver file: $driverPath")
Log.error("[CustomSettingsHandler] Invalid driver file: $localDriverPath")
Toast.makeText(
activity,
activity.getString(
@ -459,6 +464,8 @@ object CustomSettingsHandler {
if (inGpuDriverSection && trimmed.startsWith("driver_path=")) {
return trimmed.substringAfter("driver_path=")
.trim()
.removeSurrounding("\"", "\"")
}
}

View file

@ -68,6 +68,48 @@ object DriverResolver {
val filename: String
)
// Matching helpers
private val KNOWN_SUFFIXES = listOf(
".adpkg.zip",
".zip",
".7z",
".tar.gz",
".tar.xz",
".rar"
)
private fun stripKnownSuffixes(name: String): String {
var result = name
var changed: Boolean
do {
changed = false
for (s in KNOWN_SUFFIXES) {
if (result.endsWith(s, ignoreCase = true)) {
result = result.dropLast(s.length)
changed = true
}
}
} while (changed)
return result
}
private fun normalizeName(name: String): String {
val base = stripKnownSuffixes(name.lowercase())
// Remove non-alphanumerics to make substring checks resilient
return base.replace(Regex("[^a-z0-9]+"), " ").trim()
}
private fun tokenize(name: String): Set<String> =
normalizeName(name).split(Regex("\\s+")).filter { it.isNotBlank() }.toSet()
// Jaccard similarity between two sets
private fun jaccard(a: Set<String>, b: Set<String>): Double {
if (a.isEmpty() || b.isEmpty()) return 0.0
val inter = a.intersect(b).size.toDouble()
val uni = a.union(b).size.toDouble()
return if (uni == 0.0) 0.0 else inter / uni
}
/**
* Resolve a driver download URL from its filename
* @param filename The driver filename (e.g., "turnip_mrpurple-T19-toasted.adpkg.zip")
@ -98,7 +140,7 @@ object DriverResolver {
async {
searchRepository(repoPath, filename)
}
}.mapNotNull { it.await() }.firstOrNull().also { resolved ->
}.firstNotNullOfOrNull { it.await() }.also { resolved ->
// Cache the result if found
resolved?.let {
urlCache[filename] = it
@ -119,22 +161,56 @@ object DriverResolver {
releaseCache[repoPath] = it
}
// Search through all releases and artifacts
// First pass: exact name (case-insensitive) against asset filenames
val target = filename.lowercase()
for (release in releases) {
for (artifact in release.artifacts) {
if (artifact.name == filename) {
Log.info(
"[DriverResolver] Found $filename in $repoPath/${release.tagName}"
)
if (artifact.name.equals(filename, ignoreCase = true) || artifact.name.lowercase() == target) {
Log.info("[DriverResolver] Found $filename in $repoPath/${release.tagName}")
return@withContext ResolvedDriver(
downloadUrl = artifact.url.toString(),
repoPath = repoPath,
releaseTag = release.tagName,
filename = filename
filename = artifact.name
)
}
}
}
// Second pass: fuzzy match by asset filenames only
val reqNorm = normalizeName(filename)
val reqTokens = tokenize(filename)
var best: ResolvedDriver? = null
var bestScore = 0.0
for (release in releases) {
for (artifact in release.artifacts) {
val artNorm = normalizeName(artifact.name)
val artTokens = tokenize(artifact.name)
var score = jaccard(reqTokens, artTokens)
// Boost if one normalized name contains the other
if (artNorm.contains(reqNorm) || reqNorm.contains(artNorm)) {
score = maxOf(score, 0.92)
}
if (score > bestScore) {
bestScore = score
best = ResolvedDriver(
downloadUrl = artifact.url.toString(),
repoPath = repoPath,
releaseTag = release.tagName,
filename = artifact.name
)
}
}
}
// Threshold to avoid bad guesses, this worked fine in testing but might need tuning
if (best != null && bestScore >= 0.6) {
Log.info("[DriverResolver] Fuzzy matched $filename -> ${best.filename} in ${best.repoPath} (score=%.2f)".format(bestScore))
return@withContext best
}
null
} catch (e: Exception) {
Log.error("[DriverResolver] Failed to search $repoPath: ${e.message}")
@ -296,8 +372,8 @@ object DriverResolver {
context: Context,
onProgress: ((Float) -> Unit)? = null
): Uri? {
// Extract filename from path
val filename = driverPath.substringAfterLast('/')
// Extract filename from path (support both separators)
val filename = driverPath.substringAfterLast('/').substringAfterLast('\\')
// Check if driver already exists locally
val localPath = "${GpuDriverHelper.driverStoragePath}$filename"