finish sample

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

@ -9,7 +9,8 @@ permissions:
on: on:
push: push:
branches: [ "main" ] branches:
- "**"
jobs: jobs:
build: build:

@ -109,7 +109,7 @@ object RuntimeManager {
} }
ENV_PYTHONHOME = if (pythonLocation.isDownloaded) { ENV_PYTHONHOME = if (pythonLocation.isDownloaded) {
pythonLocation.ldDir.parent pythonLocation.ldDir.absolutePath + "/usr"
} else { } else {
pythonLocation.ldDir.absolutePath + "/usr" pythonLocation.ldDir.absolutePath + "/usr"
} }

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

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

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

@ -5,9 +5,10 @@ import android.os.Build
import androidx.core.content.edit import androidx.core.content.edit
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.anggrayudi.storage.file.toRawFile
import com.deniscerri.ytdl.core.RuntimeManager import com.deniscerri.ytdl.core.RuntimeManager
import com.deniscerri.ytdl.core.ZipUtils import com.deniscerri.ytdl.core.ZipUtils
import com.deniscerri.ytdl.database.models.GithubReleaseAsset
import com.google.gson.annotations.SerializedName
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -17,15 +18,15 @@ import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.apache.commons.io.FileUtils import org.apache.commons.io.FileUtils
import org.json.JSONObject
import java.io.File import java.io.File
import java.time.LocalDate
import kotlin.coroutines.cancellation.CancellationException import kotlin.coroutines.cancellation.CancellationException
abstract class PluginBase { abstract class PluginBase {
protected abstract val executableName: String // e.g., "ffmpeg" protected abstract val executableName: String // e.g., "ffmpeg"
protected abstract val pluginFolderName: 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 bundledZipName: String // e.g., "libffmpeg.zip.so"
protected abstract val packageGithubRepo: String // e.g deniscerri/ytdlnis-plugins protected abstract val githubRepo: String // e.g deniscerri/ytdlnis-plugins
protected abstract val githubPackageName: String // e.g ffmpeg protected abstract val githubPackageName: String // e.g ffmpeg
fun getInstance(): PluginBase = this fun getInstance(): PluginBase = this
@ -34,11 +35,16 @@ abstract class PluginBase {
@Serializable @Serializable
data class PluginRelease( data class PluginRelease(
@SerialName("name") @SerializedName(value = "tag_name")
var version: String, var tag_name: String,
@SerialName("created_at") @SerialName("published_at")
val createdAt: String, val published_at: String,
var downloadUrl: String = "", @SerializedName(value = "assets")
var assets: List<GithubReleaseAsset>,
@SerializedName(value = "body")
val body: String,
var version: String = "",
var downloadSize: Long = 0,
var isInstalled: Boolean = false, var isInstalled: Boolean = false,
var isBundled: Boolean = false, var isBundled: Boolean = false,
var isDownloading: Boolean = false, var isDownloading: Boolean = false,
@ -143,7 +149,7 @@ abstract class PluginBase {
//download //download
val request = Request.Builder() val request = Request.Builder()
.url(release.downloadUrl) .url(release.assets.first().browser_download_url)
.build() .build()
val response = sharedClient.newCall(request).execute() val response = sharedClient.newCall(request).execute()
@ -242,12 +248,11 @@ abstract class PluginBase {
} }
} }
suspend fun getReleases() : List<PluginRelease> { suspend fun getReleases() : Result<List<PluginRelease>> {
if (packageGithubRepo.isEmpty()) return listOf() if (githubRepo.isEmpty()) return Result.success(listOf())
val request = Request.Builder() val request = Request.Builder()
.url("https://api.github.com/users/${packageGithubRepo.split("/").first()}/packages/maven/${githubPackageName}/versions") .url("https://api.github.com/repos/${githubRepo}/releases")
.header("Accept", "application/vnd.github+json")
.build() .build()
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
@ -258,18 +263,25 @@ abstract class PluginBase {
if (response.isSuccessful && jsonString.isNotEmpty()) { if (response.isSuccessful && jsonString.isNotEmpty()) {
val supportedArch = getArchSuffix() val supportedArch = getArchSuffix()
json.decodeFromString<List<PluginRelease>>(jsonString) val releases = json.decodeFromString<List<PluginRelease>>(jsonString)
.filter {
it.tag_name.contains(githubPackageName)
}
.onEach { .onEach {
it.version = it.version // nodejs-1.0.0-arm64-v8a
it.version = it.tag_name.split("-")[1]
it.assets = it.assets.filter { a -> a.name.contains(supportedArch) }
it.downloadSize = it.assets.first().size
it.isInstalled = downloadedVersion == "v${it.version}" it.isInstalled = downloadedVersion == "v${it.version}"
it.isBundled = bundledVersion == "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"
} }
Result.success(releases)
} else { } else {
emptyList() Result.failure(Throwable(JSONObject(jsonString).getString("message")))
} }
} catch (e: Exception) { } catch (e: Exception) {
listOf() Result.failure(e)
} }
} }
} }

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

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

