From d9e7f5d1333fb9abd39d9b368e2236bf716c0acd Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 17 Feb 2024 17:40:33 -0600 Subject: [PATCH] Update BinaryManager JSDocs, move validVersions to required binary objects --- server/managers/BinaryManager.js | 80 +++++++++++++++++++++++++------- server/utils/fileUtils.js | 2 +- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/server/managers/BinaryManager.js b/server/managers/BinaryManager.js index 1a898236..ec4ed3b6 100644 --- a/server/managers/BinaryManager.js +++ b/server/managers/BinaryManager.js @@ -11,16 +11,15 @@ const fileUtils = require('../utils/fileUtils') class BinaryManager { defaultRequiredBinaries = [ - { name: 'ffmpeg', envVariable: 'FFMPEG_PATH' }, - { name: 'ffprobe', envVariable: 'FFPROBE_PATH' } + { name: 'ffmpeg', envVariable: 'FFMPEG_PATH', validVersions: ['5.1', '6'] }, + { name: 'ffprobe', envVariable: 'FFPROBE_PATH', validVersions: ['5.1', '6'] } ] - goodVersions = [ '5.1', '6' ] - constructor(requiredBinaries = this.defaultRequiredBinaries) { this.requiredBinaries = requiredBinaries this.mainInstallPath = process.pkg ? path.dirname(process.execPath) : global.appRoot this.altInstallPath = global.ConfigPath + this.initialized = false this.exec = exec } @@ -31,30 +30,45 @@ class BinaryManager { await this.removeOldBinaries(missingBinaries) await this.install(missingBinaries) const missingBinariesAfterInstall = await this.findRequiredBinaries() - if (missingBinariesAfterInstall.length != 0) { + if (missingBinariesAfterInstall.length) { Logger.error(`[BinaryManager] Failed to find or install required binaries: ${missingBinariesAfterInstall.join(', ')}`) process.exit(1) } this.initialized = true } + /** + * Remove old/invalid binaries in main or alt install path + * + * @param {string[]} binaryNames + */ async removeOldBinaries(binaryNames) { for (const binaryName of binaryNames) { const executable = this.getExecutableFileName(binaryName) const mainInstallPath = path.join(this.mainInstallPath, executable) + if (await fs.pathExists(mainInstallPath)) { + Logger.debug(`[BinaryManager] Removing old binary: ${mainInstallPath}`) + await fs.remove(mainInstallPath) + } const altInstallPath = path.join(this.altInstallPath, executable) - Logger.debug(`[BinaryManager] Removing old binaries: ${mainInstallPath}, ${altInstallPath}`) - await fs.remove(mainInstallPath) - await fs.remove(altInstallPath) + if (await fs.pathExists(altInstallPath)) { + Logger.debug(`[BinaryManager] Removing old binary: ${altInstallPath}`) + await fs.remove(altInstallPath) + } } } + /** + * Find required binaries and return array of binary names that are missing + * + * @returns {Promise} + */ async findRequiredBinaries() { const missingBinaries = [] for (const binary of this.requiredBinaries) { - const binaryPath = await this.findBinary(binary.name, binary.envVariable) + const binaryPath = await this.findBinary(binary.name, binary.envVariable, binary.validVersions) if (binaryPath) { - Logger.info(`[BinaryManager] Found good ${binary.name} at ${binaryPath}`) + Logger.info(`[BinaryManager] Found valid binary ${binary.name} at ${binaryPath}`) if (process.env[binary.envVariable] !== binaryPath) { Logger.info(`[BinaryManager] Updating process.env.${binary.envVariable}`) process.env[binary.envVariable] = binaryPath @@ -67,40 +81,70 @@ class BinaryManager { return missingBinaries } - async findBinary(name, envVariable) { + /** + * Find absolute path for binary + * + * @param {string} name + * @param {string} envVariable + * @param {string[]} [validVersions] + * @returns {Promise} Path to binary + */ + async findBinary(name, envVariable, validVersions = []) { const executable = this.getExecutableFileName(name) + // 1. check path specified in environment variable const defaultPath = process.env[envVariable] - if (await this.isBinaryGood(defaultPath)) return defaultPath + if (await this.isBinaryGood(defaultPath, validVersions)) return defaultPath + // 2. find the first instance of the binary in the PATH environment variable const whichPath = which.sync(executable, { nothrow: true }) - if (await this.isBinaryGood(whichPath)) return whichPath + if (await this.isBinaryGood(whichPath, validVersions)) return whichPath + // 3. check main install path (binary root dir) const mainInstallPath = path.join(this.mainInstallPath, executable) - if (await this.isBinaryGood(mainInstallPath)) return mainInstallPath + if (await this.isBinaryGood(mainInstallPath, validVersions)) return mainInstallPath + // 4. check alt install path (/config) const altInstallPath = path.join(this.altInstallPath, executable) - if (await this.isBinaryGood(altInstallPath)) return altInstallPath + if (await this.isBinaryGood(altInstallPath, validVersions)) return altInstallPath return null } - async isBinaryGood(binaryPath) { + /** + * Check binary path exists and optionally check version is valid + * + * @param {string} binaryPath + * @param {string[]} [validVersions] + * @returns {Promise} + */ + async isBinaryGood(binaryPath, validVersions = []) { if (!binaryPath || !await fs.pathExists(binaryPath)) return false + if (!validVersions.length) return true try { const { stdout } = await this.exec('"' + binaryPath + '"' + ' -version') const version = stdout.match(/version\s([\d\.]+)/)?.[1] if (!version) return false - return this.goodVersions.some(goodVersion => version.startsWith(goodVersion)) + return validVersions.some(validVersion => version.startsWith(validVersion)) } catch (err) { Logger.error(`[BinaryManager] Failed to check version of ${binaryPath}`) return false } } + /** + * + * @param {string[]} binaries + */ async install(binaries) { - if (binaries.length == 0) return + if (!binaries.length) return Logger.info(`[BinaryManager] Installing binaries: ${binaries.join(', ')}`) let destination = await fileUtils.isWritable(this.mainInstallPath) ? this.mainInstallPath : this.altInstallPath await ffbinaries.downloadBinaries(binaries, { destination, version: '6.1', force: true }) Logger.info(`[BinaryManager] Binaries installed to ${destination}`) } + /** + * Append .exe to binary name for Windows + * + * @param {string} name + * @returns {string} + */ getExecutableFileName(name) { return name + (process.platform == 'win32' ? '.exe' : '') } diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index b01a186c..99bb49eb 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -366,7 +366,7 @@ module.exports.encodeUriPath = (path) => { * This method is necessary because fs.access(directory, fs.constants.W_OK) does not work on Windows * * @param {string} directory - * @returns {boolean} + * @returns {Promise} */ module.exports.isWritable = async (directory) => { try {