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:
parent
f9fc9fa5ed
commit
630e84d635
@ -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)
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
24
app/src/main/res/layout/dialog_progress_bar.xml
Normal file
24
app/src/main/res/layout/dialog_progress_bar.xml
Normal 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>
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user