etcher/lib/gui/app.js
Ștefan Daniel Mihăilă 9e7e5de63a feat(GUI): add uuid to flash events (#1344)
Change-Type: patch
Signed-off-by: Juan Cruz Viotti <jviotti@openmailbox.org>
2017-04-22 20:40:05 -04:00

275 lines
7.8 KiB
JavaScript

/*
* Copyright 2016 resin.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @module Etcher
*/
'use strict';
/* eslint-disable no-var */
var angular = require('angular');
/* eslint-enable no-var */
const electron = require('electron');
const Bluebird = require('bluebird');
const EXIT_CODES = require('../shared/exit-codes');
const messages = require('../shared/messages');
const packageJSON = require('../../package.json');
const flashState = require('./models/flash-state');
const windowProgress = require('./os/window-progress');
const Store = require('./models/store');
const app = angular.module('Etcher', [
require('angular-ui-router'),
require('angular-ui-bootstrap'),
require('angular-if-state'),
// Etcher modules
require('./modules/analytics'),
require('./modules/error'),
require('./modules/drive-scanner'),
// Models
require('./models/selection-state'),
require('./models/drives'),
// Components
require('./components/svg-icon/svg-icon'),
require('./components/update-notifier/update-notifier'),
require('./components/warning-modal/warning-modal'),
// Pages
require('./pages/main/main'),
require('./pages/finish/finish'),
require('./pages/settings/settings'),
// OS
require('./os/open-external/open-external'),
require('./os/dropzone/dropzone'),
require('./os/dialog/dialog'),
// Utils
require('./utils/manifest-bind/manifest-bind')
]);
app.run(() => {
console.log([
' _____ _ _',
'| ___| | | |',
'| |__ | |_ ___| |__ ___ _ __',
'| __|| __/ __| \'_ \\ / _ \\ \'__|',
'| |___| || (__| | | | __/ |',
'\\____/ \\__\\___|_| |_|\\___|_|',
'',
'Interested in joining the Etcher team?',
'Drop us a line at join+etcher@resin.io'
].join('\n'));
});
app.run((AnalyticsService, ErrorService, UpdateNotifierService, SelectionStateModel) => {
AnalyticsService.logEvent('Application start');
const shouldCheckForUpdates = UpdateNotifierService.shouldCheckForUpdates();
if (!shouldCheckForUpdates || process.env.ETCHER_DISABLE_UPDATES) {
AnalyticsService.logEvent('Not checking for updates', {
shouldCheckForUpdates,
disableUpdatesEnvironmentVariable: process.env.ETCHER_DISABLE_UPDATES
});
return;
}
AnalyticsService.logEvent('Checking for updates', {
currentVersion: packageJSON.version
});
UpdateNotifierService.isLatestVersion().then((isLatestVersion) => {
if (isLatestVersion) {
AnalyticsService.logEvent('Update notification skipped', {
reason: 'Latest version'
});
return Bluebird.resolve();
}
// In case the internet connection is not good and checking the
// latest published version takes too long, only show notify
// the user about the new version if he didn't start the flash
// process (e.g: selected an image), otherwise such interruption
// might be annoying.
if (SelectionStateModel.hasImage()) {
AnalyticsService.logEvent('Update notification skipped', {
reason: 'Image selected'
});
return Bluebird.resolve();
}
AnalyticsService.logEvent('Notifying update');
return UpdateNotifierService.notify();
}).catch(ErrorService.reportException);
});
app.run((AnalyticsService) => {
Store.subscribe(() => {
const currentFlashState = flashState.getFlashState();
// There is usually a short time period between the `isFlashing()`
// property being set, and the flashing actually starting, which
// might cause some non-sense flashing state logs including
// `undefined` values.
//
// We use the presence of `.eta` to determine that the actual
// writing started.
if (!flashState.isFlashing() || !currentFlashState.eta) {
return;
}
AnalyticsService.logDebug([
`Progress (${currentFlashState.type}):`,
`${currentFlashState.percentage}% at ${currentFlashState.speed} MB/s`,
`(eta ${currentFlashState.eta}s)`
].join(' '));
windowProgress.set(currentFlashState.percentage);
});
});
app.run(($timeout, DriveScannerService, DrivesModel, ErrorService) => {
DriveScannerService.on('drives', (drives) => {
// Safely trigger a digest cycle.
// In some cases, AngularJS doesn't acknowledge that the
// available drives list has changed, and incorrectly
// keeps asking the user to "Connect a drive".
$timeout(() => {
DrivesModel.setDrives(drives);
});
});
DriveScannerService.on('error', (error) => {
// Stop the drive scanning loop in case of errors,
// otherwise we risk presenting the same error over
// and over again to the user, while also heavily
// spamming our error reporting service.
DriveScannerService.stop();
return ErrorService.reportException(error);
});
DriveScannerService.start();
});
app.run(($window, AnalyticsService, WarningModalService, ErrorService, OSDialogService) => {
let popupExists = false;
$window.addEventListener('beforeunload', (event) => {
if (!flashState.isFlashing() || popupExists) {
AnalyticsService.logEvent('Close application', {
isFlashing: flashState.isFlashing()
});
return;
}
// Don't close window while flashing
event.returnValue = false;
// Don't open any more popups
popupExists = true;
AnalyticsService.logEvent('Close attempt while flashing');
OSDialogService.showWarning({
confirmationLabel: 'Yes, quit',
rejectionLabel: 'Cancel',
title: 'Are you sure you want to close Etcher?',
description: messages.warning.exitWhileFlashing()
}).then((confirmed) => {
if (confirmed) {
AnalyticsService.logEvent('Close confirmed while flashing', {
uuid: flashState.getFlashUuid()
});
// This circumvents the 'beforeunload' event unlike
// electron.remote.app.quit() which does not.
electron.remote.process.exit(EXIT_CODES.SUCCESS);
}
AnalyticsService.logEvent('Close rejected while flashing');
popupExists = false;
}).catch(ErrorService.reportException);
});
});
app.run(($rootScope, AnalyticsService) => {
$rootScope.$on('$stateChangeSuccess', (event, toState, toParams, fromState) => {
// Ignore first navigation
if (!fromState.name) {
return;
}
AnalyticsService.logEvent('Navigate', {
to: toState.name,
from: fromState.name
});
});
});
app.config(($urlRouterProvider) => {
$urlRouterProvider.otherwise('/main');
});
app.config(($provide) => {
$provide.decorator('$exceptionHandler', ($delegate, $injector) => {
return (exception, cause) => {
const ErrorService = $injector.get('ErrorService');
ErrorService.reportException(exception);
$delegate(exception, cause);
};
});
});
app.controller('HeaderController', function(SelectionStateModel, OSOpenExternalService) {
/**
* @summary Open help page
* @function
* @public
*
* @description
* This application will open either the image's support url, declared
* in the archive `manifest.json`, or the default Etcher help page.
*
* @example
* HeaderController.openHelpPage();
*/
this.openHelpPage = () => {
const DEFAULT_SUPPORT_URL = 'https://github.com/resin-io/etcher/blob/master/SUPPORT.md';
const supportUrl = SelectionStateModel.getImageSupportUrl() || DEFAULT_SUPPORT_URL;
OSOpenExternalService.open(supportUrl);
};
});