AquaStream/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt

343 lines
15 KiB
Kotlin
Raw Normal View History

package com.lagradost.cloudstream3.utils
import android.app.Activity
2021-08-29 17:29:00 +00:00
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
2021-08-29 17:29:00 +00:00
import android.content.IntentFilter
import android.net.Uri
2021-08-29 17:29:00 +00:00
import android.os.Environment
2022-08-15 13:27:25 +00:00
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.content.FileProvider
2021-08-29 17:29:00 +00:00
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager
import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.BuildConfig
2022-01-07 19:27:25 +00:00
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
2021-09-01 13:18:41 +00:00
import com.lagradost.cloudstream3.mvvm.logError
2021-09-01 12:02:32 +00:00
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
import kotlinx.coroutines.runBlocking
2021-08-29 17:29:00 +00:00
import java.io.File
import kotlin.concurrent.thread
class InAppUpdater {
companion object {
2022-08-07 13:09:37 +00:00
const val GITHUB_USER_NAME = "recloudstream"
const val GITHUB_REPO = "cloudstream"
2022-08-03 22:09:00 +00:00
2022-08-15 13:27:25 +00:00
const val LOG_TAG = "InAppUpdater"
// === IN APP UPDATER ===
data class GithubAsset(
@JsonProperty("name") val name: String,
@JsonProperty("size") val size: Int, // Size bytes
@JsonProperty("browser_download_url") val browser_download_url: String, // download link
@JsonProperty("content_type") val content_type: String, // application/vnd.android.package-archive
)
data class GithubRelease(
@JsonProperty("tag_name") val tag_name: String, // Version code
@JsonProperty("body") val body: String, // Desc
@JsonProperty("assets") val assets: List<GithubAsset>,
@JsonProperty("target_commitish") val target_commitish: String, // branch
@JsonProperty("prerelease") val prerelease: Boolean,
2022-04-15 13:03:21 +00:00
@JsonProperty("node_id") val node_id: String //Node Id
)
data class GithubObject(
@JsonProperty("sha") val sha: String, // sha 256 hash
2021-08-29 18:42:44 +00:00
@JsonProperty("type") val type: String, // object type
@JsonProperty("url") val url: String,
)
data class GithubTag(
@JsonProperty("object") val github_object: GithubObject,
)
data class Update(
@JsonProperty("shouldUpdate") val shouldUpdate: Boolean,
@JsonProperty("updateURL") val updateURL: String?,
@JsonProperty("updateVersion") val updateVersion: String?,
@JsonProperty("changelog") val changelog: String?,
2022-04-15 13:03:21 +00:00
@JsonProperty("updateNodeId") val updateNodeId: String?
)
private fun Activity.getAppUpdate(): Update {
2021-09-20 19:27:51 +00:00
return try {
2021-08-17 14:04:10 +00:00
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
2022-08-15 13:27:25 +00:00
if (settingsManager.getBoolean(getString(R.string.prerelease_update_key), resources.getBoolean(R.bool.is_prerelease))) {
2021-08-17 14:04:10 +00:00
getPreReleaseUpdate()
} else {
getReleaseUpdate()
}
} catch (e: Exception) {
2022-08-15 13:27:25 +00:00
Log.e(LOG_TAG, Log.getStackTraceString(e))
2022-04-15 13:03:21 +00:00
Update(false, null, null, null, null)
2021-08-17 14:04:10 +00:00
}
}
private fun Activity.getReleaseUpdate(): Update {
2022-08-03 22:09:00 +00:00
val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases"
2021-08-17 14:04:10 +00:00
val headers = mapOf("Accept" to "application/vnd.github.v3+json")
val response =
parseJson<List<GithubRelease>>(runBlocking {
app.get(
url,
headers = headers
).text
})
2021-09-20 19:27:51 +00:00
val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""")
val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""")
2021-08-17 14:04:10 +00:00
/*
val releases = response.map { it.assets }.flatten()
.filter { it.content_type == "application/vnd.android.package-archive" }
val found =
releases.sortedWith(compareBy {
versionRegex.find(it.name)?.groupValues?.get(2)
}).toList().lastOrNull()*/
val found =
2021-08-29 18:42:44 +00:00
response.filter { rel ->
!rel.prerelease
}.sortedWith(compareBy { release ->
release.assets.filter { it.content_type == "application/vnd.android.package-archive" }
.getOrNull(0)?.name?.let { it1 ->
versionRegex.find(
it1
)?.groupValues?.get(2)
}
}).toList().lastOrNull()
2021-08-17 14:04:10 +00:00
val foundAsset = found?.assets?.getOrNull(0)
val currentVersion = packageName?.let {
2021-08-29 18:42:44 +00:00
packageManager.getPackageInfo(
it,
0
)
2021-08-17 14:04:10 +00:00
}
2021-09-20 19:27:51 +00:00
foundAsset?.name?.let { assetName ->
val foundVersion = versionRegex.find(assetName)
val shouldUpdate =
if (foundAsset.browser_download_url != "" && foundVersion != null) currentVersion?.versionName?.let { versionName ->
versionRegexLocal.find(versionName)?.groupValues?.let {
it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt()
}
}?.compareTo(
foundVersion.groupValues.let {
it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt()
}
)!! < 0 else false
return if (foundVersion != null) {
2022-01-13 21:09:05 +00:00
Update(
shouldUpdate,
foundAsset.browser_download_url,
foundVersion.groupValues[2],
2022-04-15 13:03:21 +00:00
found.body,
found.node_id
2022-01-13 21:09:05 +00:00
)
2021-09-20 19:27:51 +00:00
} else {
2022-04-15 13:03:21 +00:00
Update(false, null, null, null, null)
2021-09-20 19:27:51 +00:00
}
2021-08-17 14:04:10 +00:00
}
2022-04-15 13:03:21 +00:00
return Update(false, null, null, null, null)
2021-08-17 14:04:10 +00:00
}
private fun Activity.getPreReleaseUpdate(): Update = runBlocking {
2022-01-13 21:09:05 +00:00
val tagUrl =
2022-08-03 22:09:00 +00:00
"https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release"
val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases"
2021-08-17 14:04:10 +00:00
val headers = mapOf("Accept" to "application/vnd.github.v3+json")
val response =
parseJson<List<GithubRelease>>(app.get(releaseUrl, headers = headers).text)
2021-08-17 14:04:10 +00:00
val found =
response.lastOrNull { rel ->
2022-08-15 13:27:25 +00:00
rel.prerelease || rel.tag_name == "pre-release"
}
2022-08-15 13:27:25 +00:00
val foundAsset = found?.assets?.filter { it ->
it.content_type == "application/vnd.android.package-archive"
}?.getOrNull(0)
2021-08-17 14:04:10 +00:00
val tagResponse =
parseJson<GithubTag>(app.get(tagUrl, headers = headers).text)
2021-08-17 14:04:10 +00:00
2022-08-15 13:27:25 +00:00
Log.d(LOG_TAG, "Fetched GitHub tag: ${tagResponse.github_object.sha.take(8)}")
2022-01-13 21:09:05 +00:00
val shouldUpdate =
2022-08-15 13:27:25 +00:00
(getString(R.string.commit_hash) != tagResponse.github_object.sha.take(8))
2021-08-17 14:04:10 +00:00
return@runBlocking if (foundAsset != null) {
2022-01-13 21:09:05 +00:00
Update(
shouldUpdate,
foundAsset.browser_download_url,
tagResponse.github_object.sha,
2022-04-15 13:03:21 +00:00
found.body,
found.node_id
2022-01-13 21:09:05 +00:00
)
2021-08-17 14:04:10 +00:00
} else {
2022-04-15 13:03:21 +00:00
Update(false, null, null, null, null)
}
}
private fun Activity.downloadUpdate(url: String): Boolean {
2022-08-15 13:27:25 +00:00
Log.d(LOG_TAG, "Downloading update: ${url}")
2021-08-29 17:29:00 +00:00
val downloadManager = getSystemService<DownloadManager>()!!
val request = DownloadManager.Request(Uri.parse(url))
.setMimeType("application/vnd.android.package-archive")
2021-09-02 22:39:49 +00:00
.setTitle("CloudStream Update")
2021-08-29 17:29:00 +00:00
.setDestinationInExternalPublicDir(
Environment.DIRECTORY_DOWNLOADS,
"CloudStream.apk"
)
.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
.setAllowedOverRoaming(true)
.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
val localContext = this
2021-09-02 22:27:22 +00:00
val id = try {
downloadManager.enqueue(request)
} catch (e: Exception) {
logError(e)
showToast(this, R.string.storage_error, Toast.LENGTH_SHORT)
-1
}
if (id == -1L) return true
2021-08-29 17:29:00 +00:00
registerReceiver(
object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
2021-09-01 13:16:49 +00:00
try {
val downloadId = intent?.getLongExtra(
DownloadManager.EXTRA_DOWNLOAD_ID, id
) ?: id
2021-08-29 17:29:00 +00:00
2021-09-01 13:16:49 +00:00
val query = DownloadManager.Query()
query.setFilterById(downloadId)
val c = downloadManager.query(query)
2021-08-29 17:29:00 +00:00
2021-09-01 13:16:49 +00:00
if (c.moveToFirst()) {
val columnIndex = c.getColumnIndex(DownloadManager.COLUMN_STATUS)
if (DownloadManager.STATUS_SUCCESSFUL == c
.getInt(columnIndex)
) {
c.getColumnIndex(DownloadManager.COLUMN_MEDIAPROVIDER_URI)
val uri = Uri.parse(
c.getString(c.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI))
)
openApk(localContext, uri)
}
2021-08-29 17:29:00 +00:00
}
2021-09-02 22:27:22 +00:00
} catch (e: Exception) {
2021-09-01 13:18:41 +00:00
logError(e)
2021-08-29 17:29:00 +00:00
}
}
2021-08-29 17:29:00 +00:00
}, IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
)
return true
}
2021-08-29 17:29:00 +00:00
fun openApk(context: Context, uri: Uri) {
2022-01-13 21:09:05 +00:00
try {
uri.path?.let {
val contentUri = FileProvider.getUriForFile(
context,
BuildConfig.APPLICATION_ID + ".provider",
File(it)
)
val installIntent = Intent(Intent.ACTION_VIEW).apply {
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
data = contentUri
}
context.startActivity(installIntent)
}
2022-01-13 21:09:05 +00:00
} catch (e: Exception) {
logError(e)
}
}
fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
2022-01-13 21:09:05 +00:00
if (!checkAutoUpdate || settingsManager.getBoolean(
getString(R.string.auto_update_key),
true
)
) {
val update = getAppUpdate()
if (update.shouldUpdate && update.updateURL != null) {
2022-04-15 13:03:21 +00:00
//Check if update should be skipped
val updateNodeId = settingsManager.getString(getString(R.string.skip_update_key), "")
if (update.updateNodeId.equals(updateNodeId)) {
return false
}
runOnUiThread {
2021-09-01 13:16:49 +00:00
try {
val currentVersion = packageName?.let {
packageManager.getPackageInfo(
it,
0
)
}
2021-09-01 13:16:49 +00:00
val builder: AlertDialog.Builder = AlertDialog.Builder(this)
2021-09-02 22:27:22 +00:00
builder.setTitle(
getString(R.string.new_update_format).format(
currentVersion?.versionName,
update.updateVersion
)
)
2021-09-01 13:16:49 +00:00
builder.setMessage("${update.changelog}")
2021-09-01 13:16:49 +00:00
val context = this
builder.apply {
2021-09-02 15:45:00 +00:00
setPositiveButton(R.string.update) { _, _ ->
showToast(context, R.string.download_started, Toast.LENGTH_LONG)
2021-09-01 13:16:49 +00:00
thread {
val downloadStatus =
2022-01-13 21:09:05 +00:00
normalSafeApiCall { context.downloadUpdate(update.updateURL) }
?: false
2021-09-01 13:16:49 +00:00
if (!downloadStatus) {
runOnUiThread {
showToast(
context,
2021-09-02 15:45:00 +00:00
R.string.download_failed,
2021-09-01 13:16:49 +00:00
Toast.LENGTH_LONG
)
}
}
2021-08-29 18:42:44 +00:00
}
}
2021-09-02 15:45:00 +00:00
setNegativeButton(R.string.cancel) { _, _ -> }
2021-09-01 13:16:49 +00:00
if (checkAutoUpdate) {
2022-04-15 13:03:21 +00:00
setNeutralButton(R.string.skip_update) { _, _ ->
settingsManager.edit().putString(getString(R.string.skip_update_key), update.updateNodeId ?: "")
2022-01-13 21:09:05 +00:00
.apply()
2021-09-01 13:16:49 +00:00
}
}
}
2021-09-01 13:16:49 +00:00
builder.show()
} catch (e: Exception) {
2021-09-01 13:18:41 +00:00
logError(e)
}
}
return true
}
return false
}
return false
}
}
}