diff --git a/lib/image-stream/handlers.js b/lib/image-stream/handlers.js index 04f1c527..6c32d50a 100644 --- a/lib/image-stream/handlers.js +++ b/lib/image-stream/handlers.js @@ -33,6 +33,13 @@ const fileExtensions = require('../shared/file-extensions') const path = require('path') const errors = require('../shared/errors') +/** + * @summary Default image extension to be assumed + * @type {String} + * @constant + */ +const DEFAULT_EXT = 'img' + /** * @summary Image handlers * @namespace handlers @@ -57,7 +64,7 @@ module.exports = { return { path: imagePath, archiveExtension: fileExtensions.getLastFileExtension(imagePath), - extension: fileExtensions.getPenultimateFileExtension(imagePath), + extension: fileExtensions.getPenultimateFileExtension(imagePath) || DEFAULT_EXT, stream: fs.createReadStream(imagePath), size: { original: options.size, @@ -97,7 +104,7 @@ module.exports = { return { path: imagePath, archiveExtension: fileExtensions.getLastFileExtension(imagePath), - extension: fileExtensions.getPenultimateFileExtension(imagePath), + extension: fileExtensions.getPenultimateFileExtension(imagePath) || DEFAULT_EXT, stream: fs.createReadStream(imagePath), size: { original: options.size, @@ -138,7 +145,7 @@ module.exports = { return { path: imagePath, archiveExtension: fileExtensions.getLastFileExtension(imagePath), - extension: fileExtensions.getPenultimateFileExtension(imagePath), + extension: fileExtensions.getPenultimateFileExtension(imagePath) || DEFAULT_EXT, stream: fs.createReadStream(imagePath), size: { original: options.size, diff --git a/lib/shared/file-extensions.js b/lib/shared/file-extensions.js index dd5a4eb6..05c27b92 100644 --- a/lib/shared/file-extensions.js +++ b/lib/shared/file-extensions.js @@ -16,6 +16,7 @@ 'use strict' +const mime = require('mime-types') const _ = require('lodash') /** @@ -45,15 +46,15 @@ exports.getFileExtensions = _.memoize((filePath) => { * @public * * @param {String} filePath - file path - * @returns {(String|Undefined)} last extension + * @returns {(String|Null)} last extension * * @example * const extension = fileExtensions.getLastFileExtension('path/to/foo.img.gz'); * console.log(extension); - * > [ 'gz' ] + * > 'gz' */ exports.getLastFileExtension = (filePath) => { - return _.last(exports.getFileExtensions(filePath)) + return _.last(exports.getFileExtensions(filePath)) || null } /** @@ -62,13 +63,14 @@ exports.getLastFileExtension = (filePath) => { * @public * * @param {String} filePath - file path - * @returns {(String|Undefined)} penultimate extension + * @returns {(String|Null)} penultimate extension * * @example * const extension = fileExtensions.getPenultimateFileExtension('path/to/foo.img.gz'); * console.log(extension); - * > [ 'img' ] + * > 'img' */ exports.getPenultimateFileExtension = (filePath) => { - return _.last(_.initial(exports.getFileExtensions(filePath))) + const ext = _.last(_.initial(exports.getFileExtensions(filePath))) + return !_.isNil(ext) && mime.lookup(ext) ? ext : null } diff --git a/lib/shared/store.js b/lib/shared/store.js index 5421b843..6141ea0c 100644 --- a/lib/shared/store.js +++ b/lib/shared/store.js @@ -311,6 +311,10 @@ const storeReducer = (state = DEFAULT_STATE, action) => { return state.setIn([ 'selection', 'drive' ], Immutable.fromJS(action.data)) } + // TODO(jhermsmeier): Consolidate these assertions + // with image-stream / supported-formats, and have *one* + // place where all the image extension / format handling + // takes place, to avoid having to check 2+ locations with different logic case ACTIONS.SELECT_IMAGE: { if (!action.data.path) { throw errors.createError({ diff --git a/lib/shared/supported-formats.js b/lib/shared/supported-formats.js index 86bad68c..8897df57 100644 --- a/lib/shared/supported-formats.js +++ b/lib/shared/supported-formats.js @@ -122,10 +122,15 @@ exports.isSupportedImage = (imagePath) => { return true } - return _.every([ + if (_.every([ _.includes(exports.getCompressedExtensions(), lastExtension), _.includes(exports.getNonCompressedExtensions(), penultimateExtension) - ]) + ])) { + return true + } + + return _.isNil(penultimateExtension) && + _.includes(exports.getCompressedExtensions(), lastExtension) } /** diff --git a/tests/shared/file-extensions.spec.js b/tests/shared/file-extensions.spec.js index 8f023d17..7acc037f 100644 --- a/tests/shared/file-extensions.spec.js +++ b/tests/shared/file-extensions.spec.js @@ -101,7 +101,7 @@ describe('Shared: fileExtensions', function () { describe('.getLastFileExtension()', function () { it('should return undefined if the file path has no extension', function () { - m.chai.expect(fileExtensions.getLastFileExtension('foo')).to.be.undefined + m.chai.expect(fileExtensions.getLastFileExtension('foo')).to.equal(null) }) it('should return the extension if there is only one extension', function () { @@ -119,11 +119,11 @@ describe('Shared: fileExtensions', function () { describe('.getPenultimateFileExtension()', function () { it('should return undefined in the file path has no extension', function () { - m.chai.expect(fileExtensions.getPenultimateFileExtension('foo')).to.be.undefined + m.chai.expect(fileExtensions.getPenultimateFileExtension('foo')).to.equal(null) }) it('should return undefined if there is only one extension', function () { - m.chai.expect(fileExtensions.getPenultimateFileExtension('foo.img')).to.be.undefined + m.chai.expect(fileExtensions.getPenultimateFileExtension('foo.img')).to.equal(null) }) it('should return the penultimate extension if there are two extensions', function () { diff --git a/tests/shared/models/selection-state.spec.js b/tests/shared/models/selection-state.spec.js index 13c94065..cc71c249 100644 --- a/tests/shared/models/selection-state.spec.js +++ b/tests/shared/models/selection-state.spec.js @@ -370,6 +370,42 @@ describe('Model: selectionState', function () { m.chai.expect(imagePath).to.equal('foo.zip') }) + it('should infer a compressed raw image if the penultimate extension is missing', function () { + selectionState.setImage({ + path: 'foo.xz', + extension: 'img', + archiveExtension: 'xz', + size: { + original: 999999999, + final: { + estimation: false, + value: 999999999 + } + } + }) + + const imagePath = selectionState.getImagePath() + m.chai.expect(imagePath).to.equal('foo.xz') + }) + + it('should infer a compressed raw image if the penultimate extension is not a file extension', function () { + selectionState.setImage({ + path: 'something.linux-x86-64.gz', + extension: 'img', + archiveExtension: 'gz', + size: { + original: 999999999, + final: { + estimation: false, + value: 999999999 + } + } + }) + + const imagePath = selectionState.getImagePath() + m.chai.expect(imagePath).to.equal('something.linux-x86-64.gz') + }) + it('should throw if no path', function () { m.chai.expect(function () { selectionState.setImage({