wipp todo github releaseas in ytdlnis plugins repo

pull/1112/head
deniscerri 5 months ago
parent ab524a9509
commit 32a1c85d02
No known key found for this signature in database
GPG Key ID: 95C43D517D830350

@ -160,7 +160,7 @@ dependencies {
implementation "io.github.junkfood02.youtubedl-android:aria2c:0.18.1"
// implementation "io.github.junkfood02.youtubedl-android:library:0.17.4"
implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.18.1"
implementation "io.github.junkfood02.youtubedl-android:ffmpeg:0.17.2"
// implementation "io.github.junkfood02.youtubedl-android:aria2c:0.17.2"
implementation "androidx.appcompat:appcompat:1.7.1"

@ -198,6 +198,15 @@ object RuntimeManager {
request.addOption("--js-runtimes", "quickjs:${quickJsLocation.executable.absolutePath}")
}
if (request.buildCommand().contains("libaria2c.so")) {
request
.addOption("--external-downloader-args", "aria2c:--summary-interval=1")
.addOption(
"--external-downloader-args",
"aria2c:--ca-certificate=$ENV_SSL_CERT_FILE"
)
}
if (!usingCacheDir) {
request.addOption("--no-cache-dir")
}

@ -5,5 +5,6 @@ object Aria2c : PluginBase() {
override val pluginFolderName: String get() = "aria2c"
override val bundledZipName: String get() = "libaria2c.zip.so"
override val bundledVersion: String get() = "v1.37.0"
override val githubRepositoryPackageURL: String get() = ""
override val packageGithubRepo: String get() = ""
override val githubPackageName: String get() = ""
}

@ -4,6 +4,7 @@ object FFmpeg : PluginBase() {
override val executableName: String get() = "ffmpeg"
override val pluginFolderName: String get() = "ffmpeg"
override val bundledZipName: String get() = "libffmpeg.zip.so"
override val bundledVersion: String get() = "v7.1.1"
override val githubRepositoryPackageURL: String get() = ""
override val bundledVersion: String get() = "v7.0.1"
override val packageGithubRepo: String get() = "deniscerri/ytdlnis-packages"
override val githubPackageName: String get() = "ffmpeg"
}

@ -4,6 +4,7 @@ object NodeJS : PluginBase() {
override val executableName: String get() = "node"
override val pluginFolderName: String get() = "node"
override val bundledZipName: String get() = "libnode.zip.so"
override val bundledVersion: String get() = "v25.3.0"
override val githubRepositoryPackageURL: String get() = ""
override val bundledVersion: String get() = ""
override val packageGithubRepo: String get() = "deniscerri/ytdlnis-plugins"
override val githubPackageName: String get() = "nodejs"
}

@ -9,6 +9,7 @@ import com.anggrayudi.storage.file.toRawFile
import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.core.ZipUtils
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@ -18,26 +19,30 @@ import okhttp3.Request
import org.apache.commons.io.FileUtils
import java.io.File
import java.time.LocalDate
import kotlin.coroutines.cancellation.CancellationException
abstract class PluginBase {
protected abstract val executableName: String // e.g., "ffmpeg"
protected abstract val pluginFolderName: String // e.g., "ffmpeg"
protected abstract val bundledZipName: String // e.g., "libffmpeg.zip.so"
protected abstract val packageGithubRepo: String // e.g deniscerri/ytdlnis-plugins
protected abstract val githubPackageName: String // e.g ffmpeg
fun getInstance(): PluginBase = this
abstract val bundledVersion: String?
var downloadedVersion: String? = null
protected abstract val githubRepositoryPackageURL: String // github repository package url
@Serializable
data class PluginRelease(
@SerialName("name")
val version: String,
@SerialName("package_html_url")
val downloadUrl: String,
var version: String,
@SerialName("created_at")
val createdAt: String,
var isInstalled: Boolean
var downloadUrl: String = "",
var isInstalled: Boolean = false,
var isBundled: Boolean = false,
var isDownloading: Boolean = false,
var downloadProgress: Int = 0
)
data class PluginLocation(
@ -131,19 +136,20 @@ abstract class PluginBase {
)
}
suspend fun downloadRelease(context: Context, release: PluginRelease, onProgress: (Int) -> Unit) : DocumentFile? {
val runtimeDir = getDownloadedDir(context)
FileUtils.deleteQuietly(runtimeDir)
runtimeDir.mkdirs()
suspend fun downloadRelease(context: Context, release: PluginRelease, onProgress: (Int) -> Unit) : Result<DocumentFile> {
return withContext(Dispatchers.IO) {
try {
val tempZipFile = File(context.cacheDir, "${pluginFolderName}_tmp.zip")
//download
val request = Request.Builder().url(release.downloadUrl).build()
val request = Request.Builder()
.url(release.downloadUrl)
.build()
val response = sharedClient.newCall(request).execute()
if (!response.isSuccessful) return@withContext null
if (!response.isSuccessful) {
return@withContext Result.failure(Throwable(response.body.string()))
}
val body = response.body
val totalBytes = body.contentLength()
@ -155,6 +161,8 @@ abstract class PluginBase {
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
@ -165,9 +173,9 @@ abstract class PluginBase {
}
}
DocumentFile.fromFile(tempZipFile)
Result.success(DocumentFile.fromFile(tempZipFile))
} catch (e: Exception) {
null
Result.failure(e)
}
}
}
@ -175,8 +183,9 @@ abstract class PluginBase {
fun installFromZip(context: Context, zipFile: DocumentFile, versionTag: String? = null) : Result<String> {
return kotlin.runCatching {
val runtimeDir = getDownloadedDir(context)
runtimeDir.deleteRecursively()
runtimeDir.createNewFile()
FileUtils.deleteQuietly(runtimeDir)
runtimeDir.mkdirs()
// 3. Unzip the main Bundle (contains libnode.so and libnode.zip.so)
context.contentResolver.openInputStream(zipFile.uri).use { inputStream ->
ZipUtils.unzip(inputStream, runtimeDir)
@ -234,20 +243,10 @@ abstract class PluginBase {
}
suspend fun getReleases() : List<PluginRelease> {
//
// return listOf(
// PluginRelease(
// version = "1.0",
// downloadUrl = "http://192.168.1.144:8080/x86_64/x86_64.zip",
// createdAt = LocalDate.now().toString(),
// isInstalled = downloadedVersion == "1.0"
// )
// )
if (githubRepositoryPackageURL.isEmpty()) return listOf()
if (packageGithubRepo.isEmpty()) return listOf()
val request = Request.Builder()
.url(githubRepositoryPackageURL)
.url("https://api.github.com/users/${packageGithubRepo.split("/").first()}/packages/maven/${githubPackageName}/versions")
.header("Accept", "application/vnd.github+json")
.build()
@ -260,9 +259,11 @@ abstract class PluginBase {
val supportedArch = getArchSuffix()
json.decodeFromString<List<PluginRelease>>(jsonString)
.filter { it.version.contains(supportedArch) }
.onEach {
it.isInstalled = downloadedVersion == it.version
it.version = it.version
it.isInstalled = downloadedVersion == "v${it.version}"
it.isBundled = bundledVersion == "v${it.version}"
it.downloadUrl = "https://maven.pkg.github.com/${packageGithubRepo}/${githubPackageName}/${it.version}/${githubPackageName}-${it.version}-${supportedArch}.zip"
}
} else {
emptyList()

@ -4,6 +4,7 @@ object Python : PluginBase() {
override val executableName: String get() = "python"
override val pluginFolderName: String get() = "python"
override val bundledZipName: String get() = "libpython.zip.so"
override val bundledVersion: String get() = "v3.12.11"
override val githubRepositoryPackageURL: String get() = ""
override val bundledVersion: String get() = "v3.12"
override val packageGithubRepo: String get() = "deniscerri/ytdlnis-packages"
override val githubPackageName: String get() = "python"
}

@ -5,5 +5,6 @@ object QuickJS : PluginBase() {
override val pluginFolderName: String get() = "quickjs"
override val bundledZipName: String get() = "libqjs.zip.so"
override val bundledVersion: String get() = "2025-04-26"
override val githubRepositoryPackageURL: String get() = ""
override val packageGithubRepo: String get() = ""
override val githubPackageName: String get() = ""
}

@ -1,37 +1,24 @@
package com.deniscerri.ytdl.ui.adapter
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.format.DateFormat
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdl.R
import com.deniscerri.ytdl.core.plugins.PluginBase
import com.deniscerri.ytdl.core.plugins.PluginBase.PluginRelease
import com.deniscerri.ytdl.database.models.DownloadItem
import com.deniscerri.ytdl.database.models.GithubRelease
import com.deniscerri.ytdl.database.models.PluginItem
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import java.sql.Date
import com.google.android.material.progressindicator.CircularProgressIndicator
import java.text.SimpleDateFormat
import java.time.Instant
import java.util.Locale
import java.util.TimeZone
class PluginReleaseAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<PluginRelease?, PluginReleaseAdapter.ViewHolder>(AsyncDifferConfig.Builder(
DIFF_CALLBACK
@ -65,26 +52,58 @@ class PluginReleaseAdapter(onItemClickListener: OnItemClickListener, activity: A
val item = getItem(position) ?: return
val card = holder.itemView
card.findViewById<TextView>(R.id.title).text = item.version
card.findViewById<TextView>(R.id.createdAt).text = item.createdAt
card.findViewById<TextView>(R.id.title).text = "v${item.version}"
val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
parser.timeZone = TimeZone.getTimeZone("UTC")
val date = parser.parse(item.createdAt)
val parser2 = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault())
card.findViewById<TextView>(R.id.createdAt).text = parser2.format(date.time)
val actionBtn = card.findViewById<MaterialButton>(R.id.actionBtn)
if (item.isInstalled) {
actionBtn.isVisible = !item.isBundled && item.downloadProgress < 100
val progress = card.findViewById<CircularProgressIndicator>(R.id.progress)
progress.isVisible = item.isDownloading
if (item.isDownloading) {
actionBtn.setIconResource(R.drawable.ic_cancel)
} else if (item.isInstalled) {
actionBtn.setIconResource(R.drawable.ic_baseline_delete_outline_24)
} else {
actionBtn.setIconResource(R.drawable.ic_down)
}
card.setOnClickListener {
if (item.isInstalled) {
onItemClickListener.onDeleteReleaseClick(item)
} else {
onItemClickListener.onDownloadReleaseClick(item)
actionBtn.setOnClickListener {
if (!item.isBundled) {
if (item.isDownloading && item.downloadProgress < 100) {
onItemClickListener.onCancelDownloadReleaseClick(item)
} else if (item.isInstalled) {
onItemClickListener.onDeleteReleaseClick(item)
} else {
onItemClickListener.onDownloadReleaseClick(item)
}
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val card = holder.itemView
val progress = card.findViewById<CircularProgressIndicator>(R.id.progress)
val progressValue = payloads.last().toString().toInt()
progress.progress = progressValue
progress.isIndeterminate = progressValue == 0 || progressValue == 100
}
}
interface OnItemClickListener {
fun onCancelDownloadReleaseClick(item: PluginRelease)
fun onDownloadReleaseClick(item: PluginRelease)
fun onDeleteReleaseClick(item: PluginRelease)
}
@ -96,7 +115,9 @@ class PluginReleaseAdapter(onItemClickListener: OnItemClickListener, activity: A
}
override fun areContentsTheSame(oldItem: PluginRelease, newItem: PluginRelease): Boolean {
return oldItem.isInstalled == newItem.isInstalled
return oldItem.isInstalled == newItem.isInstalled &&
oldItem.isDownloading == newItem.isDownloading &&
oldItem.downloadProgress == newItem.downloadProgress
}
}
}

@ -6,6 +6,7 @@ import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -39,6 +40,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.snackbar.Snackbar
import junit.runner.Version
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -52,6 +54,7 @@ class PluginsFragment : Fragment(), PluginsAdapter.OnItemClickListener, PluginRe
private lateinit var preferences: SharedPreferences
private var tmpItem: PluginItem? = null
private var tmpDownloadJob: Job? = null
private var plugins: List<PluginItem> = mutableListOf()
private var pluginReleases: List<PluginBase.PluginRelease> = mutableListOf()
@ -192,22 +195,61 @@ class PluginsFragment : Fragment(), PluginsAdapter.OnItemClickListener, PluginRe
deleteDownloadedVersion(item, currentVersion)
}
override fun onDownloadReleaseClick(item: PluginBase.PluginRelease) {
lifecycleScope.launch {
val instance = tmpItem!!.getInstance()
override fun onCancelDownloadReleaseClick(item: PluginBase.PluginRelease) {
tmpDownloadJob?.cancel()
val idx = pluginReleases.indexOf(item)
val list = pluginReleases.toMutableList()
list[idx] = list[idx].copy(isDownloading = false)
releaseAdapter.submitList(list.toList())
}
val file = instance.downloadRelease(requireContext(), item) {
override fun onDownloadReleaseClick(item: PluginBase.PluginRelease) {
val instance = tmpItem!!.getInstance()
val idx = pluginReleases.indexOf(item)
val list = pluginReleases.toMutableList()
list[idx] = list[idx].copy(isDownloading = true)
releaseAdapter.submitList(list.toList())
tmpDownloadJob = lifecycleScope.launch {
val fileResp = instance.downloadRelease(requireContext(), item) { progress ->
lifecycleScope.launch {
withContext(Dispatchers.Main) {
list[idx] = list[idx].copy(downloadProgress = progress)
releaseAdapter.submitList(list.toList())
//releaseAdapter.notifyItemChanged(idx, progress.toString())
}
}
}
val resp = instance.installFromZip(requireContext(), file!!, item.version)
resp.onFailure {
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show()
fileResp.onFailure {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
bottomSheet?.dismiss()
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show()
}
}
}
resp.onSuccess {
bottomSheet?.dismiss()
listAdapter.notifyDataSetChanged()
RuntimeManager.reInit(requireContext())
fileResp.onSuccess { file ->
val resp = instance.installFromZip(requireContext(), file, "v${item.version}")
resp.onFailure {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
bottomSheet?.dismiss()
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show()
}
}
}
resp.onSuccess {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
bottomSheet?.dismiss()
listAdapter.notifyDataSetChanged()
RuntimeManager.reInit(requireContext())
}
}
}
}
}
}
}

@ -883,9 +883,9 @@ class YTDLPUtil(private val context: Context, private val commandTemplateDao: Co
val aria2 = sharedPreferences.getBoolean("aria2", false)
if (aria2) {
request.addOption("--downloader", "libaria2c.so")
ytDlRequest.addOption("--downloader", "libaria2c.so")
//request.addOption("--external-downloader-args", "aria2c:\"--summary-interval=1\"")
request.addOption("--no-check-certificates")
ytDlRequest.addOption("--no-check-certificates")
//request.addOption("--external-downloader-args", "aria2c:\"--check-certificate=false\"")
}

@ -91,7 +91,7 @@
android:clickable="false"
android:contentDescription="@string/preferred_download_type"
android:minHeight="0dp"
android:padding="0dp"
android:paddingHorizontal="10dp"
app:cornerRadius="10dp"
app:icon="@drawable/ic_down"
app:iconTint="?attr/colorAccent"
@ -99,6 +99,16 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:indeterminate="true"
android:visibility="gone"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

Loading…
Cancel
Save