diff --git a/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt b/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt index 97a87212..960982cc 100644 --- a/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt +++ b/app/src/main/java/com/deniscerri/ytdl/MainActivity.kt @@ -590,6 +590,7 @@ class MainActivity : BaseActivity() { val latestRelease = releases.first() if (latestRelease.isBundled || latestRelease.isInstalled) return@apply + if (latestRelease.oldVersion) return@apply if (skipRemindingPackageUpdate.contains(latestRelease.tag_name)) return@apply skipRemindingPackageUpdate.add(latestRelease.tag_name) 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 75ae08cd..ac891154 100644 --- a/app/src/main/java/com/deniscerri/ytdl/core/RuntimeManager.kt +++ b/app/src/main/java/com/deniscerri/ytdl/core/RuntimeManager.kt @@ -67,6 +67,11 @@ object RuntimeManager { private var ENV_PYTHONHOME: String? = null private var TMPDIR: String = "" + private var NPM_CONFIG_PREFIX: String = "" + private var NPM_CONFIG_CACHE: String = "" + private var NPM_CLI_PATH: String = "" + private var NODE_OPTIONS: String = "" + val packages: List = listOf( PackageItem("Python", Python), PackageItem("FFmpeg", FFmpeg), @@ -154,6 +159,16 @@ object RuntimeManager { } TMPDIR = appContext.cacheDir.absolutePath + NPM_CONFIG_PREFIX = File(appContext.filesDir, ".npm-global").absolutePath + NPM_CONFIG_CACHE = File(appContext.filesDir, ".npm-cache").absolutePath + if (nodeLocation.executable.exists()) { + NPM_CLI_PATH = File(nodeLocation.ldDir.absolutePath, "usr/lib/node_modules/npm/bin/npm-cli.js").absolutePath + + val optionsFile = File(appContext.filesDir, "node_dns_setup.js") + optionsFile.writeText(NodeJS.getDNSSetup()) + NODE_OPTIONS = "--require ${optionsFile.absolutePath}" + } + initialized = true initLatch.countDown() updateLatch.countDown() @@ -292,6 +307,33 @@ object RuntimeManager { return executeImpl(fullCommand, processId, true, callback = callback) } + fun executeNode( + command: String, + processId: String? = null, + executeDirectory: File? = null, + callback: ((Float, Long, String) -> Unit)? = null + ) : ExecuteResponse { + assertInit() + + val fullCommand = mutableListOf(nodeLocation.executable.absolutePath) + fullCommand.addAll(command.split(" ")) + return executeImpl(fullCommand, processId, true, executeDirectory = executeDirectory, callback = callback) + } + + fun executeNpm( + command: String, + processId: String? = null, + executeDirectory: File? = null, + callback: ((Float, Long, String) -> Unit)? = null + ) : ExecuteResponse { + assertInit() + + val fullCommand = mutableListOf(nodeLocation.executable.absolutePath, NPM_CLI_PATH) + fullCommand.addAll(command.split(" ")) + return executeImpl(fullCommand, processId, true, executeDirectory = executeDirectory, callback = callback) + } + + fun executeDeno( command: String, processId: String? = null, @@ -447,6 +489,10 @@ object RuntimeManager { env["PYTHONHOME"] = ENV_PYTHONHOME env["HOME"] = ENV_PYTHONHOME env["TMPDIR"] = TMPDIR + env["NPM_CONFIG_PREFIX"] = NPM_CONFIG_PREFIX + env["NPM_CONFIG_CACHE"] = NPM_CONFIG_CACHE + env["NPM_CLI_PATH"] = NPM_CLI_PATH + env["NODE_OPTIONS"] = NODE_OPTIONS env["TERM"] = "xterm-256color" return env diff --git a/app/src/main/java/com/deniscerri/ytdl/core/packages/NodeJS.kt b/app/src/main/java/com/deniscerri/ytdl/core/packages/NodeJS.kt index 90de894b..e08e84ba 100644 --- a/app/src/main/java/com/deniscerri/ytdl/core/packages/NodeJS.kt +++ b/app/src/main/java/com/deniscerri/ytdl/core/packages/NodeJS.kt @@ -9,4 +9,76 @@ object NodeJS : PackageBase() { override val githubRepo: String get() = "deniscerri/ytdlnis-packages" override val githubPackageName: String get() = "nodejs" override val apkPackage: String get() = "com.deniscerri.ytdl.nodejs" + + fun getDNSSetup() : String { + return """ + +const dns = require('dns'); +const net = require('net'); + +const SERVERS = ['8.8.8.8', '1.1.1.1']; +dns.setServers(SERVERS); + +const resolver = new dns.Resolver(); +resolver.setServers(SERVERS); + +dns.lookup = function patchedLookup(hostname, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + options = options || {}; + + const wantAll = options.all === true; + const family = options.family || 0; // 0 = either + + // Literal IPs / wildcard bind addresses: resolve instantly, no network. + const ipVersion = net.isIP(hostname); + if (ipVersion) { + if (wantAll) return callback(null, [{ address: hostname, family: ipVersion }]); + return callback(null, hostname, ipVersion); + } + + // localhost: resolve instantly, no network. + if (hostname === 'localhost') { + const results = []; + if (family !== 6) results.push({ address: '127.0.0.1', family: 4 }); + if (family !== 4) results.push({ address: '::1', family: 6 }); + if (wantAll) return callback(null, results); + const first = results[0]; + return callback(null, first.address, first.family); + } + + // Real hostnames: resolve via c-ares against our explicit DNS servers. + const tryFamily = (fam, cb) => { + const method = fam === 6 ? 'resolve6' : 'resolve4'; + resolver[method](hostname, (err, addresses) => { + if (err) return cb(err); + cb(null, addresses.map((address) => ({ address, family: fam }))); + }); + }; + + const finish = (err, results) => { + if (err) return callback(err); + if (wantAll) return callback(null, results); + const first = results[0]; + callback(null, first.address, first.family); + }; + + if (family === 4 || family === 6) { + return tryFamily(family, (err, results) => finish(err, results)); + } + + // Try IPv4 first, fall back to IPv6 if that fails. + tryFamily(4, (err4, results4) => { + if (!err4) return finish(null, results4); + tryFamily(6, (err6, results6) => { + if (!err6) return finish(null, results6); + finish(err4); // report the original (v4) error + }); + }); +}; + + """.trimIndent() + } } \ No newline at end of file diff --git a/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt b/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt index 8edd6ab7..b951ce1c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt +++ b/app/src/main/java/com/deniscerri/ytdl/core/packages/PackageBase.kt @@ -51,7 +51,8 @@ abstract class PackageBase { var version: String = "", var downloadSize: Long = 0, var isInstalled: Boolean = false, - var isBundled: Boolean = false + var isBundled: Boolean = false, + var oldVersion: Boolean = false ) data class PackageLocation( @@ -285,6 +286,11 @@ abstract class PackageBase { it.downloadSize = it.assets.first().size it.isInstalled = downloadedVersion == "v${it.version}" it.isBundled = bundledVersion == "v${it.version}" + + val latestVersion = bundledVersion?.replace("v|.".toRegex(), "")?.toInt() + ?: downloadedVersion?.replace("v|.".toRegex(), "")?.toInt() ?: 0 + + it.oldVersion = latestVersion > it.version.replace(".", "").toInt() } Result.success(releases) diff --git a/app/src/main/java/com/deniscerri/ytdl/services/BgUtilsPoTokenGeneratorService.kt b/app/src/main/java/com/deniscerri/ytdl/services/BgUtilsPoTokenGeneratorService.kt index 0402f359..8918a849 100644 --- a/app/src/main/java/com/deniscerri/ytdl/services/BgUtilsPoTokenGeneratorService.kt +++ b/app/src/main/java/com/deniscerri/ytdl/services/BgUtilsPoTokenGeneratorService.kt @@ -60,8 +60,10 @@ class BgUtilsPoTokenGeneratorService : Service() { } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val runtimeManager = RuntimeManager.getInstance() + if (intent?.action == "ACTION_EXIT") { - RuntimeManager.getInstance().destroyProcessById(currentRunningProcess) + runtimeManager.destroyProcessById(currentRunningProcess) stopSelf() return super.onStartCommand(intent, flags, startId) } @@ -69,16 +71,32 @@ class BgUtilsPoTokenGeneratorService : Service() { serviceScope.launch { runCatching { val serverFolder = BgUtilsPoTokenGeneratorUtil.getServerFolder(App.instance) - RuntimeManager.getInstance().destroyProcessById(currentRunningProcess) - RuntimeManager.getInstance().executeDeno( - command = "run -A src/main.ts", - processId = currentRunningProcess, - executeDirectory = File(serverFolder, "server") - ) { _, _, line -> - Log.e("BGUTILS_POT", line) - val notification = createNotification(line) - notificationManager.notify(notificationCode, notification) + runtimeManager.destroyProcessById(currentRunningProcess) + + if (runtimeManager.nodeLocation.isAvailable) { + runtimeManager.executeNode( + command = "build/main.js", + processId = currentRunningProcess, + executeDirectory = File(serverFolder, "server") + ) { _, _, line -> + Log.e("BGUTILS_POT", line) + val notification = createNotification(line) + notificationManager.notify(notificationCode, notification) + } + + } else { + runtimeManager.executeDeno( + command = "run -A src/main.ts", + processId = currentRunningProcess, + executeDirectory = File(serverFolder, "server") + ) { _, _, line -> + Log.e("BGUTILS_POT", line) + val notification = createNotification(line) + notificationManager.notify(notificationCode, notification) + } } + + }.onFailure { err -> Log.e("BGUTILS_POT", err.message ?: "") stopSelf() diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/generateyoutubepotokens/GenerateYoutubePoTokensFragment.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/generateyoutubepotokens/GenerateYoutubePoTokensFragment.kt index ecc11f81..3c8b6c01 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/generateyoutubepotokens/GenerateYoutubePoTokensFragment.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/settings/advanced/generateyoutubepotokens/GenerateYoutubePoTokensFragment.kt @@ -166,23 +166,14 @@ class GenerateYoutubePoTokensFragment : Fragment() { } serverRadio.apply { - isChecked = bgUtilsMethod == "server" && denoIsInstalled - alpha = if (denoIsInstalled) 1f else 0.3f - + isChecked = bgUtilsMethod == "server" setOnClickListener { - if (denoIsInstalled) { - preferences.edit(commit = true) { - putString("bgutils_potoken_method", "server") - } - lifecycleScope.launch { - withContext(Dispatchers.IO) { - BgUtilsPoTokenGeneratorUtil.runServer(context) {} - } - } - } else { - Snackbar.make(requireActivity().findViewById(android.R.id.content), context.getString(R.string.please_install_package, "Deno"), Snackbar.LENGTH_SHORT).show() - post { - scriptRadio.performClick() + preferences.edit(commit = true) { + putString("bgutils_potoken_method", "server") + } + lifecycleScope.launch { + withContext(Dispatchers.IO) { + BgUtilsPoTokenGeneratorUtil.runServer(context) {} } } } diff --git a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/MkSession.kt b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/MkSession.kt index 169602ab..71fed6b8 100644 --- a/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/MkSession.kt +++ b/app/src/main/java/com/deniscerri/ytdl/ui/more/terminal/MkSession.kt @@ -108,6 +108,35 @@ object MkSession { } val pythonBin = runtimeManager.pythonLocation.executable + val pythonExec = if (pythonBin.name.endsWith(".so")) { + "$linker \"${pythonBin.absolutePath}\"" + } else { + "\"${pythonBin.absolutePath}\"" + } + + rcBuilder.append( + shellFunction( + "pip", + "$pythonExec -m pip" + ) + ) + + val nodeBin = runtimeManager.nodeLocation.executable + if (nodeBin.exists()) { + val nodeExec = if (nodeBin.name.endsWith(".so")) { + "$linker \"${nodeBin.absolutePath}\"" + } else { + "\"${nodeBin.absolutePath}\"" + } + + rcBuilder.append( + shellFunction( + "npm", + "$nodeExec ${$$"$NPM_CLI_PATH"}" + ) + ) + } + val ytdlpBin = runtimeManager.ytdlpPath if (pythonBin.exists() && ytdlpBin != null && ytdlpBin.exists()) { val pythonExec = if (pythonBin.name.endsWith(".so")) { diff --git a/app/src/main/java/com/deniscerri/ytdl/util/BgUtilsPoTokenGeneratorUtil.kt b/app/src/main/java/com/deniscerri/ytdl/util/BgUtilsPoTokenGeneratorUtil.kt index a5bf15c9..3d03a47c 100644 --- a/app/src/main/java/com/deniscerri/ytdl/util/BgUtilsPoTokenGeneratorUtil.kt +++ b/app/src/main/java/com/deniscerri/ytdl/util/BgUtilsPoTokenGeneratorUtil.kt @@ -97,27 +97,54 @@ object BgUtilsPoTokenGeneratorUtil { progress?.invoke(ytdlResponse.err) return Result.failure(Exception(ytdlResponse.err)) } + val hasNode = RuntimeManager.getInstance().nodeLocation.isAvailable + val hasDeno = RuntimeManager.getInstance().denoLocation.isAvailable progress?.invoke("Downloading node-modules...") - val denoResponse = RuntimeManager.getInstance().executeDeno( - command = "install", - executeDirectory = File(serverFolder, "server")) { _, _, line -> - progress?.invoke(line) - } - if (denoResponse.exitCode != 0) { - progress?.invoke(denoResponse.err) - return Result.failure(Exception(denoResponse.err)) - } + if (hasNode) { + val nodeResponse = RuntimeManager.getInstance().executeNpm( + command = "install --ignore-scripts", + executeDirectory = File(serverFolder, "server")) { _, _, line -> + progress?.invoke(line) + } + if (nodeResponse.exitCode != 0) { + progress?.invoke(nodeResponse.err) + return Result.failure(Exception(nodeResponse.err)) + } - progress?.invoke("Building typescript files...") - val denoResponse2 = RuntimeManager.getInstance().executeDeno( - command = "run -A npm:typescript/tsc --outDir build", - executeDirectory = File(serverFolder, "server")) { _, _, line -> - progress?.invoke(line) + progress?.invoke("Building typescript files...") + val nodeResponse2 = RuntimeManager.getInstance().executeNode( + command = "node_modules/typescript/bin/tsc", + executeDirectory = File(serverFolder, "server")) { _, _, line -> + progress?.invoke(line) + } + if (nodeResponse2.exitCode != 0) { + progress?.invoke(nodeResponse2.err) + return Result.failure(Exception(nodeResponse2.err)) + } } - if (denoResponse2.exitCode != 0) { - progress?.invoke(denoResponse2.err) - return Result.failure(Exception(denoResponse2.err)) + + if (hasDeno) { + val denoResponse = RuntimeManager.getInstance().executeDeno( + command = "install", + executeDirectory = File(serverFolder, "server")) { _, _, line -> + progress?.invoke(line) + } + if (denoResponse.exitCode != 0) { + progress?.invoke(denoResponse.err) + return Result.failure(Exception(denoResponse.err)) + } + + progress?.invoke("Building typescript files...") + val denoResponse2 = RuntimeManager.getInstance().executeDeno( + command = "run -A npm:typescript/tsc --outDir build", + executeDirectory = File(serverFolder, "server")) { _, _, line -> + progress?.invoke(line) + } + if (denoResponse2.exitCode != 0) { + progress?.invoke(denoResponse2.err) + return Result.failure(Exception(denoResponse2.err)) + } } if (runServerAfterwards) {