@ -1,6 +1,7 @@
package com.deniscerri.ytdl.database.models package com.deniscerri.ytdl.database.models
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.serialization.Serializable
import java.util.Date import java.util.Date
data class GithubRelease( data class GithubRelease(
@ -17,9 +18,12 @@ data class GithubRelease(
) )
@Serializable
data class GithubReleaseAsset( data class GithubReleaseAsset(
@SerializedName(value = "name") @SerializedName(value = "name")
var name: String, var name: String,
@SerializedName(value = "browser_download_url") @SerializedName(value = "browser_download_url")
var browser_download_url: String var browser_download_url: String,
@SerializedName(value = "size")
var size: Long
) )

@ -52,11 +52,12 @@ class PluginReleaseAdapter(onItemClickListener: OnItemClickListener, activity: A
val item = getItem(position) ?: return val item = getItem(position) ?: return
val card = holder.itemView val card = holder.itemView
card.findViewById<TextView>(R.id.title).text = "v${item.version}" val version = "v${item.version}"
card.findViewById<TextView>(R.id.title).text = version
val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault()) val parser = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.getDefault())
parser.timeZone = TimeZone.getTimeZone("UTC") parser.timeZone = TimeZone.getTimeZone("UTC")
val date = parser.parse(item.createdAt) val date = parser.parse(item.published_at)
val parser2 = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault()) val parser2 = SimpleDateFormat(DateFormat.getBestDateTimePattern(Locale.getDefault(), "ddMMMyyyy - HHmm"), Locale.getDefault())
card.findViewById<TextView>(R.id.createdAt).text = parser2.format(date.time) card.findViewById<TextView>(R.id.createdAt).text = parser2.format(date.time)

