mirror of
https://github.com/balena-io/etcher.git
synced 2025-07-21 02:06:33 +00:00
Initial commit
This commit is contained in:
commit
4bfb161e5c
10
.editorconfig
Normal file
10
.editorconfig
Normal file
@ -0,0 +1,10 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
32
.gitignore
vendored
Normal file
32
.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directory
|
||||
# https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git
|
||||
node_modules
|
||||
bower_components
|
||||
|
||||
# Compiled Electron releases
|
||||
release/
|
116
.jshintrc
Normal file
116
.jshintrc
Normal file
@ -0,0 +1,116 @@
|
||||
{
|
||||
// --------------------------------------------------------------------
|
||||
// JSHint Configuration, Strict Edition
|
||||
// --------------------------------------------------------------------
|
||||
//
|
||||
// This is a options template for [JSHint][1], using [JSHint example][2]
|
||||
// and [Ory Band's example][3] as basis and setting config values to
|
||||
// be most strict:
|
||||
//
|
||||
// * set all enforcing options to true
|
||||
// * set all relaxing options to false
|
||||
// * set all environment options to false, except the browser value
|
||||
// * set all JSLint legacy options to false
|
||||
//
|
||||
// [1]: http://www.jshint.com/
|
||||
// [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json
|
||||
// [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc
|
||||
//
|
||||
// @author http://michael.haschke.biz/
|
||||
// @license http://unlicense.org/
|
||||
|
||||
// == Enforcing Options ===============================================
|
||||
//
|
||||
// These options tell JSHint to be more strict towards your code. Use
|
||||
// them if you want to allow only a safe subset of JavaScript, very
|
||||
// useful when your codebase is shared with a big number of developers
|
||||
// with different skill levels.
|
||||
|
||||
"bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.).
|
||||
"curly" : true, // Require {} for every new block or scope.
|
||||
"eqeqeq" : true, // Require triple equals i.e. `===`.
|
||||
"forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`.
|
||||
"immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );`
|
||||
"latedef" : true, // Prohibit variable use before definition.
|
||||
"newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`.
|
||||
"noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`.
|
||||
"noempty" : true, // Prohibit use of empty blocks.
|
||||
"nonew" : true, // Prohibit use of constructors for side-effects.
|
||||
"plusplus" : true, // Prohibit use of `++` & `--`.
|
||||
"regexp" : true, // Prohibit `.` and `[^...]` in regular expressions.
|
||||
"undef" : true, // Require all non-global variables be declared before they are used.
|
||||
"strict" : true, // Require `use strict` pragma in every file.
|
||||
"trailing" : true, // Prohibit trailing whitespaces.
|
||||
|
||||
// == Relaxing Options ================================================
|
||||
//
|
||||
// These options allow you to suppress certain types of warnings. Use
|
||||
// them only if you are absolutely positive that you know what you are
|
||||
// doing.
|
||||
|
||||
"asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons).
|
||||
"boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments.
|
||||
"debug" : false, // Allow debugger statements e.g. browser breakpoints.
|
||||
"eqnull" : false, // Tolerate use of `== null`.
|
||||
"es5" : false, // Allow EcmaScript 5 syntax.
|
||||
"esnext" : false, // Allow ES.next specific features such as `const` and `let`.
|
||||
"evil" : false, // Tolerate use of `eval`.
|
||||
"expr" : true, // Tolerate `ExpressionStatement` as Programs.
|
||||
"funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside.
|
||||
"globalstrict" : false, // Allow global "use strict" (also enables 'strict').
|
||||
"iterator" : false, // Allow usage of __iterator__ property.
|
||||
"lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block.
|
||||
"laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons.
|
||||
"laxcomma" : false, // Suppress warnings about comma-first coding style.
|
||||
"loopfunc" : false, // Allow functions to be defined within loops.
|
||||
"multistr" : false, // Tolerate multi-line strings.
|
||||
"onecase" : false, // Tolerate switches with just one case.
|
||||
"proto" : false, // Tolerate __proto__ property. This property is deprecated.
|
||||
"regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`.
|
||||
"scripturl" : false, // Tolerate script-targeted URLs.
|
||||
"smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only.
|
||||
"shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`.
|
||||
"sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`.
|
||||
"supernew" : false, // Tolerate `new function () { ... };` and `new Object;`.
|
||||
"validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function.
|
||||
|
||||
// == Environments ====================================================
|
||||
//
|
||||
// These options pre-define global variables that are exposed by
|
||||
// popular JavaScript libraries and runtime environments—such as
|
||||
// browser or node.js.
|
||||
|
||||
"browser" : true, // Standard browser globals e.g. `window`, `document`.
|
||||
"couch" : false, // Enable globals exposed by CouchDB.
|
||||
"devel" : false, // Allow development statements e.g. `console.log();`.
|
||||
"dojo" : false, // Enable globals exposed by Dojo Toolkit.
|
||||
"esnext" : true, // Enable globals exposed by ES6.
|
||||
"mocha" : true, // Enable globals exposed by Mocha.
|
||||
"jquery" : false, // Enable globals exposed by jQuery JavaScript library.
|
||||
"mootools" : false, // Enable globals exposed by MooTools JavaScript framework.
|
||||
"node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment.
|
||||
"nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape.
|
||||
"prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework.
|
||||
"rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment.
|
||||
"wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host.
|
||||
|
||||
// == JSLint Legacy ===================================================
|
||||
//
|
||||
// These options are legacy from JSLint. Aside from bug fixes they will
|
||||
// not be improved in any way and might be removed at any point.
|
||||
|
||||
"nomen" : false, // Prohibit use of initial or trailing underbars in names.
|
||||
"onevar" : false, // Allow only one `var` statement per function.
|
||||
"passfail" : false, // Stop on first error.
|
||||
"white" : false, // Check against strict whitespace and indentation rules.
|
||||
|
||||
// == Undocumented Options ============================================
|
||||
//
|
||||
// While I've found these options in [example1][2] and [example2][3]
|
||||
// they are not described in the [JSHint Options documentation][4].
|
||||
//
|
||||
// [4]: http://www.jshint.com/options/
|
||||
|
||||
"maxerr" : 100, // Maximum errors before stopping.
|
||||
"indent" : 4 // Specify indentation spacing
|
||||
}
|
8
.travis.yml
Normal file
8
.travis.yml
Normal file
@ -0,0 +1,8 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "4.0"
|
||||
- "0.12"
|
||||
- "0.10"
|
||||
- "iojs"
|
||||
before_script:
|
||||
- npm install -g electron-prebuilt
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@ -0,0 +1,21 @@
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
39
README.md
Normal file
39
README.md
Normal file
@ -0,0 +1,39 @@
|
||||
Herostratus
|
||||
===========
|
||||
|
||||
[](https://david-dm.org/resin-io/herostratus.png)
|
||||
[](https://travis-ci.org/resin-io/herostratus)
|
||||
[](https://ci.appveyor.com/project/resin-io/herostratus/branch/master)
|
||||
|
||||
The easy way to burn images in all operating systems
|
||||
----------------------------------------------------
|
||||
|
||||
An image burner with support for Windows, OS X and GNU/Linux.
|
||||
|
||||

|
||||
|
||||
**Notice:** Herostratus is in a very early state and things might break or not work at all in certain setups.
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
We're working on providing installers for all major operating systems.
|
||||
|
||||
For now you can manually run the application with the following commands:
|
||||
|
||||
```sh
|
||||
git clone https://github.com/resin-io/herostratus
|
||||
cd herostratus
|
||||
npm install && bower install
|
||||
npm start
|
||||
```
|
||||
|
||||
Support
|
||||
-------
|
||||
|
||||
If you're having any problem, please [raise an issue](https://github.com/resin-io/herostratus/issues/new) on GitHub and the Resin.io team will be happy to help.
|
||||
|
||||
License
|
||||
-------
|
||||
|
||||
Herostratus is free software, and may be redistributed under the terms specified in the [license](https://github.com/resin-io/herostratus/blob/master/LICENSE).
|
31
appveyor.yml
Normal file
31
appveyor.yml
Normal file
@ -0,0 +1,31 @@
|
||||
# appveyor file
|
||||
# http://www.appveyor.com/docs/appveyor-yml
|
||||
|
||||
init:
|
||||
- git config --global core.autocrlf input
|
||||
|
||||
cache:
|
||||
- C:\Users\appveyor\.node-gyp
|
||||
- '%AppData%\npm-cache'
|
||||
|
||||
# what combinations to test
|
||||
environment:
|
||||
matrix:
|
||||
- nodejs_version: 0.10
|
||||
- nodejs_version: 0.12
|
||||
- nodejs_version: 4
|
||||
|
||||
install:
|
||||
- ps: Install-Product node $env:nodejs_version x64
|
||||
- npm -g install npm@2
|
||||
- set PATH=%APPDATA%\npm;%PATH%
|
||||
- npm install -g electron-prebuilt
|
||||
- npm install
|
||||
|
||||
build: off
|
||||
|
||||
test_script:
|
||||
- node --version
|
||||
- npm --version
|
||||
- ps: npm test
|
||||
- cmd: npm test
|
21
bower.json
Normal file
21
bower.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "herostratus",
|
||||
"version": "0.0.1",
|
||||
"homepage": "https://github.com/resin-io/herostratus",
|
||||
"authors": [
|
||||
"Juan Cruz Viotti <juan@resin.io>"
|
||||
],
|
||||
"main": "lib/templates/index.html",
|
||||
"license": "MIT",
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
"dependencies": {
|
||||
"iron-icon": "polymerelements/iron-icon#~1.0.7",
|
||||
"polymer": "Polymer/polymer#^1.1.0"
|
||||
}
|
||||
}
|
50373
build/browser/app.js
Normal file
50373
build/browser/app.js
Normal file
File diff suppressed because it is too large
Load Diff
5996
build/css/main.css
Normal file
5996
build/css/main.css
Normal file
File diff suppressed because it is too large
Load Diff
58
gulpfile.js
Normal file
58
gulpfile.js
Normal file
@ -0,0 +1,58 @@
|
||||
var gulp = require('gulp');
|
||||
var jshint = require('gulp-jshint');
|
||||
var jshintStylish = require('jshint-stylish');
|
||||
var sass = require('gulp-sass');
|
||||
var browserify = require('browserify');
|
||||
var source = require('vinyl-source-stream');
|
||||
var buffer = require('vinyl-buffer');
|
||||
|
||||
var paths = {
|
||||
scripts: [
|
||||
'./tests/**/*.spec.js',
|
||||
'./lib/**/*.js',
|
||||
'gulpfile.js'
|
||||
],
|
||||
sass: [
|
||||
'./lib/scss/**/*.scss'
|
||||
]
|
||||
};
|
||||
|
||||
gulp.task('sass', function() {
|
||||
'use strict';
|
||||
|
||||
return gulp.src(paths.sass)
|
||||
.pipe(sass().on('error', sass.logError))
|
||||
.pipe(gulp.dest('./build/css'));
|
||||
});
|
||||
|
||||
gulp.task('lint', function() {
|
||||
'use strict';
|
||||
|
||||
return gulp.src(paths.scripts)
|
||||
.pipe(jshint())
|
||||
.pipe(jshint.reporter(jshintStylish));
|
||||
});
|
||||
|
||||
gulp.task('javascript', function() {
|
||||
'use strict';
|
||||
|
||||
var b = browserify({
|
||||
entries: './lib/browser/app.js',
|
||||
|
||||
// No need for Browserify builtins since Electron
|
||||
// has access to all NodeJS libraries
|
||||
builtins: {}
|
||||
});
|
||||
|
||||
return b.bundle()
|
||||
.pipe(source('app.js'))
|
||||
.pipe(buffer())
|
||||
.pipe(gulp.dest('./build/browser/'));
|
||||
});
|
||||
|
||||
gulp.task('watch', [ 'lint', 'javascript', 'sass' ], function() {
|
||||
'use strict';
|
||||
|
||||
gulp.watch(paths.scripts, [ 'lint', 'javascript' ]);
|
||||
gulp.watch(paths.sass, [ 'sass' ]);
|
||||
});
|
78
lib/browser/app.js
Normal file
78
lib/browser/app.js
Normal file
@ -0,0 +1,78 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module herostratus
|
||||
*/
|
||||
|
||||
var angular = require('angular');
|
||||
var remote = window.require('remote');
|
||||
var shell = remote.require('shell');
|
||||
var dialog = remote.require('./src/dialog');
|
||||
|
||||
require('angular-ui-bootstrap');
|
||||
require('./modules/selection-state');
|
||||
require('./modules/drive-scanner');
|
||||
require('./modules/image-writer');
|
||||
require('./modules/path');
|
||||
|
||||
var app = angular.module('Herostratus', [
|
||||
'ui.bootstrap',
|
||||
|
||||
// Herostratus modules
|
||||
'herostratus.path',
|
||||
'herostratus.selection-state',
|
||||
'herostratus.drive-scanner',
|
||||
'herostratus.image-writer'
|
||||
]);
|
||||
|
||||
app.controller('AppController', function($q, DriveScannerService, SelectionStateService, ImageWriterService) {
|
||||
'use strict';
|
||||
|
||||
var self = this;
|
||||
this.selection = SelectionStateService;
|
||||
this.writer = ImageWriterService;
|
||||
this.scanner = DriveScannerService;
|
||||
this.scanner.start(2000);
|
||||
|
||||
this.selectImage = function() {
|
||||
return $q.when(dialog.selectImage()).then(function(image) {
|
||||
self.selection.setImage(image);
|
||||
console.debug('Image selected: ' + image);
|
||||
});
|
||||
};
|
||||
|
||||
this.selectDrive = function(drive) {
|
||||
self.selection.setDrive(drive);
|
||||
console.debug('Drive selected: ' + drive);
|
||||
};
|
||||
|
||||
this.burn = function(image, drive) {
|
||||
console.debug('Burning ' + image + ' to ' + drive);
|
||||
return self.writer.burn(image, drive).then(function() {
|
||||
console.debug('Done!');
|
||||
});
|
||||
};
|
||||
|
||||
this.open = shell.openExternal;
|
||||
});
|
170
lib/browser/modules/drive-scanner.js
Normal file
170
lib/browser/modules/drive-scanner.js
Normal file
@ -0,0 +1,170 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module herostratus.drive-scanner
|
||||
*/
|
||||
|
||||
var angular = require('angular');
|
||||
var _ = require('lodash');
|
||||
var remote = window.require('remote');
|
||||
|
||||
if (window.mocha) {
|
||||
var drives = remote.require(require('path').join(__dirname, '..', '..', 'src', 'drives'));
|
||||
} else {
|
||||
var drives = remote.require('./src/drives');
|
||||
}
|
||||
|
||||
var driveScanner = angular.module('herostratus.drive-scanner', []);
|
||||
|
||||
driveScanner.service('DriveScannerRefreshService', function($interval) {
|
||||
'use strict';
|
||||
|
||||
var interval = null;
|
||||
|
||||
/**
|
||||
* @summary Run a function every certain milliseconds
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Function} fn - function
|
||||
* @param {Number} ms - interval milliseconds
|
||||
*
|
||||
* @example
|
||||
* DriveScannerRefreshService.every(function() {
|
||||
* console.log('I get printed every 2 seconds!');
|
||||
* }, 2000);
|
||||
*/
|
||||
this.every = function(fn, ms) {
|
||||
fn();
|
||||
interval = $interval(fn, ms);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Stop the runnning interval
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* DriveScannerRefreshService.stop();
|
||||
*/
|
||||
this.stop = function() {
|
||||
$interval.cancel(interval);
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
driveScanner.service('DriveScannerService', function($q, DriveScannerRefreshService) {
|
||||
'use strict';
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* @summary List of available drives
|
||||
* @type {Object[]}
|
||||
* @public
|
||||
*/
|
||||
this.drives = [];
|
||||
|
||||
/**
|
||||
* @summary Check if there are available drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean} whether there are available drives
|
||||
*
|
||||
* @example
|
||||
* if (DriveScannerService.hasAvailableDrives()) {
|
||||
* console.log('There are available drives!');
|
||||
* }
|
||||
*/
|
||||
this.hasAvailableDrives = function() {
|
||||
return !_.isEmpty(self.drives);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Set the list of drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Object[]} drives - drives
|
||||
*
|
||||
* @example
|
||||
* DriveScannerService.scan().then(function(drives) {
|
||||
* DriveScannerService.setDrives(drives);
|
||||
* });
|
||||
*/
|
||||
this.setDrives = function(drives) {
|
||||
|
||||
// Only update if something has changed
|
||||
// to avoid unnecessary DOM manipulations
|
||||
// angular.equals ignores $$hashKey by default
|
||||
if (!angular.equals(self.drives, drives)) {
|
||||
self.drives = drives;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get available drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @fulfil {Object[]} - drives
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* DriveScannerService.scan().then(function(drives) {
|
||||
* console.log(drives);
|
||||
* });
|
||||
*/
|
||||
this.scan = function() {
|
||||
return $q.when(drives.listRemovable());
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Scan drives and populate `.drives`
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {Number} ms - interval milliseconds
|
||||
*
|
||||
* @example
|
||||
* DriveScannerService.start(2000);
|
||||
*/
|
||||
this.start = function(ms) {
|
||||
DriveScannerRefreshService.every(function() {
|
||||
return self.scan().then(self.setDrives);
|
||||
}, ms);
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Stop scanning drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @example
|
||||
* DriveScannerService.stop();
|
||||
*/
|
||||
this.stop = DriveScannerRefreshService.stop;
|
||||
|
||||
});
|
159
lib/browser/modules/image-writer.js
Normal file
159
lib/browser/modules/image-writer.js
Normal file
@ -0,0 +1,159 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module herostratus.image-writer
|
||||
*/
|
||||
|
||||
var angular = require('angular');
|
||||
var remote = window.require('remote');
|
||||
|
||||
if (window.mocha) {
|
||||
var writer = remote.require(require('path').join(__dirname, '..', '..', 'src', 'writer'));
|
||||
} else {
|
||||
var writer = remote.require('./src/writer');
|
||||
}
|
||||
|
||||
var imageWriter = angular.module('herostratus.image-writer', []);
|
||||
|
||||
imageWriter.service('ImageWriterService', function($q, $timeout) {
|
||||
'use strict';
|
||||
|
||||
var self = this;
|
||||
var burning = false;
|
||||
|
||||
/**
|
||||
* @summary Progress percentage
|
||||
* @type Number
|
||||
* @public
|
||||
*/
|
||||
this.progress = 0;
|
||||
|
||||
/**
|
||||
* @summary Set progress percentage
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {Number} progress
|
||||
*
|
||||
* @example
|
||||
* ImageWriterService.setProgress(50);
|
||||
*/
|
||||
this.setProgress = function(progress) {
|
||||
|
||||
// Safely bring the state to the world of Angular
|
||||
$timeout(function() {
|
||||
self.progress = Math.floor(progress);
|
||||
console.debug('Progress: ' + self.progress);
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Check if currently burning
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @returns {Boolean} whether is burning or not
|
||||
*
|
||||
* @example
|
||||
* if (ImageWriterService.isBurning()) {
|
||||
* console.log('We\'re currently burning');
|
||||
* }
|
||||
*/
|
||||
this.isBurning = function() {
|
||||
return burning;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Set the burning status
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {Boolean} status - burning status
|
||||
*
|
||||
* @example
|
||||
* ImageWriterService.setBurning(true);
|
||||
*/
|
||||
this.setBurning = function(status) {
|
||||
burning = !!status;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Perform write operation
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @description
|
||||
* This function is extracted for testing purposes.
|
||||
*
|
||||
* @param {String} image - image path
|
||||
* @param {String} drive - drive device
|
||||
* @param {Function} onProgress - in progress callback (state)
|
||||
*
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* ImageWriter.performWrite('path/to/image.img', '/dev/disk2', function(state) {
|
||||
* console.log(state.percentage);
|
||||
* });
|
||||
*/
|
||||
this.performWrite = function(image, drive, onProgress) {
|
||||
return $q.when(writer.writeImage(image, drive, onProgress));
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Burn an image to a drive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* This function will update `.progress` with the current writing percentage.
|
||||
*
|
||||
* @param {String} image - image path
|
||||
* @param {String} drive - drive device
|
||||
*
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* ImageWriterService.burn('foo.img', '/dev/disk').then(function() {
|
||||
* console.log('Write completed!');
|
||||
* });
|
||||
*/
|
||||
this.burn = function(image, drive) {
|
||||
|
||||
// Avoid writing more than once
|
||||
if (self.isBurning()) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.setBurning(true);
|
||||
|
||||
return self.performWrite(image, drive, function(state) {
|
||||
self.setProgress(state.percentage);
|
||||
}).finally(function() {
|
||||
self.setBurning(false);
|
||||
});
|
||||
};
|
||||
|
||||
});
|
48
lib/browser/modules/path.js
Normal file
48
lib/browser/modules/path.js
Normal file
@ -0,0 +1,48 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module herostratus.path
|
||||
*/
|
||||
|
||||
var angular = require('angular');
|
||||
var path = require('path');
|
||||
|
||||
var pathModule = angular.module('herostratus.path', []);
|
||||
|
||||
pathModule.filter('basename', function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* @summary Get the basename of a path
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} input - input path
|
||||
* @returns {String} path basename
|
||||
*
|
||||
* @example
|
||||
* {{ '/foo/bar/baz' | basename }}
|
||||
*/
|
||||
return path.basename;
|
||||
});
|
131
lib/browser/modules/selection-state.js
Normal file
131
lib/browser/modules/selection-state.js
Normal file
@ -0,0 +1,131 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module herostratus.selection-state
|
||||
*/
|
||||
|
||||
var angular = require('angular');
|
||||
var selectionState = angular.module('herostratus.selection-state', []);
|
||||
|
||||
selectionState.service('SelectionStateService', function() {
|
||||
'use strict';
|
||||
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* @summary Selection state
|
||||
* @type Object
|
||||
* @private
|
||||
*/
|
||||
var selection = {};
|
||||
|
||||
/**
|
||||
* @summary Set a drive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} drive - drive
|
||||
*
|
||||
* @example
|
||||
* SelectionStateService.setDrive('/dev/disk2');
|
||||
*/
|
||||
this.setDrive = function(drive) {
|
||||
selection.drive = drive;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Set a image
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @param {String} image - image
|
||||
*
|
||||
* @example
|
||||
* SelectionStateService.setImage('foo.img');
|
||||
*/
|
||||
this.setImage = function(image) {
|
||||
selection.image = image;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get drive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} drive
|
||||
*
|
||||
* @example
|
||||
* var drive = SelectionStateService.getDrive();
|
||||
*/
|
||||
this.getDrive = function() {
|
||||
return selection.drive;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Get image
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {String} image
|
||||
*
|
||||
* @example
|
||||
* var image = SelectionStateService.getImage();
|
||||
*/
|
||||
this.getImage = function() {
|
||||
return selection.image;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Check if there is a selected drive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean} whether there is a selected drive
|
||||
*
|
||||
* @example
|
||||
* if (SelectionStateService.hasDrive()) {
|
||||
* console.log('There is a drive!');
|
||||
* }
|
||||
*/
|
||||
this.hasDrive = function() {
|
||||
return !!self.getDrive();
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Check if there is a selected image
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @returns {Boolean} whether there is a selected image
|
||||
*
|
||||
* @example
|
||||
* if (SelectionStateService.hasImage()) {
|
||||
* console.log('There is an image!');
|
||||
* }
|
||||
*/
|
||||
this.hasImage = function() {
|
||||
return !!self.getImage();
|
||||
};
|
||||
|
||||
});
|
24
lib/components/hero-badge.html
Normal file
24
lib/components/hero-badge.html
Normal file
@ -0,0 +1,24 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="hero-badge">
|
||||
<template>
|
||||
<style is="custom-style">
|
||||
:host ::content .badge {
|
||||
border: 2px solid;
|
||||
border-radius: 50%;
|
||||
padding: 7px 10px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
<span class="badge">
|
||||
<content></content>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({ is: "hero-badge" });
|
||||
</script>
|
||||
</dom-module>
|
44
lib/components/hero-button.html
Normal file
44
lib/components/hero-button.html
Normal file
@ -0,0 +1,44 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="hero-button">
|
||||
<template>
|
||||
<style>
|
||||
:host ::content button.btn {
|
||||
padding: 10px;
|
||||
padding-top: 11px;
|
||||
|
||||
border-radius: 2px;
|
||||
border: none;
|
||||
|
||||
letter-spacing: 0.5px;
|
||||
outline: none;
|
||||
|
||||
min-width: 170px;
|
||||
|
||||
position: relative;
|
||||
}
|
||||
</style>
|
||||
|
||||
<button type="button" class="btn" disabled="{{disabled}}">
|
||||
<content></content>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: "hero-button",
|
||||
properties: {
|
||||
disabled: {
|
||||
type: String
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
value: 'primary'
|
||||
}
|
||||
},
|
||||
ready: function() {
|
||||
this.querySelector('.btn').className += ' btn-' + this.type;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
20
lib/components/hero-caption.html
Normal file
20
lib/components/hero-caption.html
Normal file
@ -0,0 +1,20 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
|
||||
<dom-module id="hero-caption">
|
||||
<template>
|
||||
<style>
|
||||
:host ::content strong {
|
||||
font-size: 11px;
|
||||
}
|
||||
</style>
|
||||
<div>
|
||||
<strong>
|
||||
<content></content>
|
||||
</strong>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({ is: "hero-caption" });
|
||||
</script>
|
||||
</dom-module>
|
36
lib/components/hero-icon.html
Normal file
36
lib/components/hero-icon.html
Normal file
@ -0,0 +1,36 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="../../bower_components/iron-icon/iron-icon.html">
|
||||
<link rel="import" href="hero-caption.html">
|
||||
|
||||
<dom-module id="hero-icon">
|
||||
<template>
|
||||
<style>
|
||||
:host {
|
||||
--iron-icon-height: 40px;
|
||||
--iron-icon-width: 40px;
|
||||
}
|
||||
|
||||
:host ::content div {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
<iron-icon src="{{path}}"></iron-icon>
|
||||
|
||||
<div>
|
||||
<hero-caption>
|
||||
<content></content>
|
||||
</hero-caption>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: "hero-icon",
|
||||
properties: {
|
||||
path: {
|
||||
type: String
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
63
lib/components/hero-progress-button.html
Normal file
63
lib/components/hero-progress-button.html
Normal file
@ -0,0 +1,63 @@
|
||||
<link rel="import" href="../../bower_components/polymer/polymer.html">
|
||||
<link rel="import" href="hero-button.html">
|
||||
|
||||
<!-- From http://tympanus.net/Development/ProgressButtonStyles/ -->
|
||||
<dom-module id="hero-progress-button">
|
||||
<template>
|
||||
<style>
|
||||
:host:not([percentage="0"]) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
:host ::content .text {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
:host ::content .bar {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
|
||||
width: 0;
|
||||
height: 100%;
|
||||
|
||||
transition: width 0.3s, opacity 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
<hero-button disabled="{{disabled}}">
|
||||
<span class="text">
|
||||
<content></content>
|
||||
</span>
|
||||
<span class="bar"></span>
|
||||
</hero-button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
Polymer({
|
||||
is: "hero-progress-button",
|
||||
properties: {
|
||||
disabled: {
|
||||
type: String
|
||||
},
|
||||
percentage: {
|
||||
type: Number,
|
||||
value: 0,
|
||||
notify: true
|
||||
}
|
||||
},
|
||||
setProgress: function(percentage) {
|
||||
var bar = this.querySelector('.bar');
|
||||
bar.style.width = percentage + '%';
|
||||
},
|
||||
ready: function() {
|
||||
this.setProgress(this.percentage);
|
||||
this.addEventListener('percentage-changed', function(event) {
|
||||
this.setProgress(event.detail.value);
|
||||
}.bind(this));
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</dom-module>
|
79
lib/elevate.js
Normal file
79
lib/elevate.js
Normal file
@ -0,0 +1,79 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var _ = require('lodash');
|
||||
var dialog = require('dialog');
|
||||
var isElevated = require('is-elevated');
|
||||
var sudoPrompt = require('sudo-prompt');
|
||||
var windosu = require('windosu');
|
||||
var os = require('os');
|
||||
var platform = os.platform();
|
||||
|
||||
exports.require = function(callback) {
|
||||
'use strict';
|
||||
|
||||
isElevated(function(error, elevated) {
|
||||
if (error) {
|
||||
return callback(error);
|
||||
}
|
||||
|
||||
if (elevated) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (!elevated) {
|
||||
|
||||
if (platform === 'darwin') {
|
||||
sudoPrompt.setName('Herostratus');
|
||||
sudoPrompt.exec(process.argv.join(' '), function(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Don't keep the original parent process alive
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
else if (platform === 'win32') {
|
||||
var command = _.map(process.argv, function(word) {
|
||||
return '"' + word + '"';
|
||||
});
|
||||
|
||||
windosu.exec(command.join(' '), null, function(error) {
|
||||
if (error) {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Don't keep the original parent process alive
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
else {
|
||||
dialog.showErrorBox('You don\'t have enough permissions', 'Please run this application as root or administrator');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
54
lib/herostratus.js
Normal file
54
lib/herostratus.js
Normal file
@ -0,0 +1,54 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var path = require('path');
|
||||
var app = require('app');
|
||||
var Menu = require('menu');
|
||||
var Window = require('electron-window');
|
||||
var elevate = require('./elevate');
|
||||
|
||||
app.on('window-all-closed', app.quit);
|
||||
|
||||
app.on('ready', function() {
|
||||
'use strict';
|
||||
|
||||
// No menu bar
|
||||
Menu.setApplicationMenu(null);
|
||||
|
||||
elevate.require(function(error) {
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var mainWindow = Window.createWindow({
|
||||
width: 800,
|
||||
height: 380,
|
||||
resizable: false,
|
||||
fullscreen: false
|
||||
});
|
||||
|
||||
mainWindow.showUrl(path.join(__dirname, 'index.html'));
|
||||
});
|
||||
});
|
||||
|
28
lib/images/burn.svg
Normal file
28
lib/images/burn.svg
Normal file
@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="999.944px" height="999.986px" viewBox="500.028 0 999.944 999.986" enable-background="new 500.028 0 999.944 999.986"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M1174.844,499.923c0-96.492-78.443-174.991-174.991-174.991c-96.492,0-174.991,78.498-174.991,174.991
|
||||
s78.498,174.991,174.991,174.991c50.949,0,96.548-22.254,128.442-57.25l-29.505-94.243l67.853,26.292
|
||||
C1171.491,533.927,1174.844,517.428,1174.844,499.923z M999.853,599.922c-55.196,0-99.999-44.802-99.999-99.999
|
||||
s44.802-99.999,99.999-99.999c55.196,0,99.999,44.802,99.999,99.999S1055.091,599.922,999.853,599.922z"/>
|
||||
<path fill="#FFFFFF" d="M1109.296,675.808c7.055-8.843,11.693-15.255,15.395-20.802c-34.353,27.647-77.339,44.9-124.74,44.9
|
||||
c-110.295,0-199.983-89.689-199.983-199.983S889.656,299.94,999.951,299.94s199.983,89.689,199.983,199.983
|
||||
c0,20.648-4.051,40.248-9.849,58.996l5.7,2.151c15.395,5.951,29.854,13.495,43.098,22.255
|
||||
c-2.249-40.542,6.147-85.302,44.649-129.587l72.1-83.192l15.2,108.995c4.904,34.995,42.847,82.899,76.389,125.187
|
||||
c10.505,13.3,21.053,26.655,31.251,40.248c13.9-45.948,21.5-94.592,21.5-144.997C1499.972,223.886,1276.128,0,999.993,0
|
||||
S500.028,223.886,500.028,499.979s223.83,499.965,499.965,499.965c44.048,0,86.489-6.245,127.24-17.002
|
||||
c-44.802-45.096-72.24-106.593-72.24-173.286C1054.896,744.206,1086.245,704.712,1109.296,675.808z"/>
|
||||
<path fill="#FFFFFF" d="M1283.476,756.709c-3.604-54.791-29.603-119.445-105.95-149.048
|
||||
c26.948,86.252-72.743,101.438-72.743,202.037c0,91.589,62.698,167.838,147.246,190.288c-27.843-25.845,10.1-76.096-18.846-112.6
|
||||
c36.35,11.553,69.097,38.153,69.097,84.799c36.392-24.755-8.955-92.497,58.354-137.341
|
||||
c-20.592,61.092,34.995,90.639,25.901,130.887c-2.654,11.805-8.494,20.899-15.898,28.597
|
||||
c75.243-27.954,129.196-99.691,129.196-184.644c0-109.093-164.289-219.933-178.637-323.228
|
||||
C1233.183,587.921,1321.782,652.017,1283.476,756.709z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
18
lib/images/drive.svg
Normal file
18
lib/images/drive.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="134.229px" height="134.229px" viewBox="0 0 134.229 134.229" enable-background="new 0 0 134.229 134.229"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M21.343,112.528c2.317,0,4.195,1.875,4.195,4.189c0,2.319-1.878,4.201-4.195,4.201
|
||||
c-2.32,0-4.199-1.882-4.199-4.201C17.144,114.403,19.022,112.528,21.343,112.528z"/>
|
||||
<path fill="#FFFFFF" d="M131.246,110.53L119.604,5.8C119.25,2.615,116.047,0,112.48,0H21.754c-3.568,0-6.777,2.615-7.127,5.8
|
||||
L2.984,110.53c0,0.129-0.061,0.232-0.061,0.359v11.667c0,6.437,5.237,11.673,11.667,11.673h105.05
|
||||
c6.431,0,11.667-5.236,11.667-11.673v-11.667C131.307,110.762,131.246,110.652,131.246,110.53z M125.474,122.556
|
||||
c0,3.222-2.631,5.84-5.84,5.84H14.59c-3.206,0-5.836-2.618-5.836-5.84v-11.667c0-3.221,2.63-5.839,5.836-5.839h105.05
|
||||
c3.203,0,5.834,2.618,5.834,5.839V122.556L125.474,122.556z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.2 KiB |
16
lib/images/image.svg
Normal file
16
lib/images/image.svg
Normal file
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="41px" height="41px" viewBox="0 0 41 41" enable-background="new 0 0 41 41" xml:space="preserve">
|
||||
<g>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,0C31.822,0,41,9.178,41,20.5S31.822,41,20.5,41
|
||||
S0,31.822,0,20.5S9.178,0,20.5,0z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#555760" d="M20.5,12c4.694,0,8.5,3.806,8.5,8.5S25.194,29,20.5,29
|
||||
S12,25.194,12,20.5S15.806,12,20.5,12z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#FFFFFF" d="M20.5,12.625c4.349,0,7.875,3.526,7.875,7.875
|
||||
s-3.526,7.875-7.875,7.875s-7.875-3.526-7.875-7.875S16.151,12.625,20.5,12.625z"/>
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" fill="#555760" d="M20.5,16c2.485,0,4.5,2.015,4.5,4.5S22.985,25,20.5,25
|
||||
S16,22.985,16,20.5S18.015,16,20.5,16z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
104
lib/index.html
Normal file
104
lib/index.html
Normal file
@ -0,0 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Herostratus</title>
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/flexboxgrid/dist/flexboxgrid.css">
|
||||
<link rel="stylesheet" type="text/css" href="../node_modules/angular/angular-csp.css">
|
||||
<link rel="stylesheet" type="text/css" href="../build/css/main.css">
|
||||
|
||||
<link rel="import" href="components/hero-badge.html">
|
||||
<link rel="import" href="components/hero-caption.html">
|
||||
<link rel="import" href="components/hero-icon.html">
|
||||
<link rel="import" href="components/hero-button.html">
|
||||
<link rel="import" href="components/hero-progress-button.html">
|
||||
|
||||
<script src="../build/browser/app.js"></script>
|
||||
</head>
|
||||
<body ng-app="Herostratus" ng-controller="AppController as app" ng-cloak>
|
||||
|
||||
<div class="content row middle-xs space-horizontal-large">
|
||||
<div class="col-xs">
|
||||
|
||||
<div class="row around-xs space-top-large">
|
||||
<div class="col-xs">
|
||||
<div class="box text-center">
|
||||
<hero-icon path="images/image.svg">SELECT IMAGE</hero-icon>
|
||||
<hero-badge class="block space-vertical-medium">1</hero-badge>
|
||||
|
||||
<div class="space-vertical-large">
|
||||
<div ng-hide="app.selection.hasImage()">
|
||||
<hero-button ng-click="app.selectImage()">Select image</hero-button>
|
||||
</div>
|
||||
<div ng-show="app.selection.hasImage()">
|
||||
<span>{{ app.selection.getImage() | basename }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs">
|
||||
<div class="box text-center relative">
|
||||
<div class="step-border-left" ng-disabled="!app.selection.hasImage()"></div>
|
||||
<div class="step-border-right" ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()"></div>
|
||||
|
||||
<hero-icon path="images/drive.svg" ng-disabled="!app.selection.hasImage()">SELECT DRIVE</hero-icon>
|
||||
<hero-badge class="block space-vertical-medium" ng-disabled="!app.selection.hasImage()">2</hero-badge>
|
||||
|
||||
<div class="space-vertical-large">
|
||||
<div ng-hide="app.selection.hasDrive()">
|
||||
|
||||
<div ng-show="app.scanner.hasAvailableDrives() || !app.selection.hasImage()">
|
||||
<div class="btn-group" uib-dropdown>
|
||||
<hero-button ng-disabled="!app.selection.hasImage()"
|
||||
uib-dropdown-toggle>Select drive</hero-button>
|
||||
|
||||
<ul class="dropdown-menu">
|
||||
<li ng-repeat="drive in app.scanner.drives">
|
||||
<a href="#" ng-click="app.selectDrive(drive.device)">{{ drive.device }} - {{ drive.size }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-hide="app.scanner.hasAvailableDrives() || !app.selection.hasImage()">
|
||||
<hero-button type="danger">Connect a drive</hero-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div ng-show="app.selection.hasDrive()">
|
||||
<span>{{ app.selection.getDrive() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs">
|
||||
<div class="box text-center">
|
||||
<hero-icon path="images/burn.svg" ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">BURN IMAGE</hero-icon>
|
||||
<hero-badge class="block space-vertical-medium" ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">3</hero-badge>
|
||||
|
||||
<div class="space-vertical-large">
|
||||
<hero-progress-button percentage="{{ app.writer.progress }}"
|
||||
ng-click="app.burn(app.selection.getImage(), app.selection.getDrive())"
|
||||
ng-disabled="!app.selection.hasImage() || !app.selection.hasDrive()">
|
||||
<span ng-show="app.writer.progress == 100">Done</span>
|
||||
<span ng-show="app.writer.progress == 0 && !app.writer.isBurning()">Burn!</span>
|
||||
<span ng-show="app.writer.progress == 0 && app.writer.isBurning()">Starting...</span>
|
||||
<span ng-show="app.writer.progress != 0 && app.writer.progress != 100">{{ app.writer.progress }}%</span>
|
||||
</hero-progress-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row around-xs section-footer">
|
||||
<div class="col-xs">
|
||||
<div class="box text-center">
|
||||
<hero-caption ng-click="app.open('https://resin.io')">POWERED BY RESIN.IO</hero-caption>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
27
lib/scss/_angular.scss
Normal file
27
lib/scss/_angular.scss
Normal file
@ -0,0 +1,27 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
[ng-click] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
43
lib/scss/_desktop.scss
Normal file
43
lib/scss/_desktop.scss
Normal file
@ -0,0 +1,43 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
// Prevent text selection
|
||||
body {
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
// Prevent WebView bounce effect in OS X
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
html {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
117
lib/scss/main.scss
Normal file
117
lib/scss/main.scss
Normal file
@ -0,0 +1,117 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
$body-bg: rgb(83, 87, 96);
|
||||
$text-color: white;
|
||||
$brand-primary: rgb(87, 147, 219);
|
||||
$font-size-base: 13px;
|
||||
$cursor-disabled: initial;
|
||||
$color-disabled: rgb(120, 124, 127);
|
||||
$btn-disabled: rgb(49, 51, 57);
|
||||
$badge-disabled: rgb(92, 94, 92);
|
||||
$btn-padding: 10px;
|
||||
|
||||
@import "../../node_modules/bootstrap-sass/assets/stylesheets/bootstrap";
|
||||
@import "./angular";
|
||||
@import "./desktop";
|
||||
|
||||
@import "./modules/space";
|
||||
|
||||
hero-badge .badge {
|
||||
background-color: $body-bg;
|
||||
}
|
||||
|
||||
hero-badge[disabled] .badge {
|
||||
background-color: $badge-disabled;
|
||||
color: $color-disabled;
|
||||
}
|
||||
|
||||
hero-icon[disabled] hero-caption {
|
||||
color: $color-disabled;
|
||||
}
|
||||
|
||||
hero-button .btn {
|
||||
&[disabled] {
|
||||
background-color: $btn-disabled;
|
||||
color: $color-disabled;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($btn-disabled, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hero-progress-button .bar {
|
||||
background: lighten($brand-primary, 5);
|
||||
}
|
||||
|
||||
hero-progress-button[percentage="100"] .bar {
|
||||
background-color: $brand-success;
|
||||
}
|
||||
|
||||
.block {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
width: 170px;
|
||||
}
|
||||
|
||||
body {
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.relative {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-footer {
|
||||
color: $gray-dark;
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
.step-border {
|
||||
height: 2px;
|
||||
background-color: $text-color;
|
||||
position: absolute;
|
||||
width: 230px;
|
||||
top: 55%;
|
||||
|
||||
&[disabled] {
|
||||
background-color: $color-disabled;
|
||||
}
|
||||
}
|
||||
|
||||
.step-border-left {
|
||||
@extend .step-border;
|
||||
left: -120px;
|
||||
}
|
||||
|
||||
.step-border-right {
|
||||
@extend .step-border;
|
||||
right: -120px;
|
||||
}
|
41
lib/scss/modules/_space.scss
Normal file
41
lib/scss/modules/_space.scss
Normal file
@ -0,0 +1,41 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
$spacing-large: 25px;
|
||||
$spacing-medium: 15px;
|
||||
|
||||
.space-vertical-medium {
|
||||
margin: $spacing-medium 0;
|
||||
}
|
||||
|
||||
.space-vertical-large {
|
||||
margin: $spacing-large 0;
|
||||
}
|
||||
|
||||
.space-horizontal-large {
|
||||
margin: 0 $spacing-large;
|
||||
}
|
||||
|
||||
.space-top-large {
|
||||
margin-top: $spacing-large;
|
||||
}
|
56
lib/src/dialog.js
Normal file
56
lib/src/dialog.js
Normal file
@ -0,0 +1,56 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var dialog = require('dialog');
|
||||
var _ = require('lodash');
|
||||
|
||||
/**
|
||||
* @summary Open an image selection dialog
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* Notice that by image, we mean *.img/*.iso files.
|
||||
*
|
||||
* @fulfil {String} - selected image
|
||||
* @returns {Promise};
|
||||
*
|
||||
* @example
|
||||
* dialog.selectImage().then(function(image) {
|
||||
* console.log('The selected image is', image);
|
||||
* });
|
||||
*/
|
||||
exports.selectImage = function() {
|
||||
'use strict';
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
dialog.showOpenDialog({
|
||||
properties: [ 'openFile' ],
|
||||
filters: [
|
||||
{ name: 'IMG/ISO', extensions: [ 'img', 'iso' ] }
|
||||
]
|
||||
}, function(file) {
|
||||
return resolve(_.first(file));
|
||||
});
|
||||
});
|
||||
};
|
80
lib/src/drives.js
Normal file
80
lib/src/drives.js
Normal file
@ -0,0 +1,80 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var drivelist = require('drivelist');
|
||||
|
||||
/**
|
||||
* @summary List all available drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* See https://github.com/resin-io/drivelist
|
||||
*
|
||||
* @fulfil {Object[]} - available drives
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* drives.list().then(function(drives) {
|
||||
* drives.forEach(function(drive) {
|
||||
* console.log(drive.device);
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
exports.list = function() {
|
||||
'use strict';
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
drivelist.list(function(error, drives) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
return resolve(drives);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary List all available removable drives
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @fulfil {Object[]} - available removable drives
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* drives.listRemovable().then(function(drives) {
|
||||
* drives.forEach(function(drive) {
|
||||
* console.log(drive.device);
|
||||
* });
|
||||
* });
|
||||
*/
|
||||
exports.listRemovable = function() {
|
||||
'use strict';
|
||||
|
||||
return exports.list().then(function(drives) {
|
||||
return drives.filter(function(drive) {
|
||||
return !drive.system;
|
||||
});
|
||||
});
|
||||
};
|
115
lib/src/writer.js
Normal file
115
lib/src/writer.js
Normal file
@ -0,0 +1,115 @@
|
||||
/* The MIT License
|
||||
*
|
||||
* Copyright (c) 2015 Resin.io. https://resin.io.
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*/
|
||||
|
||||
var imageWrite = require('resin-image-write');
|
||||
var umount = require('umount');
|
||||
var fs = require('fs');
|
||||
|
||||
/**
|
||||
* @summary Get image readable stream
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @description
|
||||
* This function adds a custom `.length` property
|
||||
* to the stream which equals the image size in bytes.
|
||||
*
|
||||
* @param {String} image - path to image
|
||||
* @returns {ReadableStream} image stream
|
||||
*
|
||||
* @example
|
||||
* var stream = writer.getImageStream('foo/bar/baz.img');
|
||||
*/
|
||||
exports.getImageStream = function(image) {
|
||||
'use strict';
|
||||
|
||||
var stream = fs.createReadStream(image);
|
||||
stream.length = fs.statSync(image).size;
|
||||
return stream;
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Unmount a drive
|
||||
* @function
|
||||
* @private
|
||||
*
|
||||
* @param {String} disk - disk device
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* writer.unmountDisk('/dev/disk2').then(function() {
|
||||
* console.log('Disk unmounted!');
|
||||
* });
|
||||
*/
|
||||
exports.unmountDisk = function(disk) {
|
||||
'use strict';
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
umount.umount(disk, function(error) {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
|
||||
return resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @summary Write an image to a disk drive
|
||||
* @function
|
||||
* @public
|
||||
*
|
||||
* @description
|
||||
* See https://github.com/resin-io/resin-image-write for information
|
||||
* about the `state` object passed to `onProgress` callback.
|
||||
*
|
||||
* @param {String} image - path to image
|
||||
* @param {String} drive - drive device
|
||||
* @param {Function} onProgress - on progress callback (state)
|
||||
*
|
||||
* @returns {Promise}
|
||||
*
|
||||
* @example
|
||||
* writer.writeImage('path/to/image.img', '/dev/disk2', function(state) {
|
||||
* console.log(state.percentage);
|
||||
* }).then(function() {
|
||||
* console.log('Done!');
|
||||
* });
|
||||
*/
|
||||
exports.writeImage = function(image, drive, onProgress) {
|
||||
'use strict';
|
||||
|
||||
return exports.unmountDisk(drive).then(function() {
|
||||
var stream = exports.getImageStream(image);
|
||||
return imageWrite.write(drive, stream);
|
||||
}).then(function(writer) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
writer.on('progress', onProgress);
|
||||
writer.on('error', reject);
|
||||
writer.on('done', resolve);
|
||||
});
|
||||
}).then(function() {
|
||||
return exports.unmountDisk(drive);
|
||||
});
|
||||
};
|
46
package.json
Normal file
46
package.json
Normal file
@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "herostratus",
|
||||
"version": "0.0.1",
|
||||
"main": "lib/herostratus.js",
|
||||
"description": "An image burner with support for Windows, OS X and GNU/Linux.",
|
||||
"homepage": "https://github.com/resin-io/herostratus",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@github.com:resin-io/herostratus.git"
|
||||
},
|
||||
"scripts": {
|
||||
"test:main": "electron-mocha --recursive tests/src",
|
||||
"test:browser": "electron-mocha --recursive --renderer tests/browser",
|
||||
"test": "npm run-script test:main && npm run-script test:browser",
|
||||
"start": "electron lib/herostratus.js"
|
||||
},
|
||||
"author": "Juan Cruz Viotti <juan@resin.io>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"angular": "^1.4.6",
|
||||
"angular-ui-bootstrap": "^0.14.2",
|
||||
"bootstrap-sass": "^3.3.5",
|
||||
"drivelist": "^2.0.0",
|
||||
"electron-window": "^0.6.0",
|
||||
"flexboxgrid": "^6.3.0",
|
||||
"is-elevated": "^1.0.0",
|
||||
"lodash": "^3.10.1",
|
||||
"resin-image-write": "^2.0.5",
|
||||
"sudo-prompt": "^1.1.7",
|
||||
"umount": "^1.1.1",
|
||||
"windosu": "^0.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"angular-mocks": "^1.4.7",
|
||||
"browserify": "^12.0.1",
|
||||
"electron-mocha": "^0.5.0",
|
||||
"electron-prebuilt": "^0.33.0",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-jshint": "^1.11.2",
|
||||
"gulp-sass": "^2.0.4",
|
||||
"jshint-stylish": "^2.0.1",
|
||||
"mochainon": "^1.0.0",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0"
|
||||
}
|
||||
}
|
BIN
screenshot.png
Normal file
BIN
screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 106 KiB |
181
tests/browser/modules/drive-scanner.spec.js
Normal file
181
tests/browser/modules/drive-scanner.spec.js
Normal file
@ -0,0 +1,181 @@
|
||||
var m = require('mochainon');
|
||||
var angular = require('angular');
|
||||
window.mocha = true;
|
||||
require('../../../lib/browser/modules/drive-scanner');
|
||||
require('angular-mocks');
|
||||
|
||||
describe('Browser: DriveScanner', function() {
|
||||
'use strict';
|
||||
|
||||
beforeEach(angular.mock.module('herostratus.drive-scanner'));
|
||||
|
||||
describe('DriveScannerRefreshService', function() {
|
||||
|
||||
var DriveScannerRefreshService;
|
||||
var $interval;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$interval_, _DriveScannerRefreshService_) {
|
||||
$interval = _$interval_;
|
||||
DriveScannerRefreshService = _DriveScannerRefreshService_;
|
||||
}));
|
||||
|
||||
describe('.every()', function() {
|
||||
|
||||
it('should call the function right away', function() {
|
||||
var spy = m.sinon.spy();
|
||||
DriveScannerRefreshService.every(spy, 1000);
|
||||
DriveScannerRefreshService.stop();
|
||||
m.chai.expect(spy).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should call the function in an interval', function() {
|
||||
var spy = m.sinon.spy();
|
||||
DriveScannerRefreshService.every(spy, 100);
|
||||
|
||||
// 400ms = 100ms / 4 + 1 (the initial call)
|
||||
$interval.flush(400);
|
||||
|
||||
DriveScannerRefreshService.stop();
|
||||
m.chai.expect(spy).to.have.callCount(5);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('DriveScannerService', function() {
|
||||
|
||||
var $interval;
|
||||
var $q;
|
||||
var DriveScannerService;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$interval_, _$q_, _DriveScannerService_) {
|
||||
$interval = _$interval_;
|
||||
$q = _$q_;
|
||||
DriveScannerService = _DriveScannerService_;
|
||||
}));
|
||||
|
||||
it('should have no drives by default', function() {
|
||||
m.chai.expect(DriveScannerService.drives).to.deep.equal([]);
|
||||
});
|
||||
|
||||
describe('given no drives', function() {
|
||||
|
||||
describe('.hasAvailableDrives()', function() {
|
||||
|
||||
it('should return false', function() {
|
||||
var hasDrives = DriveScannerService.hasAvailableDrives();
|
||||
m.chai.expect(hasDrives).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.setDrives()', function() {
|
||||
|
||||
it('should be able to set drives', function() {
|
||||
var drives = [
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
description: 'Foo',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
}
|
||||
];
|
||||
|
||||
DriveScannerService.setDrives(drives);
|
||||
m.chai.expect(DriveScannerService.drives).to.deep.equal(drives);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drives = [
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
description: 'Foo',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
},
|
||||
{
|
||||
device: '/dev/sdc',
|
||||
description: 'Bar',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/bar',
|
||||
system: false
|
||||
}
|
||||
];
|
||||
|
||||
DriveScannerService.drives = this.drives;
|
||||
});
|
||||
|
||||
describe('.hasAvailableDrives()', function() {
|
||||
|
||||
it('should return true', function() {
|
||||
var hasDrives = DriveScannerService.hasAvailableDrives();
|
||||
m.chai.expect(hasDrives).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.setDrives()', function() {
|
||||
|
||||
it('should keep the same drives if equal', function() {
|
||||
DriveScannerService.setDrives(this.drives);
|
||||
m.chai.expect(DriveScannerService.drives).to.deep.equal(this.drives);
|
||||
});
|
||||
|
||||
it('should consider drives with different $$hashKey the same', function() {
|
||||
this.drives[0].$$haskey = 1234;
|
||||
DriveScannerService.setDrives(this.drives);
|
||||
m.chai.expect(DriveScannerService.drives).to.deep.equal(this.drives);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given available drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drives = [
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
description: 'Foo',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
},
|
||||
{
|
||||
device: '/dev/sdc',
|
||||
description: 'Bar',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/bar',
|
||||
system: false
|
||||
}
|
||||
];
|
||||
|
||||
this.scanStub = m.sinon.stub(DriveScannerService, 'scan');
|
||||
this.scanStub.returns($q.resolve(this.drives));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.scanStub.restore();
|
||||
});
|
||||
|
||||
it('should set the drives to the scanned ones', function() {
|
||||
DriveScannerService.start(200);
|
||||
$interval.flush(400);
|
||||
m.chai.expect(DriveScannerService.drives).to.deep.equal(this.drives);
|
||||
DriveScannerService.stop();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
});
|
144
tests/browser/modules/image-writer.spec.js
Normal file
144
tests/browser/modules/image-writer.spec.js
Normal file
@ -0,0 +1,144 @@
|
||||
var m = require('mochainon');
|
||||
var angular = require('angular');
|
||||
window.mocha = true;
|
||||
require('../../../lib/browser/modules/image-writer');
|
||||
require('angular-mocks');
|
||||
|
||||
describe('Browser: ImageWriter', function() {
|
||||
'use strict';
|
||||
|
||||
beforeEach(angular.mock.module('herostratus.image-writer'));
|
||||
|
||||
describe('ImageWriterService', function() {
|
||||
|
||||
var $q;
|
||||
var $timeout;
|
||||
var $rootScope;
|
||||
var ImageWriterService;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_$q_, _$timeout_, _$rootScope_, _ImageWriterService_) {
|
||||
$q = _$q_;
|
||||
$timeout = _$timeout_;
|
||||
$rootScope = _$rootScope_;
|
||||
ImageWriterService = _ImageWriterService_;
|
||||
}));
|
||||
|
||||
it('should set progress to zero by default', function() {
|
||||
m.chai.expect(ImageWriterService.progress).to.equal(0);
|
||||
});
|
||||
|
||||
describe('.isBurning()', function() {
|
||||
|
||||
it('should return false by default', function() {
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.false;
|
||||
});
|
||||
|
||||
it('should return true if burning', function() {
|
||||
ImageWriterService.setBurning(true);
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.setBurning()', function() {
|
||||
|
||||
it('should be able to set burning to true', function() {
|
||||
ImageWriterService.setBurning(true);
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.true;
|
||||
});
|
||||
|
||||
it('should be able to set burning to false', function() {
|
||||
ImageWriterService.setBurning(false);
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.false;
|
||||
});
|
||||
|
||||
it('should cast to boolean by default', function() {
|
||||
ImageWriterService.setBurning('hello');
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.true;
|
||||
|
||||
ImageWriterService.setBurning('');
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.setProgress()', function() {
|
||||
|
||||
it('should be able to set the progress', function() {
|
||||
ImageWriterService.setProgress(50);
|
||||
$timeout.flush();
|
||||
m.chai.expect(ImageWriterService.progress).to.equal(50);
|
||||
});
|
||||
|
||||
it('should floor the percentage', function() {
|
||||
ImageWriterService.setProgress(49.9999);
|
||||
$timeout.flush();
|
||||
m.chai.expect(ImageWriterService.progress).to.equal(49);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.burn()', function() {
|
||||
|
||||
describe('given a succesful write', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite');
|
||||
this.performWriteStub.returns($q.resolve());
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.performWriteStub.restore();
|
||||
});
|
||||
|
||||
it('should set burning to false when done', function() {
|
||||
ImageWriterService.burn('foo.img', '/dev/disk2');
|
||||
$rootScope.$apply();
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.false;
|
||||
});
|
||||
|
||||
it('should prevent writing more than once', function() {
|
||||
ImageWriterService.burn('foo.img', '/dev/disk2');
|
||||
ImageWriterService.burn('foo.img', '/dev/disk2');
|
||||
$rootScope.$apply();
|
||||
m.chai.expect(this.performWriteStub).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an unsuccesful write', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.performWriteStub = m.sinon.stub(ImageWriterService, 'performWrite');
|
||||
this.performWriteStub.returns($q.reject(new Error('write error')));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.performWriteStub.restore();
|
||||
});
|
||||
|
||||
it('should set burning to false when done', function() {
|
||||
ImageWriterService.burn('foo.img', '/dev/disk2');
|
||||
$rootScope.$apply();
|
||||
m.chai.expect(ImageWriterService.isBurning()).to.be.false;
|
||||
});
|
||||
|
||||
it('should be rejected with the error', function() {
|
||||
var rejection;
|
||||
ImageWriterService.burn('foo.img', '/dev/disk2').catch(function(error) {
|
||||
rejection = error;
|
||||
});
|
||||
|
||||
$rootScope.$apply();
|
||||
|
||||
m.chai.expect(rejection).to.be.an.instanceof(Error);
|
||||
m.chai.expect(rejection.message).to.equal('write error');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
35
tests/browser/modules/path.spec.js
Normal file
35
tests/browser/modules/path.spec.js
Normal file
@ -0,0 +1,35 @@
|
||||
var m = require('mochainon');
|
||||
var angular = require('angular');
|
||||
var os = require('os');
|
||||
window.mocha = true;
|
||||
require('../../../lib/browser/modules/path');
|
||||
require('angular-mocks');
|
||||
|
||||
describe('Browser: Path', function() {
|
||||
'use strict';
|
||||
|
||||
beforeEach(angular.mock.module('herostratus.path'));
|
||||
|
||||
describe('BasenameFilter', function() {
|
||||
|
||||
var basenameFilter;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_basenameFilter_) {
|
||||
basenameFilter = _basenameFilter_;
|
||||
}));
|
||||
|
||||
it('should return the basename', function() {
|
||||
var isWindows = os.platform() === 'win32';
|
||||
var basename;
|
||||
|
||||
if (isWindows) {
|
||||
basename = basenameFilter('C:\\Users\\jviotti\\foo.img');
|
||||
} else {
|
||||
basename = basenameFilter('/Users/jviotti/foo.img');
|
||||
}
|
||||
|
||||
m.chai.expect(basename).to.equal('foo.img');
|
||||
});
|
||||
|
||||
});
|
||||
});
|
146
tests/browser/modules/selection-state.spec.js
Normal file
146
tests/browser/modules/selection-state.spec.js
Normal file
@ -0,0 +1,146 @@
|
||||
var m = require('mochainon');
|
||||
var angular = require('angular');
|
||||
window.mocha = true;
|
||||
require('../../../lib/browser/modules/selection-state');
|
||||
require('angular-mocks');
|
||||
|
||||
describe('Browser: SelectionState', function() {
|
||||
'use strict';
|
||||
|
||||
beforeEach(angular.mock.module('herostratus.selection-state'));
|
||||
|
||||
describe('SelectionStateService', function() {
|
||||
|
||||
var SelectionStateService;
|
||||
|
||||
beforeEach(angular.mock.inject(function(_SelectionStateService_) {
|
||||
SelectionStateService = _SelectionStateService_;
|
||||
}));
|
||||
|
||||
describe('given a clean state', function() {
|
||||
|
||||
it('getDrive() should return undefined', function() {
|
||||
var drive = SelectionStateService.getDrive();
|
||||
m.chai.expect(drive).to.be.undefined;
|
||||
});
|
||||
|
||||
it('getImage() should return undefined', function() {
|
||||
var image = SelectionStateService.getImage();
|
||||
m.chai.expect(image).to.be.undefined;
|
||||
});
|
||||
|
||||
it('hasDrive() should return false', function() {
|
||||
var hasDrive = SelectionStateService.hasDrive();
|
||||
m.chai.expect(hasDrive).to.be.false;
|
||||
});
|
||||
|
||||
it('hasImage() should return false', function() {
|
||||
var hasImage = SelectionStateService.hasImage();
|
||||
m.chai.expect(hasImage).to.be.false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given a drive', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SelectionStateService.setDrive('/dev/disk2');
|
||||
});
|
||||
|
||||
describe('.getDrive()', function() {
|
||||
|
||||
it('should return the drive', function() {
|
||||
var drive = SelectionStateService.getDrive();
|
||||
m.chai.expect(drive).to.equal('/dev/disk2');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.hasDrive()', function() {
|
||||
|
||||
it('should return true', function() {
|
||||
var hasDrive = SelectionStateService.hasDrive();
|
||||
m.chai.expect(hasDrive).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.setDrive()', function() {
|
||||
|
||||
it('should override the drive', function() {
|
||||
SelectionStateService.setDrive('/dev/disk5');
|
||||
var drive = SelectionStateService.getDrive();
|
||||
m.chai.expect(drive).to.equal('/dev/disk5');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given no drive', function() {
|
||||
|
||||
describe('.setDrive()', function() {
|
||||
|
||||
it('should be able to set a drive', function() {
|
||||
SelectionStateService.setDrive('/dev/disk5');
|
||||
var drive = SelectionStateService.getDrive();
|
||||
m.chai.expect(drive).to.equal('/dev/disk5');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an image', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
SelectionStateService.setImage('foo.img');
|
||||
});
|
||||
|
||||
describe('.getImage()', function() {
|
||||
|
||||
it('should return the image', function() {
|
||||
var image = SelectionStateService.getImage();
|
||||
m.chai.expect(image).to.equal('foo.img');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.hasImage()', function() {
|
||||
|
||||
it('should return true', function() {
|
||||
var hasImage = SelectionStateService.hasImage();
|
||||
m.chai.expect(hasImage).to.be.true;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.setImage()', function() {
|
||||
|
||||
it('should override the image', function() {
|
||||
SelectionStateService.setImage('bar.img');
|
||||
var image = SelectionStateService.getImage();
|
||||
m.chai.expect(image).to.equal('bar.img');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given no image', function() {
|
||||
|
||||
describe('.setImage()', function() {
|
||||
|
||||
it('should be able to set an image', function() {
|
||||
SelectionStateService.setImage('foo.img');
|
||||
var image = SelectionStateService.getImage();
|
||||
m.chai.expect(image).to.equal('foo.img');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
32
tests/src/dialog.spec.js
Normal file
32
tests/src/dialog.spec.js
Normal file
@ -0,0 +1,32 @@
|
||||
var m = require('mochainon');
|
||||
var electronDialog = require('dialog');
|
||||
var dialog = require('../../lib/src/dialog');
|
||||
|
||||
describe('Dialog:', function() {
|
||||
'use strict';
|
||||
|
||||
describe('.selectImage()', function() {
|
||||
|
||||
describe('given the users performs a selection', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.showOpenDialogStub = m.sinon.stub(electronDialog, 'showOpenDialog');
|
||||
this.showOpenDialogStub.yields([ 'foo/bar' ]);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.showOpenDialogStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually equal the file', function(done) {
|
||||
dialog.selectImage().then(function(image) {
|
||||
m.chai.expect(image).to.equal('foo/bar');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
217
tests/src/drives.spec.js
Normal file
217
tests/src/drives.spec.js
Normal file
@ -0,0 +1,217 @@
|
||||
var m = require('mochainon');
|
||||
var drivelist = require('drivelist');
|
||||
var drives = require('../../lib/src/drives');
|
||||
|
||||
describe('Drives:', function() {
|
||||
'use strict';
|
||||
|
||||
describe('.list()', function() {
|
||||
|
||||
describe('given no available drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drivelistListStub = m.sinon.stub(drivelist, 'list');
|
||||
this.drivelistListStub.yields(null, []);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivelistListStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually equal an empty array', function(done) {
|
||||
drives.list().then(function(drives) {
|
||||
m.chai.expect(drives).to.deep.equal([]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given available drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drives = [
|
||||
{
|
||||
device: '/dev/sda',
|
||||
description: 'WDC WD10JPVX-75J',
|
||||
size: '931.5G',
|
||||
mountpoint: '/',
|
||||
system: true
|
||||
}
|
||||
];
|
||||
|
||||
this.drivelistListStub = m.sinon.stub(drivelist, 'list');
|
||||
this.drivelistListStub.yields(null, this.drives);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivelistListStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually equal the drives', function(done) {
|
||||
drives.list().then(function(drives) {
|
||||
m.chai.expect(drives).to.deep.equal(this.drives);
|
||||
done();
|
||||
}.bind(this)).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an error when listing the drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drivelistListStub = m.sinon.stub(drivelist, 'list');
|
||||
this.drivelistListStub.yields(new Error('scan error'));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivelistListStub.restore();
|
||||
});
|
||||
|
||||
it('should be rejected with the error', function(done) {
|
||||
drives.list().catch(function(error) {
|
||||
m.chai.expect(error).to.be.an.instanceof(Error);
|
||||
m.chai.expect(error.message).to.equal('scan error');
|
||||
return done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.listRemovable()', function() {
|
||||
|
||||
describe('given no available drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drivesListStub = m.sinon.stub(drives, 'list');
|
||||
this.drivesListStub.returns(Promise.resolve([]));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivesListStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually equal an empty array', function(done) {
|
||||
drives.listRemovable().then(function(drives) {
|
||||
m.chai.expect(drives).to.deep.equal([]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given available system drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drives = [
|
||||
{
|
||||
device: '/dev/sda',
|
||||
description: 'WDC WD10JPVX-75J',
|
||||
size: '931.5G',
|
||||
mountpoint: '/',
|
||||
system: true
|
||||
}
|
||||
];
|
||||
|
||||
this.drivesListStub = m.sinon.stub(drives, 'list');
|
||||
this.drivesListStub.returns(Promise.resolve(this.drives));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivesListStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually equal an empty array', function(done) {
|
||||
drives.listRemovable().then(function(drives) {
|
||||
m.chai.expect(drives).to.deep.equal([]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given available system and removable drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drives = [
|
||||
{
|
||||
device: '/dev/sda',
|
||||
description: 'WDC WD10JPVX-75J',
|
||||
size: '931.5G',
|
||||
mountpoint: '/',
|
||||
system: true
|
||||
},
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
description: 'Foo',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
},
|
||||
{
|
||||
device: '/dev/sdc',
|
||||
description: 'Bar',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/bar',
|
||||
system: false
|
||||
}
|
||||
];
|
||||
|
||||
this.drivesListStub = m.sinon.stub(drives, 'list');
|
||||
this.drivesListStub.returns(Promise.resolve(this.drives));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivesListStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually become the removable drives', function(done) {
|
||||
drives.listRemovable().then(function(drives) {
|
||||
m.chai.expect(drives).to.deep.equal([
|
||||
{
|
||||
device: '/dev/sdb',
|
||||
description: 'Foo',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/foo',
|
||||
system: false
|
||||
},
|
||||
{
|
||||
device: '/dev/sdc',
|
||||
description: 'Bar',
|
||||
size: '14G',
|
||||
mountpoint: '/mnt/bar',
|
||||
system: false
|
||||
}
|
||||
]);
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an error when listing the drives', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.drivesListStub = m.sinon.stub(drives, 'list');
|
||||
this.drivesListStub.returns(Promise.reject(new Error('scan error')));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.drivesListStub.restore();
|
||||
});
|
||||
|
||||
it('should be rejected with the error', function(done) {
|
||||
drives.listRemovable().catch(function(error) {
|
||||
m.chai.expect(error).to.be.an.instanceof(Error);
|
||||
m.chai.expect(error.message).to.equal('scan error');
|
||||
return done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
77
tests/src/writer.spec.js
Normal file
77
tests/src/writer.spec.js
Normal file
@ -0,0 +1,77 @@
|
||||
var m = require('mochainon');
|
||||
var ReadableStream = require('stream').Readable;
|
||||
var path = require('path');
|
||||
var umount = require('umount');
|
||||
var writer = require('../../lib/src/writer');
|
||||
|
||||
describe('Writer:', function() {
|
||||
'use strict';
|
||||
|
||||
describe('.getImageStream()', function() {
|
||||
|
||||
describe('given a valid image', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.image = path.join(__dirname, '..', 'utils', 'data.random');
|
||||
});
|
||||
|
||||
it('should return a readable stream', function() {
|
||||
var stream = writer.getImageStream(this.image);
|
||||
m.chai.expect(stream).to.be.an.instanceof(ReadableStream);
|
||||
});
|
||||
|
||||
it('should append a .length property with the correct size', function() {
|
||||
var stream = writer.getImageStream(this.image);
|
||||
m.chai.expect(stream.length).to.equal(2097152);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('.unmountDisk()', function() {
|
||||
|
||||
describe('given a successful unmount', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.umountStub = m.sinon.stub(umount, 'umount');
|
||||
this.umountStub.yields(null);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.umountStub.restore();
|
||||
});
|
||||
|
||||
it('should eventually resolve undefined', function(done) {
|
||||
writer.unmountDisk('/dev/disk2').then(function() {
|
||||
m.chai.expect(arguments[0]).to.be.undefined;
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('given an unsuccessful unmount', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
this.umountStub = m.sinon.stub(umount, 'umount');
|
||||
this.umountStub.yields(new Error('unmount error'));
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
this.umountStub.restore();
|
||||
});
|
||||
|
||||
it('should be rejected with the error', function(done) {
|
||||
writer.unmountDisk('/dev/disk2').catch(function(error) {
|
||||
m.chai.expect(error).to.be.an.instanceof(Error);
|
||||
m.chai.expect(error.message).to.equal('unmount error');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
BIN
tests/utils/data.random
Normal file
BIN
tests/utils/data.random
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user