pull/1185/merge
deniscerri 3 months ago
parent 48f9637a7e
commit 98318bdb1f
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -25,9 +25,6 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<!-- foreground service permission for android 14 and up-->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"
tools:ignore="RequestInstallPackagesPolicy" />
<queries>
<package android:name="com.deniscerri.ytdl.python" />
<package android:name="com.deniscerri.ytdl.ffmpeg" />

@ -1,18 +1,28 @@
package com.deniscerri.ytdl.core.packages
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.edit
import androidx.core.net.toUri
import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.core.ZipUtils
import com.deniscerri.ytdl.database.models.GithubReleaseAsset
import com.deniscerri.ytdl.util.FileUtil
import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
@ -187,44 +197,88 @@ abstract class PackageBase {
)
}
suspend fun downloadReleaseApk(context: Context, release: PackageRelease, onProgress: (Int) -> Unit) : Result<File> {
return withContext(Dispatchers.IO) {
try {
val tempApk = File(context.cacheDir, "${packageFolderName}_${release.version.replace(".", "")}.apk")
//download
val request = Request.Builder()
.url(release.assets.first().browser_download_url)
.build()
val response = sharedClient.newCall(request).execute()
if (!response.isSuccessful) {
return@withContext Result.failure(Throwable(response.body.string()))
private var downloadReleaseId = -1L
private val onDownloadReleaseComplete = object : BroadcastReceiver() {
@SuppressLint("Range")
override fun onReceive(context: Context?, intent: Intent) {
if (context == null) return;
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
Log.e("sssssss",id.toString())
if (id == downloadReleaseId) {
context.unregisterReceiver(this)
val query = DownloadManager.Query().setFilterById(id)
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_SUCCESSFUL) {
val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
Log.e("sssssss", localUri)
FileUtil.openFileIntent(context, localUri)
}
}
cursor.close()
}
}
}
val body = response.body
val totalBytes = body.contentLength()
var bytesDownloaded = 0L
body.byteStream().use { inputStream ->
tempApk.outputStream().use { outputStream ->
val buffer = ByteArray(8192) // 8KB buffer
var bytesRead: Int
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
if (!isActive) throw CancellationException("Download cancelled by user")
outputStream.write(buffer, 0, bytesRead)
bytesDownloaded += bytesRead
@SuppressLint("UnspecifiedRegisterReceiverFlag", "Range")
suspend fun downloadReleaseApk(context: Context, release: PackageRelease, onProgress: (Long) -> Unit) : Result<Unit> {
return withContext(Dispatchers.IO) {
try {
val releaseVersion = release.assets.first()
val uri = releaseVersion.browser_download_url.toUri()
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs()
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
downloadReleaseId = downloadManager.enqueue(
DownloadManager.Request(uri)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(releaseVersion.name)
.setDescription(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
)
var downloading = true
while (downloading) {
val query = DownloadManager.Query().setFilterById(downloadReleaseId)
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_SUCCESSFUL) {
downloading = false
}
// Calculate percentage
val progress = ((bytesDownloaded * 100) / totalBytes).toInt()
if (totalBytes > 0) {
val progress = (bytesDownloaded * 100L / totalBytes)
onProgress(progress)
}
}
cursor.close()
delay(500)
}
Result.success(tempApk)
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(onDownloadReleaseComplete, intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(onDownloadReleaseComplete, intentFilter)
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}

@ -199,6 +199,8 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
}
override fun onDownloadReleaseClick(item: PackageBase.PackageRelease) {
bottomSheet?.dismiss()
var positiveButton: Button? = null
val updateDialog = MaterialAlertDialogBuilder(requireContext())
.setTitle("${item.tag_name} (${FileUtil.convertFileSize(item.downloadSize)})")
@ -245,15 +247,14 @@ class PackagesFragment : Fragment(), PackagesAdapter.OnItemClickListener, Packag
}
}
}
fileResp.onSuccess { file ->
view.dismiss()
val contentUri = FileProvider.getUriForFile(requireContext(), requireContext().packageName + ".fileprovider", file)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(contentUri, "application/vnd.android.package-archive")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
fileResp.onSuccess {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
Snackbar.make(requireView(), getString(R.string.install_downloaded_file), Snackbar.LENGTH_LONG).show()
}
}
installLauncher.launch(intent)
}
}
}

