mirror of
				https://github.com/advplyr/audiobookshelf.git
				synced 2025-11-04 03:17:00 -05:00 
			
		
		
		
	Merge pull request #2391 from mikiher/binary-manager
Add a binary manager that finds ffmpeg and ffprobe and installs them if not found
This commit is contained in:
		
						commit
						8c6a2ac5dd
					
				
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -13,6 +13,8 @@
 | 
				
			|||||||
/deploy/
 | 
					/deploy/
 | 
				
			||||||
/coverage/
 | 
					/coverage/
 | 
				
			||||||
/.nyc_output/
 | 
					/.nyc_output/
 | 
				
			||||||
 | 
					/ffmpeg*
 | 
				
			||||||
 | 
					/ffprobe*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sw.*
 | 
					sw.*
 | 
				
			||||||
.DS_STORE
 | 
					.DS_STORE
 | 
				
			||||||
 | 
				
			|||||||
@ -33,6 +33,7 @@ const AudioMetadataMangaer = require('./managers/AudioMetadataManager')
 | 
				
			|||||||
const RssFeedManager = require('./managers/RssFeedManager')
 | 
					const RssFeedManager = require('./managers/RssFeedManager')
 | 
				
			||||||
const CronManager = require('./managers/CronManager')
 | 
					const CronManager = require('./managers/CronManager')
 | 
				
			||||||
const ApiCacheManager = require('./managers/ApiCacheManager')
 | 
					const ApiCacheManager = require('./managers/ApiCacheManager')
 | 
				
			||||||
 | 
					const BinaryManager = require('./managers/BinaryManager')
 | 
				
			||||||
const LibraryScanner = require('./scanner/LibraryScanner')
 | 
					const LibraryScanner = require('./scanner/LibraryScanner')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//Import the main Passport and Express-Session library
 | 
					//Import the main Passport and Express-Session library
 | 
				
			||||||
