mirror of
https://github.com/balena-io/etcher.git
synced 2025-04-24 07:17:18 +00:00
Merge pull request #182 from resin-io/feat/zip
Add support for zip images in select image dialog
This commit is contained in:
commit
b0129eba3e
@ -128,6 +128,13 @@ app.controller('AppController', function(
|
||||
|
||||
this.selectImage = function() {
|
||||
return $q.when(dialog.selectImage()).then(function(image) {
|
||||
|
||||
// Avoid analytics and selection state changes
|
||||
// if no file was resolved from the dialog.
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.selection.setImage(image);
|
||||
AnalyticsService.logEvent('Select image', {
|
||||
image: image
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="space-vertical-large">
|
||||
<div ng-hide="app.selection.hasImage()">
|
||||
<hero-button ng-click="app.selectImage()">Select image</hero-button>
|
||||
<p class="step-footer tiny">*supported files: .img, .iso</p>
|
||||
<p class="step-footer tiny">*supported files: .img, .iso, .zip</p>
|
||||
</div>
|
||||
<div ng-show="app.selection.hasImage()">
|
||||
<span ng-bind="app.selection.getImage() | basename" ng-click="app.reselectImage()"></span>
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
const electron = require('electron');
|
||||
const Bluebird = require('bluebird');
|
||||
const zipImage = require('resin-zip-image');
|
||||
|
||||
/**
|
||||
* @summary Open an image selection dialog
|
||||
@ -25,7 +26,10 @@ const Bluebird = require('bluebird');
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* Notice that by image, we mean *.img/*.iso files.
|
||||
* Notice that by image, we mean *.img/*.iso/*.zip files.
|
||||
*
|
||||
* If the user selects an invalid zip image, an error alert
|
||||
* is shown, and the promise resolves `undefined`.
|
||||
*
|
||||
* @fulfil {String} - selected image
|
||||
* @returns {Promise};
|
||||
@ -41,15 +45,27 @@ exports.selectImage = function() {
|
||||
properties: [ 'openFile' ],
|
||||
filters: [
|
||||
{
|
||||
name: 'IMG/ISO',
|
||||
name: 'IMG/ISO/ZIP',
|
||||
extensions: [
|
||||
'zip',
|
||||
'img',
|
||||
'iso'
|
||||
]
|
||||
}
|
||||
]
|
||||
}, resolve);
|
||||
}).get(0);
|
||||
}).get(0).then(function(file) {
|
||||
if (zipImage.isZip(file) && !zipImage.isValidZipImage(file)) {
|
||||
electron.dialog.showErrorBox(
|
||||
'Invalid zip image',
|
||||
'Etcher can only open Zip archives that contain exactly one image file inside.'
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return file;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@
|
||||
'use strict';
|
||||
|
||||
const imageWrite = require('resin-image-write');
|
||||
const zipImage = require('resin-zip-image');
|
||||
const Bluebird = require('bluebird');
|
||||
const umount = Bluebird.promisifyAll(require('umount'));
|
||||
const fs = require('fs');
|
||||
@ -43,9 +44,17 @@ if (isWindows) {
|
||||
* const stream = writer.getImageStream('foo/bar/baz.img');
|
||||
*/
|
||||
exports.getImageStream = function(image) {
|
||||
if (zipImage.isZip(image)) {
|
||||
if (!zipImage.isValidZipImage(image)) {
|
||||
return Bluebird.reject(new Error('Invalid zip image'));
|
||||
}
|
||||
|
||||
return zipImage.extractImage(image);
|
||||
}
|
||||
|
||||
let stream = fs.createReadStream(image);
|
||||
stream.length = fs.statSync(image).size;
|
||||
return stream;
|
||||
return Bluebird.resolve(stream);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -78,7 +87,8 @@ exports.getImageStream = function(image) {
|
||||
*/
|
||||
exports.writeImage = function(image, drive, options, onProgress) {
|
||||
return umount.umountAsync(drive.device).then(function() {
|
||||
let stream = exports.getImageStream(image);
|
||||
return exports.getImageStream(image);
|
||||
}).then(function(stream) {
|
||||
return imageWrite.write(drive.device, stream);
|
||||
}).then(function(writer) {
|
||||
return new Bluebird(function(resolve, reject) {
|
||||
|
@ -17,3 +17,5 @@ node_modules/gulp*
|
||||
node_modules/jshint-stylish
|
||||
node_modules/mochainon
|
||||
node_modules/vinyl-*
|
||||
node_modules/rindle
|
||||
node_modules/tmp
|
||||
|
@ -61,6 +61,7 @@
|
||||
"lodash": "^4.5.1",
|
||||
"ngstorage": "^0.3.10",
|
||||
"resin-image-write": "^2.0.5",
|
||||
"resin-zip-image": "^1.1.1",
|
||||
"sudo-prompt": "^2.2.0",
|
||||
"trackjs": "^2.1.16",
|
||||
"umount": "^1.1.1",
|
||||
@ -78,6 +79,8 @@
|
||||
"gulp-sass": "^2.0.4",
|
||||
"jshint": "^2.9.1",
|
||||
"jshint-stylish": "^2.0.1",
|
||||
"mochainon": "^1.0.0"
|
||||
"mochainon": "^1.0.0",
|
||||
"rindle": "^1.3.0",
|
||||
"tmp": "0.0.28"
|
||||
}
|
||||
}
|
||||
|
@ -2,27 +2,84 @@
|
||||
|
||||
const m = require('mochainon');
|
||||
const ReadableStream = require('stream').Readable;
|
||||
const Bluebird = require('bluebird');
|
||||
const fs = Bluebird.promisifyAll(require('fs'));
|
||||
const path = require('path');
|
||||
const tmp = require('tmp');
|
||||
const rindle = require('rindle');
|
||||
const writer = require('../../lib/src/writer');
|
||||
|
||||
describe('Writer:', function() {
|
||||
|
||||
describe('.getImageStream()', function() {
|
||||
|
||||
describe('given a valid image', function() {
|
||||
describe('given a valid image file', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.image = path.join(__dirname, '..', 'utils', 'data.random');
|
||||
});
|
||||
|
||||
it('should return a readable stream', function() {
|
||||
const stream = writer.getImageStream(this.image);
|
||||
m.chai.expect(stream).to.be.an.instanceof(ReadableStream);
|
||||
it('should return a readable stream', function(done) {
|
||||
writer.getImageStream(this.image).then(function(stream) {
|
||||
m.chai.expect(stream).to.be.an.instanceof(ReadableStream);
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
it('should append a .length property with the correct size', function() {
|
||||
const stream = writer.getImageStream(this.image);
|
||||
m.chai.expect(stream.length).to.equal(2097152);
|
||||
it('should append a .length property with the correct size', function(done) {
|
||||
writer.getImageStream(this.image).then(function(stream) {
|
||||
m.chai.expect(stream.length).to.equal(2097152);
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given a valid image zip', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.image = path.join(__dirname, '..', 'utils', 'data.zip');
|
||||
});
|
||||
|
||||
it('should return a readable stream', function(done) {
|
||||
writer.getImageStream(this.image).then(function(stream) {
|
||||
m.chai.expect(stream).to.be.an.instanceof(ReadableStream);
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
it('should append a .length property with the correct size', function(done) {
|
||||
writer.getImageStream(this.image).then(function(stream) {
|
||||
m.chai.expect(stream.length).to.equal(2097152);
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
it('should pipe the image from the zip', function(done) {
|
||||
const tmpFile = tmp.tmpNameSync();
|
||||
const image = path.join(__dirname, '..', 'utils', 'data.random');
|
||||
const output = fs.createWriteStream(tmpFile);
|
||||
|
||||
writer.getImageStream(this.image).then(function(stream) {
|
||||
return stream.pipe(output);
|
||||
}).then(rindle.wait).then(function() {
|
||||
return Bluebird.props({
|
||||
output: fs.readFileAsync(tmpFile),
|
||||
data: fs.readFileAsync(image)
|
||||
});
|
||||
}).then(function(results) {
|
||||
m.chai.expect(results.output).to.deep.equal(results.data);
|
||||
return fs.unlinkAsync(tmpFile);
|
||||
}).nodeify(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an invalid image zip', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.image = path.join(__dirname, '..', 'utils', 'invalid.zip');
|
||||
});
|
||||
|
||||
it('should be rejected with an error', function() {
|
||||
const promise = writer.getImageStream(this.image);
|
||||
m.chai.expect(promise).to.be.rejectedWith('Invalid zip image');
|
||||
});
|
||||
|
||||
});
|
||||
|
BIN
tests/utils/data.zip
Normal file
BIN
tests/utils/data.zip
Normal file
Binary file not shown.
BIN
tests/utils/invalid.zip
Normal file
BIN
tests/utils/invalid.zip
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user