@ -2757,18 +2757,26 @@ object UiUtil {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
Snackbar.make(context.findViewById(R.id.frame_layout), it.message ?: context.getString(R.string.errored), Snackbar.LENGTH_LONG).show()
Snackbar.make(
context.findViewById(R.id.frame_layout),
it.message ?: context.getString(R.string.errored),
Snackbar.LENGTH_LONG
).show()
}
}
}
fileResp.onSuccess { file ->
view.dismiss()
val contentUri = FileProvider.getUriForFile(context, context.packageName + ".fileprovider", file)
val intent = Intent(Intent.ACTION_VIEW).apply {
setDataAndType(contentUri, "application/vnd.android.package-archive")
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK
fileResp.onSuccess {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
view.dismiss()
Snackbar.make(
context.findViewById(R.id.frame_layout),
context.getString(R.string.install_downloaded_file),
Snackbar.LENGTH_LONG
).show()
}
}
context.startActivity(intent)
}
}
}

@ -1,8 +1,14 @@
package com.deniscerri.ytdl.util
import android.annotation.SuppressLint
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.util.Log
import androidx.preference.PreferenceManager
import com.deniscerri.ytdl.BuildConfig
@ -15,6 +21,7 @@ import com.deniscerri.ytdl.database.models.GithubRelease
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import okhttp3.Request
@ -23,6 +30,7 @@ import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import kotlin.coroutines.cancellation.CancellationException
import androidx.core.net.toUri
class UpdateUtil(var context: Context) {
@ -158,45 +166,82 @@ class UpdateUtil(var context: Context) {
}
suspend fun downloadReleaseApk(context: Context, release: GithubRelease, onProgress: (Int) -> Unit) : Result<File> {
@SuppressLint("Range", "UnspecifiedRegisterReceiverFlag")
suspend fun downloadReleaseApk(context: Context, release: GithubRelease, onProgress: (Long) -> Unit) : Result<Unit> {
return withContext(Dispatchers.IO) {
try {
val tempApk = File(context.cacheDir, "YTDLnis-${release.tag_name}_interally_downloaded.apk")
val releaseVersion = release.assets.firstOrNull { it.name.contains(Build.SUPPORTED_ABIS[0]) }
//download
val request = Request.Builder()
.url(releaseVersion!!.browser_download_url)
.build()
val uri = releaseVersion!!.browser_download_url.toUri()
Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
.mkdirs()
val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
val downloadID = downloadManager.enqueue(
DownloadManager.Request(uri)
.setAllowedNetworkTypes(
DownloadManager.Request.NETWORK_WIFI or
DownloadManager.Request.NETWORK_MOBILE
)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
.setTitle(releaseVersion.name)
.setDescription(context.getString(R.string.downloading_update))
.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, releaseVersion.name)
)
var downloading = true
while (downloading) {
val query = DownloadManager.Query().setFilterById(downloadID)
val cursor = downloadManager.query(query)
if (cursor.moveToFirst()) {
val bytesDownloaded = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
val totalBytes = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_SUCCESSFUL) {
downloading = false
}
val response = sharedClient.newCall(request).execute()
if (!response.isSuccessful) {
return@withContext Result.failure(Throwable(response.body.string()))
if (totalBytes > 0) {
val progress = (bytesDownloaded * 100L / totalBytes)
onProgress(progress)
}
}
cursor.close()
delay(500)
}
val body = response.body
val totalBytes = body.contentLength()
var bytesDownloaded = 0L
val onDownloadComplete = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
val id = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1L)
body.byteStream().use { inputStream ->
tempApk.outputStream().use { outputStream ->
val buffer = ByteArray(8192) // 8KB buffer
var bytesRead: Int
if (id == downloadID) {
context?.unregisterReceiver(this)
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
if (!isActive) throw CancellationException("Download cancelled by user")
val query = DownloadManager.Query().setFilterById(id)
val cursor = downloadManager.query(query)
outputStream.write(buffer, 0, bytesRead)
bytesDownloaded += bytesRead
// Calculate percentage
val progress = ((bytesDownloaded * 100) / totalBytes).toInt()
onProgress(progress)
if (cursor.moveToFirst()) {
val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS))
if (status == DownloadManager.STATUS_SUCCESSFUL) {
val localUri = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
FileUtil.openFileIntent(context!!, localUri)
}
}
cursor.close()
}
}
}
Result.success(tempApk)
val intentFilter = IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
context.registerReceiver(onDownloadComplete, intentFilter, Context.RECEIVER_EXPORTED)
} else {
context.registerReceiver(onDownloadComplete, intentFilter)
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}

@ -519,4 +519,5 @@
<string name="playlist_subdirectory">Save to playlist subdirectory</string>
<string name="playlist_subdirectory_summary">Save files in folders named as the playlist name</string>
<string name="trim_filenames">Trim filenames</string>
<string name="install_downloaded_file">Install the downloaded file</string>
</resources>

Loading…
Cancel
Save