From cb19cb4685a5f7a093d5687fc30c603c4049d3d1 Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sun, 1 Feb 2026 21:26:18 +0100 Subject: [PATCH] success build nodejs, todo downloadable plugins --- .gitignore | 3 +- .../deniscerri/ytdl/core/RuntimeManager.kt | 5 + .../ytdl/core/runtimes/BaseRuntime.kt | 103 +++++++++++++++--- .../deniscerri/ytdl/core/runtimes/NodeJS.kt | 2 +- .../updating/UpdateSettingsFragment.kt | 56 ++++++++++ .../res/drawable/outline_data_object_24.xml | 5 + app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/updating_preferences.xml | 29 ++++- 8 files changed, 186 insertions(+), 19 deletions(-) create mode 100644 app/src/main/res/drawable/outline_data_object_24.xml diff --git a/.gitignore b/.gitignore index 68fa944b..1dfaeb1d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ /captures .externalNativeBuild /benchmark -*.png \ No newline at end of file +*.png +app/src/main/jniLibs \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/core/RuntimeManager.kt b/app/src/main/java/com/deniscerri/ytdl/core/RuntimeManager.kt index 2d5865dd..2e8e0e3a 100644 --- a/app/src/main/java/com/deniscerri/ytdl/core/RuntimeManager.kt +++ b/app/src/main/java/com/deniscerri/ytdl/core/RuntimeManager.kt @@ -103,6 +103,11 @@ object RuntimeManager { initialized = true } + fun reInit(context: Context) { + initialized = false + init(context) + } + private fun assertInit() { check(initialized) { "instance not initialized" } } diff --git a/app/src/main/java/com/deniscerri/ytdl/core/runtimes/BaseRuntime.kt b/app/src/main/java/com/deniscerri/ytdl/core/runtimes/BaseRuntime.kt index 9016144c..3d2923e1 100644 --- a/app/src/main/java/com/deniscerri/ytdl/core/runtimes/BaseRuntime.kt +++ b/app/src/main/java/com/deniscerri/ytdl/core/runtimes/BaseRuntime.kt @@ -9,6 +9,7 @@ import com.deniscerri.ytdl.core.ZipUtils import org.apache.commons.io.FileUtils import org.json.JSONObject import java.io.File +import java.net.HttpURLConnection import java.net.URL abstract class BaseRuntime { @@ -51,30 +52,92 @@ abstract class BaseRuntime { } } - fun downloadAndInstall(context: Context, zipUrl: String, versionTag: String) { - val downloadDir = File(context.noBackupFilesDir, "runtimes/$runtimeName") - FileUtils.deleteQuietly(downloadDir) - downloadDir.mkdirs() + fun downloadAndInstall(context: Context, zipUrl: String, versionTag: String, onProgress: (Long, Long) -> Unit) : Boolean { + // 1. Clean up old installation + val runtimeDir = File(context.noBackupFilesDir, "runtimes/$runtimeName") + FileUtils.deleteQuietly(runtimeDir) + runtimeDir.mkdirs() - val tempZip = File(context.cacheDir, "${runtimeName}_tmp.zip") + var success = false + + val tempZip = File(context.cacheDir, "${runtimeName}_bundle_tmp.zip") try { - URL(zipUrl).openStream().use { input -> - tempZip.outputStream().use { output -> input.copyTo(output) } + // 2. Download the bundle (handles GitHub 302 redirects) + downloadWithRedirects(zipUrl, tempZip, onProgress) + // 3. Unzip the main Bundle (contains libnode.so and libnode.zip.so) + ZipUtils.unzip(tempZip, runtimeDir) + // 4. Handle the Bootstrap Zip (Double Unzip) + // Look for any .zip.so file in the extracted directory + runtimeDir.listFiles()?.forEach { file -> + if (file.name.endsWith(".zip.so")) { + ZipUtils.unzip(file, runtimeDir) + file.delete() // Remove the internal zip to save space + } } + // 5. Global Permission Fix + // Scan for all files in any 'bin' folder and make them executable + applyExecutablePermissions(runtimeDir) + // 6. Save installation state + saveState(context, versionTag) + success = true + } catch (ex: Exception) { + success = false + } + if (tempZip.exists()) tempZip.delete() + return success + } + + private fun downloadWithRedirects(url: String, dest: File, onProgress: (Long, Long) -> Unit) { + var currentUrl = url + var connection: HttpURLConnection + var redirectCount = 0 - ZipUtils.unzip(tempZip, downloadDir) + while (true) { + connection = URL(currentUrl).openConnection() as HttpURLConnection + connection.instanceFollowRedirects = true + + val status = connection.responseCode + if (status == HttpURLConnection.HTTP_MOVED_TEMP || + status == HttpURLConnection.HTTP_MOVED_PERM || + status == HttpURLConnection.HTTP_SEE_OTHER) { + + currentUrl = connection.getHeaderField("Location") + redirectCount++ + if (redirectCount > 5) throw Exception("Too many redirects") + continue + } + break + } - // Recursive function to set executable permissions on everything in bin/ - File(downloadDir, "bin").listFiles()?.forEach { - it.setExecutable(true, false) + val fileSize = connection.contentLength.toLong() + var bytesCopied = 0L + val buffer = ByteArray(8192) + + connection.inputStream.use { input -> + dest.outputStream().use { output -> + var bytes = input.read(buffer) + while (bytes >= 0) { + output.write(buffer, 0, bytes) + bytesCopied += bytes + // Trigger the callback + onProgress?.invoke(bytesCopied, fileSize) + bytes = input.read(buffer) + } } + } + } - PreferenceManager.getDefaultSharedPreferences(context).edit { - putBoolean(installedKey, true) - putString(versionKey, versionTag) + private fun applyExecutablePermissions(file: File) { + if (file.isDirectory) { + // Check if this folder is a 'bin' folder + if (file.name == "bin") { + file.listFiles()?.forEach { it.setExecutable(true, false) } } - } finally { - tempZip.delete() + // Recurse into subdirectories + file.listFiles()?.forEach { applyExecutablePermissions(it) } + } else if (file.name == "libnode.so") { + // Specifically ensure our renamed main binary is executable + file.setExecutable(true, false) } } @@ -87,6 +150,14 @@ abstract class BaseRuntime { } } + private fun saveState(context: Context, version: String) { + val prefs = PreferenceManager.getDefaultSharedPreferences(context) + prefs.edit(commit = true) { + putBoolean(installedKey, true) + putString(versionKey, version) + } + } + open fun checkForUpdates(context: Context): RuntimeUpdateInfo? { return try { val response = URL(manifestURL).readText() diff --git a/app/src/main/java/com/deniscerri/ytdl/core/runtimes/NodeJS.kt b/app/src/main/java/com/deniscerri/ytdl/core/runtimes/NodeJS.kt index 1f8520ad..f71b0301 100644 --- a/app/src/main/java/com/deniscerri/ytdl/core/runtimes/NodeJS.kt +++ b/app/src/main/java/com/deniscerri/ytdl/core/runtimes/NodeJS.kt @@ -2,7 +2,7 @@ package com.deniscerri.ytdl.core.runtimes object NodeJS : BaseRuntime() { override val runtimeName: String get() = "node" - override val bundledZipName: String get() = "" + override val bundledZipName: String get() = "libnode.zip.so" override val manifestURL: String get() = "" @JvmStatic diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/UpdateSettingsFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/UpdateSettingsFragment.kt index 700584d4..f0c99b6f 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/UpdateSettingsFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/updating/UpdateSettingsFragment.kt @@ -11,6 +11,12 @@ import androidx.preference.Preference import androidx.preference.PreferenceManager import com.deniscerri.ytdl.BuildConfig import com.deniscerri.ytdl.R +import com.deniscerri.ytdl.core.RuntimeManager +import com.deniscerri.ytdl.core.runtimes.Aria2c +import com.deniscerri.ytdl.core.runtimes.BaseRuntime +import com.deniscerri.ytdl.core.runtimes.FFmpeg +import com.deniscerri.ytdl.core.runtimes.NodeJS +import com.deniscerri.ytdl.core.runtimes.Python import com.deniscerri.ytdl.database.viewmodel.SettingsViewModel import com.deniscerri.ytdl.database.viewmodel.YTDLPViewModel import com.deniscerri.ytdl.ui.more.settings.BaseSettingsFragment @@ -110,6 +116,21 @@ class UpdateSettingsFragment : BaseSettingsFragment() { true } + //packages + findPreference("package_python")?.apply { + val instance = Python.getInstance() + summary = instance.getVersion(requireContext()) + } + findPreference("package_ffmpeg")?.apply { + val instance = FFmpeg.getInstance() + summary = instance.getVersion(requireContext()) + } + findPreference("package_aria2c")?.apply { + val instance = Aria2c.getInstance() + summary = instance.getVersion(requireContext()) + } + + handlePackage(NodeJS.getInstance(), findPreference("package_nodejs")) findPreference("reset_preferences")?.setOnPreferenceClickListener { UiUtil.showGenericConfirmDialog(requireContext(), getString(R.string.reset), getString(R.string.reset_preferences_in_screen)) { @@ -185,4 +206,39 @@ class UpdateSettingsFragment : BaseSettingsFragment() { } + private fun handlePackage(instance: BaseRuntime, preference: Preference?) { + //TODO REWRITE THIS LOL + preference?.apply { + summary = instance.getVersion(requireContext()) + onPreferenceClickListener = Preference.OnPreferenceClickListener { + summary = getString(R.string.loading) + val response = instance.checkForUpdates(requireContext()) + if (response == null) { + Snackbar.make(requireView(), getString(R.string.failed_download), Snackbar.LENGTH_SHORT).show() + } else { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + val success = instance.downloadAndInstall(requireContext(), response.downloadUrl, response.version) { progress, total -> + val downloadedMB = progress / 1048576 + val totalMB = total / 1048576 + + lifecycleScope.launch { + withContext(Dispatchers.Main) { + summary = "${getString(R.string.downloading)} $downloadedMB MB / $totalMB MB" + } + } + } + + if (success) { + RuntimeManager.reInit(requireContext()) + } + } + } + } + summary = instance.getVersion(requireContext()) + true + } + } + } + } \ No newline at end of file diff --git a/app/src/main/res/drawable/outline_data_object_24.xml b/app/src/main/res/drawable/outline_data_object_24.xml new file mode 100644 index 00000000..c3812c49 --- /dev/null +++ b/app/src/main/res/drawable/outline_data_object_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3250f718..ab9d30a6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -510,4 +510,6 @@ Show option to download immediately in the share menu Don\'t fetch data Hook/Greetings + Packages + Not installed diff --git a/app/src/main/res/xml/updating_preferences.xml b/app/src/main/res/xml/updating_preferences.xml index 11858846..3b8f7219 100644 --- a/app/src/main/res/xml/updating_preferences.xml +++ b/app/src/main/res/xml/updating_preferences.xml @@ -1,6 +1,7 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> @@ -75,6 +76,32 @@ app:title="@string/changelog" /> + + + + + + + + + +