@ -2,11 +2,20 @@ package com.deniscerri.ytdl.ui.more.settings.updating
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.AlertDialog
import android.app.DownloadManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.text.method.LinkMovementMethod
import android.util.DisplayMetrics import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -34,15 +43,20 @@ import com.deniscerri.ytdl.ui.adapter.PluginReleaseAdapter
import com.deniscerri.ytdl.ui.adapter.PluginsAdapter import com.deniscerri.ytdl.ui.adapter.PluginsAdapter
import com.deniscerri.ytdl.ui.more.settings.SettingsActivity import com.deniscerri.ytdl.ui.more.settings.SettingsActivity
import com.deniscerri.ytdl.util.Extensions.enableFastScroll import com.deniscerri.ytdl.util.Extensions.enableFastScroll
import com.deniscerri.ytdl.util.FileUtil
import com.deniscerri.ytdl.util.UiUtil import com.deniscerri.ytdl.util.UiUtil
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.progressindicator.CircularProgressIndicator import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import junit.runner.Version import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.io.File
class PluginsFragment : Fragment(), PluginsAdapter.OnItemClickListener, PluginReleaseAdapter.OnItemClickListener { class PluginsFragment : Fragment(), PluginsAdapter.OnItemClickListener, PluginReleaseAdapter.OnItemClickListener {
@ -119,7 +133,15 @@ class PluginsFragment : Fragment(), PluginsAdapter.OnItemClickListener, PluginRe
lifecycleScope.launch { lifecycleScope.launch {
val instance = tmpItem!!.getInstance() val instance = tmpItem!!.getInstance()
instance.getReleases().apply { instance.getReleases().apply {
pluginReleases = this.toMutableList() this.onFailure {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
Toast.makeText(requireContext(), it.message ?: getString(R.string.errored), Toast.LENGTH_SHORT).show()
}
}
}
pluginReleases = this.getOrElse { listOf() }
releaseAdapter.submitList(pluginReleases) releaseAdapter.submitList(pluginReleases)
releaseRecyclerView.isVisible = pluginReleases.isNotEmpty() releaseRecyclerView.isVisible = pluginReleases.isNotEmpty()
loader?.isVisible = false loader?.isVisible = false
@ -204,52 +226,75 @@ class PluginsFragment : Fragment(), PluginsAdapter.OnItemClickListener, PluginRe
} }
override fun onDownloadReleaseClick(item: PluginBase.PluginRelease) { override fun onDownloadReleaseClick(item: PluginBase.PluginRelease) {
val instance = tmpItem!!.getInstance() var positiveButton: Button? = null
val idx = pluginReleases.indexOf(item) val updateDialog = MaterialAlertDialogBuilder(requireContext())
val list = pluginReleases.toMutableList() .setTitle("${item.tag_name} (${FileUtil.convertFileSize(item.downloadSize)})")
list[idx] = list[idx].copy(isDownloading = true) .setMessage(item.body)
releaseAdapter.submitList(list.toList()) .setIcon(R.drawable.ic_update_app)
.setNegativeButton(requireContext().getString(R.string.cancel)) { _: DialogInterface?, _: Int ->
tmpDownloadJob?.cancel()
}
.setPositiveButton(requireContext().getString(R.string.download), null)
val view = updateDialog.show()
val textView = view.findViewById<TextView>(android.R.id.message)
textView!!.movementMethod = LinkMovementMethod.getInstance()
val mw = Markwon.builder(requireContext()).usePlugin(object: AbstractMarkwonPlugin() {
tmpDownloadJob = lifecycleScope.launch { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
val fileResp = instance.downloadRelease(requireContext(), item) { progress -> builder.linkResolver { view, link ->
lifecycleScope.launch { val browserIntent = Intent(Intent.ACTION_VIEW, Uri.parse(link))
withContext(Dispatchers.Main) { requireContext().startActivity(browserIntent)
list[idx] = list[idx].copy(downloadProgress = progress)
releaseAdapter.submitList(list.toList())
//releaseAdapter.notifyItemChanged(idx, progress.toString())
}
} }
} }
}).build()
mw.setMarkdown(textView, item.body)
positiveButton = view.getButton(AlertDialog.BUTTON_POSITIVE)
positiveButton?.setOnClickListener {
positiveButton.isEnabled = false
positiveButton.text = "0%"
fileResp.onFailure { tmpDownloadJob = lifecycleScope.launch {
lifecycleScope.launch { val instance = tmpItem!!.getInstance()
withContext(Dispatchers.Main) { val fileResp = instance.downloadRelease(requireContext(), item) { progress ->
bottomSheet?.dismiss() lifecycleScope.launch {
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show() withContext(Dispatchers.Main) {
positiveButton?.text = "$progress%"
}
} }
} }
}
fileResp.onSuccess { file -> fileResp.onFailure {
val resp = instance.installFromZip(requireContext(), file, "v${item.version}")
resp.onFailure {
lifecycleScope.launch { lifecycleScope.launch {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
bottomSheet?.dismiss() view.dismiss()
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show() Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show()
} }
} }
} }
resp.onSuccess { fileResp.onSuccess { file ->
lifecycleScope.launch { val resp = instance.installFromZip(requireContext(), file, "v${item.version}")
withContext(Dispatchers.Main) { resp.onFailure {
bottomSheet?.dismiss() lifecycleScope.launch {
listAdapter.notifyDataSetChanged() withContext(Dispatchers.Main) {
RuntimeManager.reInit(requireContext()) bottomSheet?.dismiss()
view.dismiss()
Snackbar.make(requireView(), it.message ?: getString(R.string.errored), Snackbar.LENGTH_LONG).show()
}
}
}
resp.onSuccess {
lifecycleScope.launch {
withContext(Dispatchers.Main) {
bottomSheet?.dismiss()
view.dismiss()
listAdapter.notifyDataSetChanged()
RuntimeManager.reInit(requireContext())
}
} }
} }
} }
} }
} }
} }
} }

@ -92,9 +92,13 @@ class UpdateSettingsFragment : BaseSettingsFragment() {
false false
} }
findPreference<Preference>("plugins")?.setOnPreferenceClickListener { findPreference<Preference>("plugins")?.apply {
findNavController().navigate(R.id.pluginsFragment) summary = "Python, FFmpeg, Aria2c, NodeJS"
false
setOnPreferenceClickListener {
findNavController().navigate(R.id.pluginsFragment)
false
}
} }

@ -70,15 +70,16 @@
app:summary="@string/update_app_beta_summary" app:summary="@string/update_app_beta_summary"
app:title="@string/update_app_beta" /> app:title="@string/update_app_beta" />
<Preference
app:icon="@drawable/ic_code"
app:key="plugins"
app:title="@string/plugins" />
<Preference <Preference
app:icon="@drawable/ic_chapters" app:icon="@drawable/ic_chapters"
app:key="changelog" app:key="changelog"
app:title="@string/changelog" /> app:title="@string/changelog" />
<Preference
app:icon="@drawable/ic_code"
app:key="plugins"
app:title="@string/plugins" />
</PreferenceCategory> </PreferenceCategory>

Loading…
Cancel
Save