0
0
mirror of https://github.com/Ishan09811/pine.git synced 2025-04-24 08:55:10 +00:00

Allow also fetching non official or non valid drivers + Improve error checking + show realtime downloading progress (#70)

This commit is contained in:
Ishan09811 2025-02-19 16:16:11 +05:30 committed by GitHub
parent f9fc9fa5ed
commit 630e84d635
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 155 additions and 23 deletions

View File

@ -10,6 +10,8 @@ import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION
import android.os.Bundle
import android.view.ViewTreeObserver
import android.view.LayoutInflater
import android.view.View
import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.coordinatorlayout.widget.CoordinatorLayout
@ -22,6 +24,7 @@ import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.LinearProgressIndicator
import dagger.hilt.android.AndroidEntryPoint
import emu.skyline.R
import emu.skyline.adapter.GenericListItem
@ -38,6 +41,8 @@ import emu.skyline.utils.GpuDriverInstallResult
import emu.skyline.utils.WindowInsetsHelper
import emu.skyline.utils.serializable
import emu.skyline.utils.DriversFetcher
import emu.skyline.utils.DriversFetcher.FetchResult
import emu.skyline.utils.DriversFetcher.FetchResultOutput
import emu.skyline.utils.DriversFetcher.DownloadResult
import emu.skyline.di.getSettings
import emu.skyline.SkylineApplication
@ -252,20 +257,38 @@ class GpuDriverActivity : AppCompatActivity() {
fetchAndShowDrivers(textInputValue)
}
}
.setNegativeButton(android.R.string.cancel) {_, _ -> }
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun fetchAndShowDrivers(repoUrl: String) {
private fun fetchAndShowDrivers(repoUrl: String, bypassValidation: Boolean = false) {
lifecycleScope.launch(Dispatchers.Main) {
val releases = DriversFetcher.fetchReleases(repoUrl)
if (releases.isEmpty()) {
Snackbar.make(binding.root, "Failed to fetch ${repoUrl}: validation failed or check your internet connection", Snackbar.LENGTH_SHORT).show()
val progressDialog = MaterialAlertDialogBuilder(this@GpuDriverActivity)
.setTitle(R.string.fetching)
.setView(R.layout.dialog_progress_bar)
.setCancelable(false)
.create()
progressDialog.show()
val progressBar = progressDialog.findViewById<LinearProgressIndicator>(R.id.progress_bar)
val progressText = progressDialog.findViewById<TextView>(R.id.progress_text)
progressText?.visibility = View.GONE
progressBar?.isIndeterminate = true
val fetchOutput = DriversFetcher.fetchReleases(repoUrl, bypassValidation)
progressDialog.dismiss()
if (fetchOutput.result is FetchResult.Error) {
showErrorDialog(fetchOutput.result.message ?: "Something unexpected occurred while fetching $repoUrl drivers")
return@launch
}
if (fetchOutput.result is FetchResult.Warning) {
showWarningDialog(repoUrl, fetchOutput.result.message ?: "Something unexpected occurred while fetching $repoUrl drivers")
return@launch
}
val releaseNames = releases.map { it.first }
val releaseUrls = releases.map { it.second }
val releaseNames = fetchOutput.fetchedDrivers.map { it.first }
val releaseUrls = fetchOutput.fetchedDrivers.map { it.second }
var chosenUrl: String? = releaseUrls[0]
var chosenName: String? = releaseNames[0]
@ -285,21 +308,70 @@ class GpuDriverActivity : AppCompatActivity() {
private fun downloadDriver(chosenUrl: String, chosenName: String) {
GlobalScope.launch(Dispatchers.Main) {
val progressDialog = MaterialAlertDialogBuilder(this@GpuDriverActivity)
.setTitle(R.string.downloading)
.setView(R.layout.dialog_progress_bar)
.setCancelable(false)
.create()
progressDialog.show()
val progressBar = progressDialog.findViewById<LinearProgressIndicator>(R.id.progress_bar)
val progressText = progressDialog.findViewById<TextView>(R.id.progress_text)
progressText?.visibility = View.GONE
progressBar?.isIndeterminate = true
var driverFile = File("${SkylineApplication.instance.getPublicFilesDir().canonicalPath}/${chosenName}.zip")
if (!driverFile.exists()) driverFile.createNewFile()
val result = DriversFetcher.downloadAsset(chosenUrl!!, driverFile)
val result = DriversFetcher.downloadAsset(chosenUrl, driverFile) { downloadedBytes, totalBytes ->
// when using unit it stays to of this unit origin thread that's why we need to use main thread
GlobalScope.launch(Dispatchers.Main) {
if (totalBytes > 0) {
if (progressBar?.isIndeterminate ?: false) progressBar?.isIndeterminate = false
if (progressText?.visibility == View.GONE) progressText?.visibility = View.VISIBLE
val progress = (downloadedBytes * 100 / totalBytes).toInt()
progressBar?.max = 100
progressBar?.progress = progress
progressText?.text = "$progress%"
} else {
if (progressText?.visibility == View.VISIBLE) progressText?.visibility = View.GONE
if (!(progressBar?.isIndeterminate ?: false)) progressBar?.isIndeterminate = true
}
}
}
progressDialog.dismiss()
when (result) {
is DownloadResult.Success -> {
val result = GpuDriverHelper.installDriver(this@GpuDriverActivity, FileInputStream(driverFile))
Snackbar.make(binding.root, resolveInstallResultString(result), Snackbar.LENGTH_LONG).show()
if (result == GpuDriverInstallResult.Success) populateAdapter()
driverFile.delete()
}
is DownloadResult.Error -> Snackbar.make(binding.root, "Failed to import ${chosenName}: ${result.message}", Snackbar.LENGTH_SHORT).show()
}
driverFile.delete()
}
}
private fun showErrorDialog(message: String) {
MaterialAlertDialogBuilder(this@GpuDriverActivity)
.setTitle(R.string.error)
.setMessage(message)
.setPositiveButton(R.string.close, null)
.create()
.show()
}
private fun showWarningDialog(repoUrl: String, message: String) {
MaterialAlertDialogBuilder(this@GpuDriverActivity)
.setTitle(R.string.warning)
.setMessage(message)
.setPositiveButton(R.string.misc_continue) { _, _ ->
fetchAndShowDrivers(repoUrl, true)
}
.setNegativeButton(android.R.string.cancel, null)
.create()
.show()
}
private fun resolveInstallResultString(result : GpuDriverInstallResult) = when (result) {
GpuDriverInstallResult.Success -> getString(R.string.gpu_driver_install_success)
GpuDriverInstallResult.InvalidArchive -> getString(R.string.gpu_driver_install_invalid_archive)

View File

@ -11,6 +11,7 @@ import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*
import io.ktor.utils.io.*
import io.ktor.http.HttpHeaders
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable
@ -38,12 +39,19 @@ object DriversFetcher {
@Serializable
data class Asset(val browser_download_url: String)
suspend fun fetchReleases(repoUrl: String): List<Pair<String, String?>> {
suspend fun fetchReleases(repoUrl: String, bypassValidation: Boolean = false): FetchResultOutput {
val repoPath = repoUrl.removePrefix("https://github.com/")
val validationUrl = "https://api.github.com/repos/$repoPath/contents/.adrenoDrivers"
val apiUrl = "https://api.github.com/repos/$repoPath/releases"
return try {
val response: HttpResponse = withContext(Dispatchers.IO) {
httpClient.get(apiUrl)
}
if (response.status.value != 200)
return FetchResultOutput(emptyList(), FetchResult.Error("Failed to fetch drivers"))
val isValid = withContext(Dispatchers.IO) {
try {
httpClient.get(validationUrl).status.value == 200
@ -52,31 +60,35 @@ object DriversFetcher {
}
}
if (!isValid) {
Log.d("DriversFetcher", "Provided driver repo url is not valid.")
return emptyList()
if (!isValid && !bypassValidation) {
return FetchResultOutput(emptyList(), FetchResult.Warning("Provided driver repo url is not valid."))
}
val releases: List<GitHubRelease> = withContext(Dispatchers.IO) {
httpClient.get(apiUrl).body()
}
releases.map { release ->
val releases: List<GitHubRelease> = response.body()
val drivers = releases.map { release ->
val assetUrl = release.assets.firstOrNull()?.browser_download_url
release.name to assetUrl
}
FetchResultOutput(drivers, FetchResult.Success)
} catch (e: Exception) {
Log.e("DriversFetcher", "Error fetching releases: ${e.message}", e)
emptyList()
FetchResultOutput(emptyList(), FetchResult.Error("Error fetching releases: ${e.message}"))
}
}
suspend fun downloadAsset(assetUrl: String, destinationFile: File): DownloadResult {
suspend fun downloadAsset(
assetUrl: String,
destinationFile: File,
progressCallback: (Long, Long) -> Unit
): DownloadResult {
return try {
withContext(Dispatchers.IO) {
val response: HttpResponse = httpClient.get(assetUrl)
val contentLength = response.headers[HttpHeaders.ContentLength]?.toLong() ?: -1L
FileOutputStream(destinationFile)?.use { outputStream ->
writeResponseToStream(response, outputStream)
} ?: return@withContext DownloadResult.Error("Failed to open ${destinationFile.absolutePath.toString()}")
writeResponseToStream(response, outputStream, contentLength, progressCallback)
} ?: return@withContext DownloadResult.Error("Failed to open ${destinationFile.absolutePath}")
}
DownloadResult.Success
} catch (e: Exception) {
@ -85,14 +97,22 @@ object DriversFetcher {
}
}
private suspend fun writeResponseToStream(response: HttpResponse, outputStream: OutputStream) {
private suspend fun writeResponseToStream(
response: HttpResponse,
outputStream: OutputStream,
contentLength: Long,
progressCallback: (Long, Long) -> Unit
) {
val channel = response.bodyAsChannel()
val buffer = ByteArray(8192) // 8KB buffer size
val buffer = ByteArray(1024) // 1KB buffer size
var totalBytesRead = 0L
while (!channel.isClosedForRead) {
val bytesRead = channel.readAvailable(buffer)
if (bytesRead > 0) {
outputStream.write(buffer, 0, bytesRead)
totalBytesRead += bytesRead
progressCallback(totalBytesRead, contentLength)
}
}
outputStream.flush()
@ -102,4 +122,15 @@ object DriversFetcher {
object Success : DownloadResult()
data class Error(val message: String?) : DownloadResult()
}
data class FetchResultOutput(
val fetchedDrivers: List<Pair<String, String?>>,
val result: FetchResult
)
sealed class FetchResult {
object Success : FetchResult()
data class Error(val message: String?) : FetchResult()
data class Warning(val message: String?) : FetchResult()
}
}

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
app:trackCornerRadius="4dp" />
<TextView
android:id="@+id/progress_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="24dp"
android:layout_marginRight="24dp"
android:layout_marginBottom="24dp"
android:gravity="end" />
</LinearLayout>

View File

@ -3,6 +3,11 @@
<!-- Common -->
<string name="search">Search</string>
<string name="error">An error has occurred</string>
<string name="warning">Warning</string>
<string name="fetching">Fetching</string>
<string name="downloading">Downloading</string>
<string name="misc_continue">Continue</string>
<string name="close">Close</string>
<string name="copied_to_clipboard">Copied to clipboard</string>
<string name="emulator">Emulator</string>
<string name="enabled">Enabled</string>