ytdlnis/app/src/main/java/com/deniscerri/ytdl/util/UpdateUtil.kt
2025-03-02 17:15:52 +01:00

203 lines
No EOL
7.6 KiB
Kotlin

package com.deniscerri.ytdl.util
import android.annotation.SuppressLint
import android.app.Activity
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.IntentFilter
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.os.Handler
import android.os.Looper
import android.text.method.LinkMovementMethod
import android.util.Log
import android.widget.TextView
import android.widget.Toast
import androidx.core.content.ContextCompat.startActivity
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.work.WorkManager.UpdateResult
import com.deniscerri.ytdl.BuildConfig
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.database.models.GithubRelease
import com.deniscerri.ytdl.ui.adapter.ChangelogAdapter
import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.yausername.youtubedl_android.YoutubeDL
import com.yausername.youtubedl_android.YoutubeDLRequest
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
class UpdateUtil(var context: Context) {
private val tag = "UpdateUtil"
private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
private val channelMap = mapOf(
Pair<String, YoutubeDL.UpdateChannel>("stable", YoutubeDL.UpdateChannel.STABLE),
Pair<String, YoutubeDL.UpdateChannel>("nightly", YoutubeDL.UpdateChannel.NIGHTLY),
Pair<String, YoutubeDL.UpdateChannel>("master", YoutubeDL.UpdateChannel.MASTER)
)
@SuppressLint("UnspecifiedRegisterReceiverFlag")
fun tryGetNewVersion() : Result<GithubRelease> {
try {
val skippedVersions = sharedPreferences.getString("skip_updates", "")?.split(",")?.distinct()?.toMutableList() ?: mutableListOf()
val res = getGithubReleases()
if (res.isEmpty()){
return Result.failure(Error(context.getString(R.string.network_error)))
}
val useBeta = sharedPreferences.getBoolean("update_beta", false)
val v: GithubRelease
if (useBeta){
val tmp = res.firstOrNull { it.tag_name.contains("beta", true) && res.indexOf(it) == 0 }
v = tmp ?: res.first()
}else{
v = res.first { !it.tag_name.contains("beta", true) }
}
val current = BuildConfig.VERSION_NAME.replace("-beta", "").replace(".", "").padEnd(10,'0').toInt()
val incoming = v.tag_name.removePrefix("v").replace("-beta", "").replace(".", "").padEnd(10,'0').toInt()
var isInLatest = true
if ((current < incoming) ||
(current > incoming && BuildConfig.VERSION_NAME.contains("beta", true) && !useBeta)){
isInLatest = false
}
if (skippedVersions.contains(v.tag_name)) isInLatest = true
if (isInLatest){
return Result.failure(Error(context.getString(R.string.you_are_in_latest_version)))
}
return Result.success(v)
}catch (e: Exception){
e.printStackTrace()
return Result.failure(e)
}
}
fun showChangeLog(activity: Activity){
runCatching {
val releases = getGithubReleases()
val view = activity.layoutInflater.inflate(R.layout.generic_list, null)
val adapter = ChangelogAdapter(activity)
val recycler = view.findViewById<RecyclerView>(R.id.download_recyclerview)
recycler.layoutManager = LinearLayoutManager(activity)
recycler.adapter = adapter
adapter.submitList(releases)
recycler.enableFastScroll()
val changeLogDialog = MaterialAlertDialogBuilder(context)
.setTitle(activity.getString(R.string.changelog))
.setView(view)
.setIcon(R.drawable.ic_chapters)
.setNegativeButton(context.resources.getString(R.string.dismiss)) { _: DialogInterface?, _: Int -> }
Handler(Looper.getMainLooper()).post {
changeLogDialog.show()
}
}.onFailure {
if (it.message != null){
Handler(Looper.getMainLooper()).post {
UiUtil.showErrorDialog(context, it.message!!)
}
}
}
}
fun getGithubReleases(): List<GithubRelease> {
val url = "https://api.github.com/repos/deniscerri/ytdlnis/releases"
val conn: HttpURLConnection
var json = listOf<GithubRelease>()
try {
val req = URL(url)
conn = req.openConnection() as HttpURLConnection
conn.requestMethod = "GET"
conn.connectTimeout = 10000
conn.readTimeout = 5000
if (conn.responseCode < 300) {
val myType = object : TypeToken<List<GithubRelease>>() {}.type
json = Gson().fromJson(InputStreamReader(conn.inputStream), myType)
}
conn.disconnect()
} catch (e: Exception) {
Log.e(tag, e.toString())
}
return json
}
data class YTDLPUpdateResponse (
val status: YTDLPUpdateStatus,
val message: String = ""
)
enum class YTDLPUpdateStatus {
DONE, ALREADY_UP_TO_DATE, PROCESSING, ERROR
}
suspend fun updateYoutubeDL(c: String? = null) : YTDLPUpdateResponse =
withContext(Dispatchers.IO){
val sharedPreferences =
PreferenceManager.getDefaultSharedPreferences(context)
if (updatingYTDL) {
YTDLPUpdateResponse(YTDLPUpdateStatus.PROCESSING)
}
updatingYTDL = true
val channel = if (c.isNullOrBlank()) sharedPreferences.getString("ytdlp_source", "stable") else c
when(channel) {
"stable", "nightly", "master" -> {
val res = YoutubeDL.updateYoutubeDL(context, channelMap[channel]!!)
if (res != YoutubeDL.UpdateStatus.DONE) {
YTDLPUpdateResponse(YTDLPUpdateStatus.ALREADY_UP_TO_DATE)
}else {
val version = YoutubeDL.version(context)
YTDLPUpdateResponse(YTDLPUpdateStatus.DONE, "Updated yt-dlp to ${channel}@${version}")
}
}
else -> {
val request = YoutubeDLRequest(emptyList())
request.addOption("--update-to", "${channel}@latest")
val res = YoutubeDL.getInstance().execute(request)
val out = res.out.split(System.getProperty("line.separator")).last { it.isNotBlank() }
if (out.contains("ERROR")) YTDLPUpdateResponse(YTDLPUpdateStatus.ERROR, out)
if (out.contains("yt-dlp is up to date")) YTDLPUpdateResponse(YTDLPUpdateStatus.ALREADY_UP_TO_DATE, out)
else YTDLPUpdateResponse(YTDLPUpdateStatus.DONE, out)
}
}
}
companion object {
var updatingYTDL = false
var updatingApp = false
}
}