From d43a1109c82bad451d9e4df9f4eef103e3ed60b2 Mon Sep 17 00:00:00 2001 From: mikiher Date: Thu, 25 Jan 2024 17:51:06 +0200 Subject: [PATCH] Modify BinaryManager to download version 6.1 and remove old dowloaded versions --- server/managers/BinaryManager.js | 51 ++++- server/utils/fileUtils.js | 3 +- test/server/managers/BinaryManager.test.js | 207 ++++++++++++++++----- 3 files changed, 207 insertions(+), 54 deletions(-) diff --git a/server/managers/BinaryManager.js b/server/managers/BinaryManager.js index e9aab609..1a898236 100644 --- a/server/managers/BinaryManager.js +++ b/server/managers/BinaryManager.js @@ -1,3 +1,6 @@ +const child_process = require('child_process') +const { promisify } = require('util') +const exec = promisify(child_process.exec) const path = require('path') const which = require('../libs/which') const fs = require('../libs/fsExtra') @@ -12,16 +15,20 @@ class BinaryManager { { name: 'ffprobe', envVariable: 'FFPROBE_PATH' } ] + 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.exec = exec } async init() { if (this.initialized) return const missingBinaries = await this.findRequiredBinaries() if (missingBinaries.length == 0) return + await this.removeOldBinaries(missingBinaries) await this.install(missingBinaries) const missingBinariesAfterInstall = await this.findRequiredBinaries() if (missingBinariesAfterInstall.length != 0) { @@ -31,18 +38,29 @@ class BinaryManager { this.initialized = true } + async removeOldBinaries(binaryNames) { + for (const binaryName of binaryNames) { + const executable = this.getExecutableFileName(binaryName) + const mainInstallPath = path.join(this.mainInstallPath, executable) + const altInstallPath = path.join(this.altInstallPath, executable) + Logger.debug(`[BinaryManager] Removing old binaries: ${mainInstallPath}, ${altInstallPath}`) + await fs.remove(mainInstallPath) + await fs.remove(altInstallPath) + } + } + async findRequiredBinaries() { const missingBinaries = [] for (const binary of this.requiredBinaries) { const binaryPath = await this.findBinary(binary.name, binary.envVariable) if (binaryPath) { - Logger.info(`[BinaryManager] Found ${binary.name} at ${binaryPath}`) + Logger.info(`[BinaryManager] Found good ${binary.name} at ${binaryPath}`) if (process.env[binary.envVariable] !== binaryPath) { Logger.info(`[BinaryManager] Updating process.env.${binary.envVariable}`) process.env[binary.envVariable] = binaryPath } } else { - Logger.info(`[BinaryManager] ${binary.name} not found`) + Logger.info(`[BinaryManager] ${binary.name} not found or version too old`) missingBinaries.push(binary.name) } } @@ -50,25 +68,42 @@ class BinaryManager { } async findBinary(name, envVariable) { - const executable = name + (process.platform == 'win32' ? '.exe' : '') + const executable = this.getExecutableFileName(name) const defaultPath = process.env[envVariable] - if (defaultPath && await fs.pathExists(defaultPath)) return defaultPath + if (await this.isBinaryGood(defaultPath)) return defaultPath const whichPath = which.sync(executable, { nothrow: true }) - if (whichPath) return whichPath + if (await this.isBinaryGood(whichPath)) return whichPath const mainInstallPath = path.join(this.mainInstallPath, executable) - if (await fs.pathExists(mainInstallPath)) return mainInstallPath + if (await this.isBinaryGood(mainInstallPath)) return mainInstallPath const altInstallPath = path.join(this.altInstallPath, executable) - if (await fs.pathExists(altInstallPath)) return altInstallPath + if (await this.isBinaryGood(altInstallPath)) return altInstallPath return null } + async isBinaryGood(binaryPath) { + if (!binaryPath || !await fs.pathExists(binaryPath)) return false + 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)) + } catch (err) { + Logger.error(`[BinaryManager] Failed to check version of ${binaryPath}`) + return false + } + } + async install(binaries) { if (binaries.length == 0) return Logger.info(`[BinaryManager] Installing binaries: ${binaries.join(', ')}`) let destination = await fileUtils.isWritable(this.mainInstallPath) ? this.mainInstallPath : this.altInstallPath - await ffbinaries.downloadBinaries(binaries, { destination }) + await ffbinaries.downloadBinaries(binaries, { destination, version: '6.1', force: true }) Logger.info(`[BinaryManager] Binaries installed to ${destination}`) } + + getExecutableFileName(name) { + return name + (process.platform == 'win32' ? '.exe' : '') + } } module.exports = BinaryManager \ No newline at end of file diff --git a/server/utils/fileUtils.js b/server/utils/fileUtils.js index 14e4d743..b01a186c 100644 --- a/server/utils/fileUtils.js +++ b/server/utils/fileUtils.js @@ -370,11 +370,12 @@ module.exports.encodeUriPath = (path) => { */ module.exports.isWritable = async (directory) => { try { - const accessTestFile = path.join(directory, 'accessTest') + const accessTestFile = Path.join(directory, 'accessTest') await fs.writeFile(accessTestFile, '') await fs.remove(accessTestFile) return true } catch (err) { + Logger.info(`[fileUtils] Directory is not writable "${directory}"`, err) return false } } diff --git a/test/server/managers/BinaryManager.test.js b/test/server/managers/BinaryManager.test.js index b93973e0..48aa5e3f 100644 --- a/test/server/managers/BinaryManager.test.js +++ b/test/server/managers/BinaryManager.test.js @@ -15,6 +15,7 @@ describe('BinaryManager', () => { describe('init', () => { let findStub let installStub + let removeOldBinariesStub let errorStub let exitStub @@ -22,6 +23,7 @@ describe('BinaryManager', () => { binaryManager = new BinaryManager() findStub = sinon.stub(binaryManager, 'findRequiredBinaries') installStub = sinon.stub(binaryManager, 'install') + removeOldBinariesStub = sinon.stub(binaryManager, 'removeOldBinaries') errorStub = sinon.stub(console, 'error') exitStub = sinon.stub(process, 'exit') }) @@ -29,6 +31,7 @@ describe('BinaryManager', () => { afterEach(() => { findStub.restore() installStub.restore() + removeOldBinariesStub.restore() errorStub.restore() exitStub.restore() }) @@ -39,6 +42,7 @@ describe('BinaryManager', () => { await binaryManager.init() expect(installStub.called).to.be.false + expect(removeOldBinariesStub.called).to.be.false expect(findStub.calledOnce).to.be.true expect(errorStub.called).to.be.false expect(exitStub.called).to.be.false @@ -54,6 +58,7 @@ describe('BinaryManager', () => { expect(findStub.calledTwice).to.be.true expect(installStub.calledOnce).to.be.true + expect(removeOldBinariesStub.calledOnce).to.be.true expect(errorStub.called).to.be.false expect(exitStub.called).to.be.false }) @@ -68,6 +73,7 @@ describe('BinaryManager', () => { expect(findStub.calledTwice).to.be.true expect(installStub.calledOnce).to.be.true + expect(removeOldBinariesStub.calledOnce).to.be.true expect(errorStub.calledOnce).to.be.true expect(exitStub.calledOnce).to.be.true expect(exitStub.calledWith(1)).to.be.true @@ -171,7 +177,7 @@ describe('BinaryManager', () => { describe('findBinary', () => { let binaryManager - let fsPathExistsStub + let isBinaryGoodStub let whichSyncStub let mainInstallPath let altInstallPath @@ -185,7 +191,7 @@ describe('findBinary', () => { beforeEach(() => { binaryManager = new BinaryManager() - fsPathExistsStub = sinon.stub(fs, 'pathExists') + isBinaryGoodStub = sinon.stub(binaryManager, 'isBinaryGood') whichSyncStub = sinon.stub(which, 'sync') binaryManager.mainInstallPath = '/path/to/main/install' mainInstallPath = path.join(binaryManager.mainInstallPath, executable) @@ -194,71 +200,182 @@ describe('findBinary', () => { }) afterEach(() => { - fsPathExistsStub.restore() + isBinaryGoodStub.restore() whichSyncStub.restore() }) - - it('should return defaultPath if it exists', async () => { + + it('should return the defaultPath if it exists and is a good binary', async () => { process.env[envVariable] = defaultPath - fsPathExistsStub.withArgs(defaultPath).resolves(true) - + isBinaryGoodStub.withArgs(defaultPath).resolves(true) + const result = await binaryManager.findBinary(name, envVariable) - - expect(result).to.equal(defaultPath) - expect(fsPathExistsStub.calledOnceWith(defaultPath)).to.be.true - expect(whichSyncStub.notCalled).to.be.true + + expect(result).to.equal(defaultPath) + expect(isBinaryGoodStub.calledOnce).to.be.true + expect(isBinaryGoodStub.calledWith(defaultPath)).to.be.true }) - - it('should return whichPath if it exists', async () => { + + it('should return the whichPath if it exists and is a good binary', async () => { delete process.env[envVariable] + isBinaryGoodStub.withArgs(undefined).resolves(false) + isBinaryGoodStub.withArgs(whichPath).resolves(true) whichSyncStub.returns(whichPath) - + const result = await binaryManager.findBinary(name, envVariable) - + expect(result).to.equal(whichPath) - expect(fsPathExistsStub.notCalled).to.be.true - expect(whichSyncStub.calledOnce).to.be.true + expect(isBinaryGoodStub.calledTwice).to.be.true + expect(isBinaryGoodStub.calledWith(undefined)).to.be.true + expect(isBinaryGoodStub.calledWith(whichPath)).to.be.true }) - - it('should return mainInstallPath if it exists', async () => { + + it('should return the mainInstallPath if it exists and is a good binary', async () => { delete process.env[envVariable] + isBinaryGoodStub.withArgs(undefined).resolves(false) + isBinaryGoodStub.withArgs(null).resolves(false) + isBinaryGoodStub.withArgs(mainInstallPath).resolves(true) whichSyncStub.returns(null) - fsPathExistsStub.withArgs(mainInstallPath).resolves(true) - + const result = await binaryManager.findBinary(name, envVariable) - + expect(result).to.equal(mainInstallPath) - expect(whichSyncStub.calledOnce).to.be.true - expect(fsPathExistsStub.calledOnceWith(mainInstallPath)).to.be.true + expect(isBinaryGoodStub.callCount).to.be.equal(3) + expect(isBinaryGoodStub.calledWith(undefined)).to.be.true + expect(isBinaryGoodStub.calledWith(null)).to.be.true + expect(isBinaryGoodStub.calledWith(mainInstallPath)).to.be.true }) - - it('should return altInstallPath if it exists', async () => { + + it('should return the altInstallPath if it exists and is a good binary', async () => { delete process.env[envVariable] + isBinaryGoodStub.withArgs(undefined).resolves(false) + isBinaryGoodStub.withArgs(null).resolves(false) + isBinaryGoodStub.withArgs(mainInstallPath).resolves(false) + isBinaryGoodStub.withArgs(altInstallPath).resolves(true) whichSyncStub.returns(null) - fsPathExistsStub.withArgs(mainInstallPath).resolves(false) - fsPathExistsStub.withArgs(altInstallPath).resolves(true) - + const result = await binaryManager.findBinary(name, envVariable) - + expect(result).to.equal(altInstallPath) - expect(whichSyncStub.calledOnce).to.be.true - expect(fsPathExistsStub.calledTwice).to.be.true - expect(fsPathExistsStub.calledWith(mainInstallPath)).to.be.true - expect(fsPathExistsStub.calledWith(altInstallPath)).to.be.true + expect(isBinaryGoodStub.callCount).to.be.equal(4) + expect(isBinaryGoodStub.calledWith(undefined)).to.be.true + expect(isBinaryGoodStub.calledWith(null)).to.be.true + expect(isBinaryGoodStub.calledWith(mainInstallPath)).to.be.true + expect(isBinaryGoodStub.calledWith(altInstallPath)).to.be.true + }) + + it('should return null if no good binary is found', async () => { + delete process.env[envVariable] + isBinaryGoodStub.withArgs(undefined).resolves(false) + isBinaryGoodStub.withArgs(null).resolves(false) + isBinaryGoodStub.withArgs(mainInstallPath).resolves(false) + isBinaryGoodStub.withArgs(altInstallPath).resolves(false) + whichSyncStub.returns(null) + + const result = await binaryManager.findBinary(name, envVariable) + + expect(result).to.be.null + expect(isBinaryGoodStub.callCount).to.be.equal(4) + expect(isBinaryGoodStub.calledWith(undefined)).to.be.true + expect(isBinaryGoodStub.calledWith(null)).to.be.true + expect(isBinaryGoodStub.calledWith(mainInstallPath)).to.be.true + expect(isBinaryGoodStub.calledWith(altInstallPath)).to.be.true + }) +}) + +describe('isBinaryGood', () => { + let binaryManager + let fsPathExistsStub + let execStub + let loggerInfoStub + let loggerErrorStub + + const binaryPath = '/path/to/binary' + const execCommand = '"' + binaryPath + '"' + ' -version' + + beforeEach(() => { + binaryManager = new BinaryManager() + fsPathExistsStub = sinon.stub(fs, 'pathExists') + execStub = sinon.stub(binaryManager, 'exec') }) - it('should return null if binary is not found', async () => { - delete process.env[envVariable] - whichSyncStub.returns(null) - fsPathExistsStub.withArgs(mainInstallPath).resolves(false) - fsPathExistsStub.withArgs(altInstallPath).resolves(false) + afterEach(() => { + fsPathExistsStub.restore() + execStub.restore() + }) - const result = await binaryManager.findBinary(name, envVariable) + it('should return false if binaryPath is falsy', async () => { + fsPathExistsStub.resolves(true) - expect(result).to.be.null - expect(whichSyncStub.calledOnce).to.be.true - expect(fsPathExistsStub.calledTwice).to.be.true - expect(fsPathExistsStub.calledWith(mainInstallPath)).to.be.true - expect(fsPathExistsStub.calledWith(altInstallPath)).to.be.true + const result = await binaryManager.isBinaryGood(null) + + expect(result).to.be.false + expect(fsPathExistsStub.called).to.be.false + expect(execStub.called).to.be.false + }) + + it('should return false if binaryPath does not exist', async () => { + fsPathExistsStub.resolves(false) + + const result = await binaryManager.isBinaryGood(binaryPath) + + expect(result).to.be.false + expect(fsPathExistsStub.calledOnce).to.be.true + expect(fsPathExistsStub.calledWith(binaryPath)).to.be.true + expect(execStub.called).to.be.false + }) + + it('should return false if failed to check version of binary', async () => { + fsPathExistsStub.resolves(true) + execStub.rejects(new Error('Failed to execute command')) + + const result = await binaryManager.isBinaryGood(binaryPath) + + expect(result).to.be.false + expect(fsPathExistsStub.calledOnce).to.be.true + expect(fsPathExistsStub.calledWith(binaryPath)).to.be.true + expect(execStub.calledOnce).to.be.true + expect(execStub.calledWith(execCommand)).to.be.true + }) + + it('should return false if version is not found', async () => { + const stdout = 'Some output without version' + fsPathExistsStub.resolves(true) + execStub.resolves({ stdout }) + + const result = await binaryManager.isBinaryGood(binaryPath) + + expect(result).to.be.false + expect(fsPathExistsStub.calledOnce).to.be.true + expect(fsPathExistsStub.calledWith(binaryPath)).to.be.true + expect(execStub.calledOnce).to.be.true + expect(execStub.calledWith(execCommand)).to.be.true + }) + + it('should return false if version is found but does not match a good version', async () => { + const stdout = 'version 1.2.3' + fsPathExistsStub.resolves(true) + execStub.resolves({ stdout }) + + const result = await binaryManager.isBinaryGood(binaryPath) + + expect(result).to.be.false + expect(fsPathExistsStub.calledOnce).to.be.true + expect(fsPathExistsStub.calledWith(binaryPath)).to.be.true + expect(execStub.calledOnce).to.be.true + expect(execStub.calledWith(execCommand)).to.be.true + }) + + it('should return true if version is found and matches a good version', async () => { + const stdout = 'version 6.1.2' + fsPathExistsStub.resolves(true) + execStub.resolves({ stdout }) + + const result = await binaryManager.isBinaryGood(binaryPath) + + expect(result).to.be.true + expect(fsPathExistsStub.calledOnce).to.be.true + expect(fsPathExistsStub.calledWith(binaryPath)).to.be.true + expect(execStub.calledOnce).to.be.true + expect(execStub.calledWith(execCommand)).to.be.true }) }) \ No newline at end of file