@ -74,6 +75,7 @@ class Server {
 | 
				
			|||||||
    this.rssFeedManager = new RssFeedManager()
 | 
					    this.rssFeedManager = new RssFeedManager()
 | 
				
			||||||
    this.cronManager = new CronManager(this.podcastManager)
 | 
					    this.cronManager = new CronManager(this.podcastManager)
 | 
				
			||||||
    this.apiCacheManager = new ApiCacheManager()
 | 
					    this.apiCacheManager = new ApiCacheManager()
 | 
				
			||||||
 | 
					    this.binaryManager = new BinaryManager()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Routers
 | 
					    // Routers
 | 
				
			||||||
    this.apiRouter = new ApiRouter(this)
 | 
					    this.apiRouter = new ApiRouter(this)
 | 
				
			||||||
@ -120,6 +122,11 @@ class Server {
 | 
				
			|||||||
    await this.cronManager.init(libraries)
 | 
					    await this.cronManager.init(libraries)
 | 
				
			||||||
    this.apiCacheManager.init()
 | 
					    this.apiCacheManager.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Download ffmpeg & ffprobe if not found (Currently only in use for Windows installs)
 | 
				
			||||||
 | 
					    if (global.isWin || Logger.isDev) {
 | 
				
			||||||
 | 
					      await this.binaryManager.init()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (Database.serverSettings.scannerDisableWatcher) {
 | 
					    if (Database.serverSettings.scannerDisableWatcher) {
 | 
				
			||||||
      Logger.info(`[Server] Watcher is disabled`)
 | 
					      Logger.info(`[Server] Watcher is disabled`)
 | 
				
			||||||
      this.watcher.disabled = true
 | 
					      this.watcher.disabled = true
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										315
									
								
								server/libs/ffbinaries/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								server/libs/ffbinaries/index.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,315 @@
 | 
				
			|||||||
 | 
					const os = require('os')
 | 
				
			||||||
 | 
					const path = require('path')
 | 
				
			||||||
 | 
					const axios = require('axios')
 | 
				
			||||||
 | 
					const fse = require('../fsExtra')
 | 
				
			||||||
 | 
					const async = require('../async')
 | 
				
			||||||
 | 
					const StreamZip = require('../nodeStreamZip')
 | 
				
			||||||
 | 
					const { finished } = require('stream/promises')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var API_URL = 'https://ffbinaries.com/api/v1'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var RUNTIME_CACHE = {}
 | 
				
			||||||
 | 
					var errorMsgs = {
 | 
				
			||||||
 | 
					  connectionIssues: 'Couldn\'t connect to ffbinaries.com API. Check your Internet connection.',
 | 
				
			||||||
 | 
					  parsingVersionData: 'Couldn\'t parse retrieved version data.',
 | 
				
			||||||
 | 
					  parsingVersionList: 'Couldn\'t parse the list of available versions.',
 | 
				
			||||||
 | 
					  notFound: 'Requested data not found.',
 | 
				
			||||||
 | 
					  incorrectVersionParam: '"version" parameter must be a string.'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function ensureDirSync(dir) {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    fse.accessSync(dir)
 | 
				
			||||||
 | 
					  } catch (e) {
 | 
				
			||||||
 | 
					    fse.mkdirSync(dir)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Resolves the platform key based on input string
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function resolvePlatform(input) {
 | 
				
			||||||
 | 
					  var rtn = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  switch (input) {
 | 
				
			||||||
 | 
					    case 'mac':
 | 
				
			||||||
 | 
					    case 'osx':
 | 
				
			||||||
 | 
					    case 'mac-64':
 | 
				
			||||||
 | 
					    case 'osx-64':
 | 
				
			||||||
 | 
					      rtn = 'osx-64'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'linux':
 | 
				
			||||||
 | 
					    case 'linux-32':
 | 
				
			||||||
 | 
					      rtn = 'linux-32'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'linux-64':
 | 
				
			||||||
 | 
					      rtn = 'linux-64'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'linux-arm':
 | 
				
			||||||
 | 
					    case 'linux-armel':
 | 
				
			||||||
 | 
					      rtn = 'linux-armel'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'linux-armhf':
 | 
				
			||||||
 | 
					      rtn = 'linux-armhf'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'win':
 | 
				
			||||||
 | 
					    case 'win-32':
 | 
				
			||||||
 | 
					    case 'windows':
 | 
				
			||||||
 | 
					    case 'windows-32':
 | 
				
			||||||
 | 
					      rtn = 'windows-32'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    case 'win-64':
 | 
				
			||||||
 | 
					    case 'windows-64':
 | 
				
			||||||
 | 
					      rtn = 'windows-64'
 | 
				
			||||||
 | 
					      break
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      rtn = null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return rtn
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Detects the platform of the machine the script is executed on.
 | 
				
			||||||
 | 
					 * Object can be provided to detect platform from info derived elsewhere.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {object} osinfo Contains "type" and "arch" properties
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function detectPlatform(osinfo) {
 | 
				
			||||||
 | 
					  var inputIsValid = typeof osinfo === 'object' && typeof osinfo.type === 'string' && typeof osinfo.arch === 'string'
 | 
				
			||||||
 | 
					  var type = (inputIsValid ? osinfo.type : os.type()).toLowerCase()
 | 
				
			||||||
 | 
					  var arch = (inputIsValid ? osinfo.arch : os.arch()).toLowerCase()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (type === 'darwin') {
 | 
				
			||||||
 | 
					    return 'osx-64'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (type === 'windows_nt') {
 | 
				
			||||||
 | 
					    return arch === 'x64' ? 'windows-64' : 'windows-32'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (type === 'linux') {
 | 
				
			||||||
 | 
					    if (arch === 'arm' || arch === 'arm64') {
 | 
				
			||||||
 | 
					      return 'linux-armel'
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return arch === 'x64' ? 'linux-64' : 'linux-32'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return null
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Gets the binary filename (appends exe in Windows)
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {string} component "ffmpeg", "ffplay", "ffprobe" or "ffserver"
 | 
				
			||||||
 | 
					 * @param {platform} platform "ffmpeg", "ffplay", "ffprobe" or "ffserver"
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function getBinaryFilename(component, platform) {
 | 
				
			||||||
 | 
					  var platformCode = resolvePlatform(platform)
 | 
				
			||||||
 | 
					  if (platformCode === 'windows-32' || platformCode === 'windows-64') {
 | 
				
			||||||
 | 
					    return component + '.exe'
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return component
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function listPlatforms() {
 | 
				
			||||||
 | 
					  return ['osx-64', 'linux-32', 'linux-64', 'linux-armel', 'linux-armhf', 'windows-32', 'windows-64']
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @returns {Promise<string[]>} array of version strings
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function listVersions() {
 | 
				
			||||||
 | 
					  if (RUNTIME_CACHE.versionsAll) {
 | 
				
			||||||
 | 
					    return RUNTIME_CACHE.versionsAll
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return axios.get(API_URL).then((res) => {
 | 
				
			||||||
 | 
					    if (!res.data?.versions || !Object.keys(res.data.versions)?.length) {
 | 
				
			||||||
 | 
					      throw new Error(errorMsgs.parsingVersionList)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const versionKeys = Object.keys(res.data.versions)
 | 
				
			||||||
 | 
					    RUNTIME_CACHE.versionsAll = versionKeys
 | 
				
			||||||
 | 
					    return versionKeys
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Gets full data set from ffbinaries.com
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function getVersionData(version) {
 | 
				
			||||||
 | 
					  if (RUNTIME_CACHE[version]) {
 | 
				
			||||||
 | 
					    return RUNTIME_CACHE[version]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (version && typeof version !== 'string') {
 | 
				
			||||||
 | 
					    throw new Error(errorMsgs.incorrectVersionParam)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  var url = version ? '/version/' + version : '/latest'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return axios.get(`${API_URL}${url}`).then((res) => {
 | 
				
			||||||
 | 
					    RUNTIME_CACHE[version] = res.data
 | 
				
			||||||
 | 
					    return res.data
 | 
				
			||||||
 | 
					  }).catch((error) => {
 | 
				
			||||||
 | 
					    if (error.response?.status == 404) {
 | 
				
			||||||
 | 
					      throw new Error(errorMsgs.notFound)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      throw new Error(errorMsgs.connectionIssues)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Download file(s) and save them in the specified directory
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function downloadUrls(components, urls, opts) {
 | 
				
			||||||
 | 
					  const destinationDir = opts.destination
 | 
				
			||||||
 | 
					  const results = []
 | 
				
			||||||
 | 
					  const remappedUrls = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (components && !Array.isArray(components)) {
 | 
				
			||||||
 | 
					    components = [components]
 | 
				
			||||||
 | 
					  } else if (!components || !Array.isArray(components)) {
 | 
				
			||||||
 | 
					    components = []
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // returns an array of objects like this: {component: 'ffmpeg', url: 'https://...'}
 | 
				
			||||||
 | 
					  if (typeof urls === 'object') {
 | 
				
			||||||
 | 
					    for (const key in urls) {
 | 
				
			||||||
 | 
					      if (components.includes(key) && urls[key]) {
 | 
				
			||||||
 | 
					        remappedUrls.push({
 | 
				
			||||||
 | 
					          component: key,
 | 
				
			||||||
 | 
					          url: urls[key]
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async function extractZipToDestination(zipFilename) {
 | 
				
			||||||
 | 
					    const oldpath = path.join(destinationDir, zipFilename)
 | 
				
			||||||
 | 
					    const zip = new StreamZip.async({ file: oldpath })
 | 
				
			||||||
 | 
					    const count = await zip.extract(null, destinationDir)
 | 
				
			||||||
 | 
					    await zip.close()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  await async.each(remappedUrls, async function (urlObject) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const url = urlObject.url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const zipFilename = url.split('/').pop()
 | 
				
			||||||
 | 
					      const binFilenameBase = urlObject.component
 | 
				
			||||||
 | 
					      const binFilename = getBinaryFilename(binFilenameBase, opts.platform || detectPlatform())
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      let runningTotal = 0
 | 
				
			||||||
 | 
					      let totalFilesize
 | 
				
			||||||
 | 
					      let interval
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      if (typeof opts.tickerFn === 'function') {
 | 
				
			||||||
 | 
					        opts.tickerInterval = parseInt(opts.tickerInterval, 10)
 | 
				
			||||||
 | 
					        const tickerInterval = (!Number.isNaN(opts.tickerInterval)) ? opts.tickerInterval : 1000
 | 
				
			||||||
 | 
					        const tickData = { filename: zipFilename, progress: 0 }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Schedule next ticks
 | 
				
			||||||
 | 
					        interval = setInterval(function () {
 | 
				
			||||||
 | 
					          if (totalFilesize && runningTotal == totalFilesize) {
 | 
				
			||||||
 | 
					            return clearInterval(interval)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          tickData.progress = totalFilesize > -1 ? runningTotal / totalFilesize : 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          opts.tickerFn(tickData)
 | 
				
			||||||
 | 
					        }, tickerInterval)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // Check if file already exists in target directory
 | 
				
			||||||
 | 
					      const binPath = path.join(destinationDir, binFilename)
 | 
				
			||||||
 | 
					      if (!opts.force && await fse.pathExists(binPath)) {
 | 
				
			||||||
 | 
					        // if the accessSync method doesn't throw we know the binary already exists
 | 
				
			||||||
 | 
					        results.push({
 | 
				
			||||||
 | 
					          filename: binFilename,
 | 
				
			||||||
 | 
					          path: destinationDir,
 | 
				
			||||||
 | 
					          status: 'File exists',
 | 
				
			||||||
 | 
					          code: 'FILE_EXISTS'
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        clearInterval(interval)
 | 
				
			||||||
 | 
					        return
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (opts.quiet) clearInterval(interval)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const zipPath = path.join(destinationDir, zipFilename)
 | 
				
			||||||
 | 
					      const zipFileTempName = zipPath + '.part'
 | 
				
			||||||
 | 
					      const zipFileFinalName = zipPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const response = await axios({
 | 
				
			||||||
 | 
					        url,
 | 
				
			||||||
 | 
					        method: 'GET',
 | 
				
			||||||
 | 
					        responseType: 'stream'
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      totalFilesize = response.headers?.['content-length'] || []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const writer = fse.createWriteStream(zipFileTempName)
 | 
				
			||||||
 | 
					      response.data.on('data', (chunk) => {        
 | 
				
			||||||
 | 
					        runningTotal += chunk.length
 | 
				
			||||||
 | 
					      }) 
 | 
				
			||||||
 | 
					      response.data.pipe(writer)
 | 
				
			||||||
 | 
					      await finished(writer)
 | 
				
			||||||
 | 
					      await fse.rename(zipFileTempName, zipFileFinalName)
 | 
				
			||||||
 | 
					      await extractZipToDestination(zipFilename)
 | 
				
			||||||
 | 
					      await fse.remove(zipFileFinalName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      results.push({
 | 
				
			||||||
 | 
					        filename: binFilename,
 | 
				
			||||||
 | 
					        path: destinationDir,
 | 
				
			||||||
 | 
					        size: Math.floor(totalFilesize / 1024 / 1024 * 1000) / 1000 + 'MB',
 | 
				
			||||||
 | 
					        status: 'File extracted to destination (downloaded from "' + url + '")',
 | 
				
			||||||
 | 
					        code: 'DONE_CLEAN'
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    } catch (err) {
 | 
				
			||||||
 | 
					      console.error(`Failed to download or extract file for component: ${urlObject.component}`, err)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Gets binaries for the platform
 | 
				
			||||||
 | 
					 * It will get the data from ffbinaries, pick the correct files
 | 
				
			||||||
 | 
					 * and save it to the specified directory
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param {Array} components
 | 
				
			||||||
 | 
					 * @param {Object} [opts]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function downloadBinaries(components, opts = {}) {
 | 
				
			||||||
 | 
					  var platform = resolvePlatform(opts.platform) || detectPlatform()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  opts.destination = path.resolve(opts.destination || '.')
 | 
				
			||||||
 | 
					  ensureDirSync(opts.destination)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const versionData = await getVersionData(opts.version)
 | 
				
			||||||
 | 
					  const urls = versionData?.bin?.[platform]
 | 
				
			||||||
 | 
					  if (!urls) {
 | 
				
			||||||
 | 
					    throw new Error('No URLs!')
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return await downloadUrls(components, urls, opts)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = {
 | 
				
			||||||
 | 
					  downloadBinaries: downloadBinaries,
 | 
				
			||||||
 | 
					  getVersionData: getVersionData,
 | 
				
			||||||
 | 
					  listVersions: listVersions,
 | 
				
			||||||
 | 
					  listPlatforms: listPlatforms,
 | 
				
			||||||
 | 
					  detectPlatform: detectPlatform,
 | 
				
			||||||
 | 
					  resolvePlatform: resolvePlatform,
 | 
				
			||||||
 | 
					  getBinaryFilename: getBinaryFilename
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										74
									
								
								server/managers/BinaryManager.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								server/managers/BinaryManager.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					const path = require('path')
 | 
				
			||||||
 | 
					const which = require('../libs/which')
 | 
				
			||||||
 | 
					const fs = require('../libs/fsExtra')
 | 
				
			||||||
 | 
					const ffbinaries = require('../libs/ffbinaries')
 | 
				
			||||||
 | 
					const Logger = require('../Logger')
 | 
				
			||||||
 | 
					const fileUtils = require('../utils/fileUtils')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class BinaryManager {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  defaultRequiredBinaries = [
 | 
				
			||||||
 | 
					    { name: 'ffmpeg', envVariable: 'FFMPEG_PATH' },
 | 
				
			||||||
 | 
					    { name: 'ffprobe', envVariable: 'FFPROBE_PATH' }
 | 
				
			||||||
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(requiredBinaries = this.defaultRequiredBinaries) {
 | 
				
			||||||
 | 
					    this.requiredBinaries = requiredBinaries
 | 
				
			||||||
 | 
					    this.mainInstallPath = process.pkg ? path.dirname(process.execPath) : global.appRoot
 | 
				
			||||||
 | 
					    this.altInstallPath = global.ConfigPath
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async init() {
 | 
				
			||||||
 | 
					    if (this.initialized) return
 | 
				
			||||||
 | 
					    const missingBinaries = await this.findRequiredBinaries()
 | 
				
			||||||
 | 
					    if (missingBinaries.length == 0) return
 | 
				
			||||||
 | 
					    await this.install(missingBinaries)
 | 
				
			||||||
 | 
					    const missingBinariesAfterInstall = await this.findRequiredBinaries()
 | 
				
			||||||
 | 
					    if (missingBinariesAfterInstall.length != 0) {
 | 
				
			||||||
 | 
					      Logger.error(`[BinaryManager] Failed to find or install required binaries: ${missingBinariesAfterInstall.join(', ')}`)
 | 
				
			||||||
 | 
					      process.exit(1)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.initialized = true
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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}`)
 | 
				
			||||||
 | 
					        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`)
 | 
				
			||||||
 | 
					        missingBinaries.push(binary.name)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return missingBinaries
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async findBinary(name, envVariable) {
 | 
				
			||||||
 | 
					    const executable = name + (process.platform == 'win32' ? '.exe' : '')
 | 
				
			||||||
 | 
					    const defaultPath = process.env[envVariable]
 | 
				
			||||||
 | 
					    if (defaultPath && await fs.pathExists(defaultPath)) return defaultPath
 | 
				
			||||||
 | 
					    const whichPath = which.sync(executable, { nothrow: true })
 | 
				
			||||||
 | 
					    if (whichPath) return whichPath
 | 
				
			||||||
 | 
					    const mainInstallPath = path.join(this.mainInstallPath, executable)
 | 
				
			||||||
 | 
					    if (await fs.pathExists(mainInstallPath)) return mainInstallPath
 | 
				
			||||||
 | 
					    const altInstallPath = path.join(this.altInstallPath, executable)
 | 
				
			||||||
 | 
					    if (await fs.pathExists(altInstallPath)) return altInstallPath
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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 })
 | 
				
			||||||
 | 
					    Logger.info(`[BinaryManager] Binaries installed to ${destination}`)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = BinaryManager
 | 
				
			||||||
@ -359,3 +359,22 @@ module.exports.encodeUriPath = (path) => {
 | 
				
			|||||||
  const uri = new URL(path, "file://")
 | 
					  const uri = new URL(path, "file://")
 | 
				
			||||||
  return uri.pathname
 | 
					  return uri.pathname
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Check if directory is writable.
 | 
				
			||||||
 | 
					 * This method is necessary because fs.access(directory, fs.constants.W_OK) does not work on Windows
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @param {string} directory 
 | 
				
			||||||
 | 
					 * @returns {boolean}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					module.exports.isWritable = async (directory) => {
 | 
				
			||||||
 | 
					  try {
 | 
				
			||||||
 | 
					    const accessTestFile = path.join(directory, 'accessTest')
 | 
				
			||||||
 | 
					    await fs.writeFile(accessTestFile, '')
 | 
				
			||||||
 | 
					    await fs.remove(accessTestFile)
 | 
				
			||||||
 | 
					    return true
 | 
				
			||||||
 | 
					  } catch (err) {
 | 
				
			||||||
 | 
					    return false
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										264
									
								
								test/server/managers/BinaryManager.test.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										264
									
								
								test/server/managers/BinaryManager.test.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,264 @@
 | 
				
			|||||||
 | 
					const chai = require('chai')
 | 
				
			||||||
 | 
					const sinon = require('sinon')
 | 
				
			||||||
 | 
					const fs = require('../../../server/libs/fsExtra')
 | 
				
			||||||
 | 
					const fileUtils = require('../../../server/utils/fileUtils')
 | 
				
			||||||
 | 
					const which = require('../../../server/libs/which')
 | 
				
			||||||
 | 
					const ffbinaries = require('../../../server/libs/ffbinaries')
 | 
				
			||||||
 | 
					const path = require('path')
 | 
				
			||||||
 | 
					const BinaryManager = require('../../../server/managers/BinaryManager')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const expect = chai.expect
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('BinaryManager', () => {
 | 
				
			||||||
 | 
					  let binaryManager
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('init', () => {
 | 
				
			||||||
 | 
					    let findStub
 | 
				
			||||||
 | 
					    let installStub
 | 
				
			||||||
 | 
					    let errorStub
 | 
				
			||||||
 | 
					    let exitStub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beforeEach(() => {
 | 
				
			||||||
 | 
					      binaryManager = new BinaryManager()
 | 
				
			||||||
 | 
					      findStub = sinon.stub(binaryManager, 'findRequiredBinaries')
 | 
				
			||||||
 | 
					      installStub = sinon.stub(binaryManager, 'install')
 | 
				
			||||||
 | 
					      errorStub = sinon.stub(console, 'error')
 | 
				
			||||||
 | 
					      exitStub = sinon.stub(process, 'exit')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    afterEach(() => {
 | 
				
			||||||
 | 
					      findStub.restore()
 | 
				
			||||||
 | 
					      installStub.restore()
 | 
				
			||||||
 | 
					      errorStub.restore()
 | 
				
			||||||
 | 
					      exitStub.restore()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should not install binaries if they are already found', async () => {
 | 
				
			||||||
 | 
					      findStub.resolves([])
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      await binaryManager.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(installStub.called).to.be.false
 | 
				
			||||||
 | 
					      expect(findStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(errorStub.called).to.be.false
 | 
				
			||||||
 | 
					      expect(exitStub.called).to.be.false
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should install missing binaries', async () => {
 | 
				
			||||||
 | 
					      const missingBinaries = ['ffmpeg', 'ffprobe']
 | 
				
			||||||
 | 
					      const missingBinariesAfterInstall = []
 | 
				
			||||||
 | 
					      findStub.onFirstCall().resolves(missingBinaries)
 | 
				
			||||||
 | 
					      findStub.onSecondCall().resolves(missingBinariesAfterInstall)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await binaryManager.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(findStub.calledTwice).to.be.true
 | 
				
			||||||
 | 
					      expect(installStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(errorStub.called).to.be.false
 | 
				
			||||||
 | 
					      expect(exitStub.called).to.be.false
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('exit if binaries are not found after installation', async () => {
 | 
				
			||||||
 | 
					      const missingBinaries = ['ffmpeg', 'ffprobe']
 | 
				
			||||||
 | 
					      const missingBinariesAfterInstall = ['ffmpeg', 'ffprobe']
 | 
				
			||||||
 | 
					      findStub.onFirstCall().resolves(missingBinaries)
 | 
				
			||||||
 | 
					      findStub.onSecondCall().resolves(missingBinariesAfterInstall)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await binaryManager.init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(findStub.calledTwice).to.be.true
 | 
				
			||||||
 | 
					      expect(installStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(errorStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(exitStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(exitStub.calledWith(1)).to.be.true
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  describe('findRequiredBinaries', () => {
 | 
				
			||||||
 | 
					    let findBinaryStub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beforeEach(() => {
 | 
				
			||||||
 | 
					      const requiredBinaries = [{ name: 'ffmpeg', envVariable: 'FFMPEG_PATH' }]
 | 
				
			||||||
 | 
					      binaryManager = new BinaryManager(requiredBinaries)
 | 
				
			||||||
 | 
					      findBinaryStub = sinon.stub(binaryManager, 'findBinary')
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    afterEach(() => {
 | 
				
			||||||
 | 
					      findBinaryStub.restore()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should put found paths in the correct environment variables', async () => {
 | 
				
			||||||
 | 
					      const pathToFFmpeg = '/path/to/ffmpeg'
 | 
				
			||||||
 | 
					      const missingBinaries = []
 | 
				
			||||||
 | 
					      delete process.env.FFMPEG_PATH
 | 
				
			||||||
 | 
					      findBinaryStub.resolves(pathToFFmpeg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const result = await binaryManager.findRequiredBinaries()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(result).to.deep.equal(missingBinaries)
 | 
				
			||||||
 | 
					      expect(findBinaryStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(process.env.FFMPEG_PATH).to.equal(pathToFFmpeg)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should add missing binaries to result', async () => {
 | 
				
			||||||
 | 
					      const missingBinaries = ['ffmpeg']
 | 
				
			||||||
 | 
					      delete process.env.FFMPEG_PATH    
 | 
				
			||||||
 | 
					      findBinaryStub.resolves(null)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const result = await binaryManager.findRequiredBinaries()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(result).to.deep.equal(missingBinaries)
 | 
				
			||||||
 | 
					      expect(findBinaryStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(process.env.FFMPEG_PATH).to.be.undefined
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  describe('install', () => {
 | 
				
			||||||
 | 
					    let isWritableStub
 | 
				
			||||||
 | 
					    let downloadBinariesStub
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    beforeEach(() => {
 | 
				
			||||||
 | 
					      binaryManager = new BinaryManager()
 | 
				
			||||||
 | 
					      isWritableStub = sinon.stub(fileUtils, 'isWritable')
 | 
				
			||||||
 | 
					      downloadBinariesStub = sinon.stub(ffbinaries, 'downloadBinaries')
 | 
				
			||||||
 | 
					      binaryManager.mainInstallPath = '/path/to/main/install'
 | 
				
			||||||
 | 
					      binaryManager.altInstallPath = '/path/to/alt/install'
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    afterEach(() => {
 | 
				
			||||||
 | 
					      isWritableStub.restore()
 | 
				
			||||||
 | 
					      downloadBinariesStub.restore()
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should not install binaries if no binaries are passed', async () => {
 | 
				
			||||||
 | 
					      const binaries = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await binaryManager.install(binaries)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(isWritableStub.called).to.be.false
 | 
				
			||||||
 | 
					      expect(downloadBinariesStub.called).to.be.false
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should install binaries in main install path if has access', async () => {
 | 
				
			||||||
 | 
					      const binaries = ['ffmpeg']
 | 
				
			||||||
 | 
					      const destination = binaryManager.mainInstallPath
 | 
				
			||||||
 | 
					      isWritableStub.withArgs(destination).resolves(true)
 | 
				
			||||||
 | 
					      downloadBinariesStub.resolves()
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      await binaryManager.install(binaries)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(isWritableStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(downloadBinariesStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(downloadBinariesStub.calledWith(binaries, sinon.match({ destination: destination }))).to.be.true  
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should install binaries in alt install path if has no access to main', async () => {
 | 
				
			||||||
 | 
					      const binaries = ['ffmpeg']
 | 
				
			||||||
 | 
					      const mainDestination = binaryManager.mainInstallPath
 | 
				
			||||||
 | 
					      const destination = binaryManager.altInstallPath
 | 
				
			||||||
 | 
					      isWritableStub.withArgs(mainDestination).resolves(false)
 | 
				
			||||||
 | 
					      downloadBinariesStub.resolves()
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      await binaryManager.install(binaries)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(isWritableStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(downloadBinariesStub.calledOnce).to.be.true
 | 
				
			||||||
 | 
					      expect(downloadBinariesStub.calledWith(binaries, sinon.match({ destination: destination }))).to.be.true  
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe('findBinary', () => {
 | 
				
			||||||
 | 
					  let binaryManager
 | 
				
			||||||
 | 
					  let fsPathExistsStub
 | 
				
			||||||
 | 
					  let whichSyncStub
 | 
				
			||||||
 | 
					  let mainInstallPath
 | 
				
			||||||
 | 
					  let altInstallPath
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const name = 'ffmpeg'
 | 
				
			||||||
 | 
					  const envVariable = 'FFMPEG_PATH'
 | 
				
			||||||
 | 
					  const defaultPath = '/path/to/ffmpeg'
 | 
				
			||||||
 | 
					  const executable = name + (process.platform == 'win32' ? '.exe' : '')
 | 
				
			||||||
 | 
					  const whichPath = '/usr/bin/ffmpeg'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(() => {
 | 
				
			||||||
 | 
					    binaryManager = new BinaryManager()
 | 
				
			||||||
 | 
					    fsPathExistsStub = sinon.stub(fs, 'pathExists')
 | 
				
			||||||
 | 
					    whichSyncStub = sinon.stub(which, 'sync')
 | 
				
			||||||
 | 
					    binaryManager.mainInstallPath = '/path/to/main/install'
 | 
				
			||||||
 | 
					    mainInstallPath = path.join(binaryManager.mainInstallPath, executable)
 | 
				
			||||||
 | 
					    binaryManager.altInstallPath = '/path/to/alt/install'
 | 
				
			||||||
 | 
					    altInstallPath = path.join(binaryManager.altInstallPath, executable)
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  afterEach(() => {
 | 
				
			||||||
 | 
					    fsPathExistsStub.restore()
 | 
				
			||||||
 | 
					    whichSyncStub.restore()
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should return defaultPath if it exists', async () => {
 | 
				
			||||||
 | 
					    process.env[envVariable] = defaultPath
 | 
				
			||||||
 | 
					    fsPathExistsStub.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
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should return whichPath if it exists', async () => {
 | 
				
			||||||
 | 
					    delete process.env[envVariable]
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should return mainInstallPath if it exists', async () => {
 | 
				
			||||||
 | 
					    delete process.env[envVariable]
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should return altInstallPath if it exists', async () => {
 | 
				
			||||||
 | 
					    delete process.env[envVariable]
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const result = await binaryManager.findBinary(name, envVariable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user